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 editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction::Direction;
56pub use editor_settings::{
57 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
58 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
59};
60pub use editor_settings_controls::*;
61pub use element::{
62 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
71 RowInfo, ToOffset, ToPoint,
72};
73pub use proposed_changes_editor::{
74 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
75};
76pub use text::Bias;
77
78use ::git::{
79 Restore,
80 blame::{BlameEntry, ParsedCommitMessage},
81};
82use aho_corasick::AhoCorasick;
83use anyhow::{Context as _, Result, anyhow};
84use blink_manager::BlinkManager;
85use buffer_diff::DiffHunkStatus;
86use client::{Collaborator, ParticipantIndex, parse_zed_link};
87use clock::{AGENT_REPLICA_ID, ReplicaId};
88use code_context_menus::{
89 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
90 CompletionsMenu, ContextMenuOrigin,
91};
92use collections::{BTreeMap, HashMap, HashSet, VecDeque};
93use convert_case::{Case, Casing};
94use dap::TelemetrySpawnLocation;
95use display_map::*;
96use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
97use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
98use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
99use futures::{
100 FutureExt, StreamExt as _,
101 future::{self, Shared, join},
102 stream::FuturesUnordered,
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
112 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
113 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
114 div, point, prelude::*, pulsating_between, px, relative, size,
115};
116use highlight_matching_bracket::refresh_matching_bracket_highlights;
117use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
121use itertools::{Either, Itertools};
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
125 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
126 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
127 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145 ToOffsetUtf16,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
151 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
152 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
153 ProjectTransaction, TaskSourceKind,
154 debugger::{
155 breakpoint_store::{
156 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
157 BreakpointStore, BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
164};
165use rand::seq::SliceRandom;
166use rpc::{ErrorCode, ErrorExt, proto::PeerId};
167use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
168use selections_collection::{
169 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
170};
171use serde::{Deserialize, Serialize};
172use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
173use smallvec::{SmallVec, smallvec};
174use snippet::Snippet;
175use std::{
176 any::{Any, TypeId},
177 borrow::Cow,
178 cell::{OnceCell, RefCell},
179 cmp::{self, Ordering, Reverse},
180 iter::{self, Peekable},
181 mem,
182 num::NonZeroU32,
183 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
184 path::{Path, PathBuf},
185 rc::Rc,
186 sync::Arc,
187 time::{Duration, Instant},
188};
189use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
190use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
191use theme::{
192 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
193 observe_buffer_font_size_adjustment,
194};
195use ui::{
196 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
197 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
198};
199use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
200use workspace::{
201 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
202 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
203 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
204 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
205 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
206 searchable::SearchEvent,
207};
208
209use crate::{
210 code_context_menus::CompletionsMenuSource,
211 editor_settings::MultiCursorModifier,
212 hover_links::{find_url, find_url_from_range},
213 scroll::{ScrollOffset, ScrollPixelOffset},
214 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
215};
216
217pub const FILE_HEADER_HEIGHT: u32 = 2;
218pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
219const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
220const MAX_LINE_LEN: usize = 1024;
221const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
222const MAX_SELECTION_HISTORY_LEN: usize = 1024;
223pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
224#[doc(hidden)]
225pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
226pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
227
228pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
229pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
230pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
231
232pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
233pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
234pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
235
236pub type RenderDiffHunkControlsFn = Arc<
237 dyn Fn(
238 u32,
239 &DiffHunkStatus,
240 Range<Anchor>,
241 bool,
242 Pixels,
243 &Entity<Editor>,
244 &mut Window,
245 &mut App,
246 ) -> AnyElement,
247>;
248
249enum ReportEditorEvent {
250 Saved { auto_saved: bool },
251 EditorOpened,
252 Closed,
253}
254
255impl ReportEditorEvent {
256 pub fn event_type(&self) -> &'static str {
257 match self {
258 Self::Saved { .. } => "Editor Saved",
259 Self::EditorOpened => "Editor Opened",
260 Self::Closed => "Editor Closed",
261 }
262 }
263}
264
265struct InlineValueCache {
266 enabled: bool,
267 inlays: Vec<InlayId>,
268 refresh_task: Task<Option<()>>,
269}
270
271impl InlineValueCache {
272 fn new(enabled: bool) -> Self {
273 Self {
274 enabled,
275 inlays: Vec::new(),
276 refresh_task: Task::ready(None),
277 }
278 }
279}
280
281#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
282pub enum InlayId {
283 EditPrediction(u32),
284 DebuggerValue(u32),
285 // LSP
286 Hint(u32),
287 Color(u32),
288}
289
290impl InlayId {
291 fn id(&self) -> u32 {
292 match self {
293 Self::EditPrediction(id) => *id,
294 Self::DebuggerValue(id) => *id,
295 Self::Hint(id) => *id,
296 Self::Color(id) => *id,
297 }
298 }
299}
300
301pub enum ActiveDebugLine {}
302pub enum DebugStackFrameLine {}
303enum DocumentHighlightRead {}
304enum DocumentHighlightWrite {}
305enum InputComposition {}
306pub enum PendingInput {}
307enum SelectedTextHighlight {}
308
309pub enum ConflictsOuter {}
310pub enum ConflictsOurs {}
311pub enum ConflictsTheirs {}
312pub enum ConflictsOursMarker {}
313pub enum ConflictsTheirsMarker {}
314
315#[derive(Debug, Copy, Clone, PartialEq, Eq)]
316pub enum Navigated {
317 Yes,
318 No,
319}
320
321impl Navigated {
322 pub fn from_bool(yes: bool) -> Navigated {
323 if yes { Navigated::Yes } else { Navigated::No }
324 }
325}
326
327#[derive(Debug, Clone, PartialEq, Eq)]
328enum DisplayDiffHunk {
329 Folded {
330 display_row: DisplayRow,
331 },
332 Unfolded {
333 is_created_file: bool,
334 diff_base_byte_range: Range<usize>,
335 display_row_range: Range<DisplayRow>,
336 multi_buffer_range: Range<Anchor>,
337 status: DiffHunkStatus,
338 },
339}
340
341pub enum HideMouseCursorOrigin {
342 TypingAction,
343 MovementAction,
344}
345
346pub fn init_settings(cx: &mut App) {
347 EditorSettings::register(cx);
348}
349
350pub fn init(cx: &mut App) {
351 init_settings(cx);
352
353 cx.set_global(GlobalBlameRenderer(Arc::new(())));
354
355 workspace::register_project_item::<Editor>(cx);
356 workspace::FollowableViewRegistry::register::<Editor>(cx);
357 workspace::register_serializable_item::<Editor>(cx);
358
359 cx.observe_new(
360 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
361 workspace.register_action(Editor::new_file);
362 workspace.register_action(Editor::new_file_vertical);
363 workspace.register_action(Editor::new_file_horizontal);
364 workspace.register_action(Editor::cancel_language_server_work);
365 workspace.register_action(Editor::toggle_focus);
366 },
367 )
368 .detach();
369
370 cx.on_action(move |_: &workspace::NewFile, cx| {
371 let app_state = workspace::AppState::global(cx);
372 if let Some(app_state) = app_state.upgrade() {
373 workspace::open_new(
374 Default::default(),
375 app_state,
376 cx,
377 |workspace, window, cx| {
378 Editor::new_file(workspace, &Default::default(), window, cx)
379 },
380 )
381 .detach();
382 }
383 });
384 cx.on_action(move |_: &workspace::NewWindow, cx| {
385 let app_state = workspace::AppState::global(cx);
386 if let Some(app_state) = app_state.upgrade() {
387 workspace::open_new(
388 Default::default(),
389 app_state,
390 cx,
391 |workspace, window, cx| {
392 cx.activate(true);
393 Editor::new_file(workspace, &Default::default(), window, cx)
394 },
395 )
396 .detach();
397 }
398 });
399}
400
401pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
402 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
403}
404
405pub trait DiagnosticRenderer {
406 fn render_group(
407 &self,
408 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
409 buffer_id: BufferId,
410 snapshot: EditorSnapshot,
411 editor: WeakEntity<Editor>,
412 cx: &mut App,
413 ) -> Vec<BlockProperties<Anchor>>;
414
415 fn render_hover(
416 &self,
417 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
418 range: Range<Point>,
419 buffer_id: BufferId,
420 cx: &mut App,
421 ) -> Option<Entity<markdown::Markdown>>;
422
423 fn open_link(
424 &self,
425 editor: &mut Editor,
426 link: SharedString,
427 window: &mut Window,
428 cx: &mut Context<Editor>,
429 );
430}
431
432pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
433
434impl GlobalDiagnosticRenderer {
435 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
436 cx.try_global::<Self>().map(|g| g.0.clone())
437 }
438}
439
440impl gpui::Global for GlobalDiagnosticRenderer {}
441pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
442 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
443}
444
445pub struct SearchWithinRange;
446
447trait InvalidationRegion {
448 fn ranges(&self) -> &[Range<Anchor>];
449}
450
451#[derive(Clone, Debug, PartialEq)]
452pub enum SelectPhase {
453 Begin {
454 position: DisplayPoint,
455 add: bool,
456 click_count: usize,
457 },
458 BeginColumnar {
459 position: DisplayPoint,
460 reset: bool,
461 mode: ColumnarMode,
462 goal_column: u32,
463 },
464 Extend {
465 position: DisplayPoint,
466 click_count: usize,
467 },
468 Update {
469 position: DisplayPoint,
470 goal_column: u32,
471 scroll_delta: gpui::Point<f32>,
472 },
473 End,
474}
475
476#[derive(Clone, Debug, PartialEq)]
477pub enum ColumnarMode {
478 FromMouse,
479 FromSelection,
480}
481
482#[derive(Clone, Debug)]
483pub enum SelectMode {
484 Character,
485 Word(Range<Anchor>),
486 Line(Range<Anchor>),
487 All,
488}
489
490#[derive(Clone, PartialEq, Eq, Debug)]
491pub enum EditorMode {
492 SingleLine,
493 AutoHeight {
494 min_lines: usize,
495 max_lines: Option<usize>,
496 },
497 Full {
498 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
499 scale_ui_elements_with_buffer_font_size: bool,
500 /// When set to `true`, the editor will render a background for the active line.
501 show_active_line_background: bool,
502 /// When set to `true`, the editor's height will be determined by its content.
503 sized_by_content: bool,
504 },
505 Minimap {
506 parent: WeakEntity<Editor>,
507 },
508}
509
510impl EditorMode {
511 pub fn full() -> Self {
512 Self::Full {
513 scale_ui_elements_with_buffer_font_size: true,
514 show_active_line_background: true,
515 sized_by_content: false,
516 }
517 }
518
519 #[inline]
520 pub fn is_full(&self) -> bool {
521 matches!(self, Self::Full { .. })
522 }
523
524 #[inline]
525 pub fn is_single_line(&self) -> bool {
526 matches!(self, Self::SingleLine { .. })
527 }
528
529 #[inline]
530 fn is_minimap(&self) -> bool {
531 matches!(self, Self::Minimap { .. })
532 }
533}
534
535#[derive(Copy, Clone, Debug)]
536pub enum SoftWrap {
537 /// Prefer not to wrap at all.
538 ///
539 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
540 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
541 GitDiff,
542 /// Prefer a single line generally, unless an overly long line is encountered.
543 None,
544 /// Soft wrap lines that exceed the editor width.
545 EditorWidth,
546 /// Soft wrap lines at the preferred line length.
547 Column(u32),
548 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
549 Bounded(u32),
550}
551
552#[derive(Clone)]
553pub struct EditorStyle {
554 pub background: Hsla,
555 pub border: Hsla,
556 pub local_player: PlayerColor,
557 pub text: TextStyle,
558 pub scrollbar_width: Pixels,
559 pub syntax: Arc<SyntaxTheme>,
560 pub status: StatusColors,
561 pub inlay_hints_style: HighlightStyle,
562 pub edit_prediction_styles: EditPredictionStyles,
563 pub unnecessary_code_fade: f32,
564 pub show_underlines: bool,
565}
566
567impl Default for EditorStyle {
568 fn default() -> Self {
569 Self {
570 background: Hsla::default(),
571 border: Hsla::default(),
572 local_player: PlayerColor::default(),
573 text: TextStyle::default(),
574 scrollbar_width: Pixels::default(),
575 syntax: Default::default(),
576 // HACK: Status colors don't have a real default.
577 // We should look into removing the status colors from the editor
578 // style and retrieve them directly from the theme.
579 status: StatusColors::dark(),
580 inlay_hints_style: HighlightStyle::default(),
581 edit_prediction_styles: EditPredictionStyles {
582 insertion: HighlightStyle::default(),
583 whitespace: HighlightStyle::default(),
584 },
585 unnecessary_code_fade: Default::default(),
586 show_underlines: true,
587 }
588 }
589}
590
591pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
592 let show_background = language_settings::language_settings(None, None, cx)
593 .inlay_hints
594 .show_background;
595
596 let mut style = cx.theme().syntax().get("hint");
597
598 if style.color.is_none() {
599 style.color = Some(cx.theme().status().hint);
600 }
601
602 if !show_background {
603 style.background_color = None;
604 return style;
605 }
606
607 if style.background_color.is_none() {
608 style.background_color = Some(cx.theme().status().hint_background);
609 }
610
611 style
612}
613
614pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
615 EditPredictionStyles {
616 insertion: HighlightStyle {
617 color: Some(cx.theme().status().predictive),
618 ..HighlightStyle::default()
619 },
620 whitespace: HighlightStyle {
621 background_color: Some(cx.theme().status().created_background),
622 ..HighlightStyle::default()
623 },
624 }
625}
626
627type CompletionId = usize;
628
629pub(crate) enum EditDisplayMode {
630 TabAccept,
631 DiffPopover,
632 Inline,
633}
634
635enum EditPrediction {
636 Edit {
637 edits: Vec<(Range<Anchor>, String)>,
638 edit_preview: Option<EditPreview>,
639 display_mode: EditDisplayMode,
640 snapshot: BufferSnapshot,
641 },
642 /// Move to a specific location in the active editor
643 MoveWithin {
644 target: Anchor,
645 snapshot: BufferSnapshot,
646 },
647 /// Move to a specific location in a different editor (not the active one)
648 MoveOutside {
649 target: language::Anchor,
650 snapshot: BufferSnapshot,
651 },
652}
653
654struct EditPredictionState {
655 inlay_ids: Vec<InlayId>,
656 completion: EditPrediction,
657 completion_id: Option<SharedString>,
658 invalidation_range: Option<Range<Anchor>>,
659}
660
661enum EditPredictionSettings {
662 Disabled,
663 Enabled {
664 show_in_menu: bool,
665 preview_requires_modifier: bool,
666 },
667}
668
669enum EditPredictionHighlight {}
670
671#[derive(Debug, Clone)]
672struct InlineDiagnostic {
673 message: SharedString,
674 group_id: usize,
675 is_primary: bool,
676 start: Point,
677 severity: lsp::DiagnosticSeverity,
678}
679
680pub enum MenuEditPredictionsPolicy {
681 Never,
682 ByProvider,
683}
684
685pub enum EditPredictionPreview {
686 /// Modifier is not pressed
687 Inactive { released_too_fast: bool },
688 /// Modifier pressed
689 Active {
690 since: Instant,
691 previous_scroll_position: Option<ScrollAnchor>,
692 },
693}
694
695impl EditPredictionPreview {
696 pub fn released_too_fast(&self) -> bool {
697 match self {
698 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
699 EditPredictionPreview::Active { .. } => false,
700 }
701 }
702
703 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
704 if let EditPredictionPreview::Active {
705 previous_scroll_position,
706 ..
707 } = self
708 {
709 *previous_scroll_position = scroll_position;
710 }
711 }
712}
713
714pub struct ContextMenuOptions {
715 pub min_entries_visible: usize,
716 pub max_entries_visible: usize,
717 pub placement: Option<ContextMenuPlacement>,
718}
719
720#[derive(Debug, Clone, PartialEq, Eq)]
721pub enum ContextMenuPlacement {
722 Above,
723 Below,
724}
725
726#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
727struct EditorActionId(usize);
728
729impl EditorActionId {
730 pub fn post_inc(&mut self) -> Self {
731 let answer = self.0;
732
733 *self = Self(answer + 1);
734
735 Self(answer)
736 }
737}
738
739// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
740// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
741
742type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
743type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
744
745#[derive(Default)]
746struct ScrollbarMarkerState {
747 scrollbar_size: Size<Pixels>,
748 dirty: bool,
749 markers: Arc<[PaintQuad]>,
750 pending_refresh: Option<Task<Result<()>>>,
751}
752
753impl ScrollbarMarkerState {
754 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
755 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
756 }
757}
758
759#[derive(Clone, Copy, PartialEq, Eq)]
760pub enum MinimapVisibility {
761 Disabled,
762 Enabled {
763 /// The configuration currently present in the users settings.
764 setting_configuration: bool,
765 /// Whether to override the currently set visibility from the users setting.
766 toggle_override: bool,
767 },
768}
769
770impl MinimapVisibility {
771 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
772 if mode.is_full() {
773 Self::Enabled {
774 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
775 toggle_override: false,
776 }
777 } else {
778 Self::Disabled
779 }
780 }
781
782 fn hidden(&self) -> Self {
783 match *self {
784 Self::Enabled {
785 setting_configuration,
786 ..
787 } => Self::Enabled {
788 setting_configuration,
789 toggle_override: setting_configuration,
790 },
791 Self::Disabled => Self::Disabled,
792 }
793 }
794
795 fn disabled(&self) -> bool {
796 matches!(*self, Self::Disabled)
797 }
798
799 fn settings_visibility(&self) -> bool {
800 match *self {
801 Self::Enabled {
802 setting_configuration,
803 ..
804 } => setting_configuration,
805 _ => false,
806 }
807 }
808
809 fn visible(&self) -> bool {
810 match *self {
811 Self::Enabled {
812 setting_configuration,
813 toggle_override,
814 } => setting_configuration ^ toggle_override,
815 _ => false,
816 }
817 }
818
819 fn toggle_visibility(&self) -> Self {
820 match *self {
821 Self::Enabled {
822 toggle_override,
823 setting_configuration,
824 } => Self::Enabled {
825 setting_configuration,
826 toggle_override: !toggle_override,
827 },
828 Self::Disabled => Self::Disabled,
829 }
830 }
831}
832
833#[derive(Clone, Debug)]
834struct RunnableTasks {
835 templates: Vec<(TaskSourceKind, TaskTemplate)>,
836 offset: multi_buffer::Anchor,
837 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
838 column: u32,
839 // Values of all named captures, including those starting with '_'
840 extra_variables: HashMap<String, String>,
841 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
842 context_range: Range<BufferOffset>,
843}
844
845impl RunnableTasks {
846 fn resolve<'a>(
847 &'a self,
848 cx: &'a task::TaskContext,
849 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
850 self.templates.iter().filter_map(|(kind, template)| {
851 template
852 .resolve_task(&kind.to_id_base(), cx)
853 .map(|task| (kind.clone(), task))
854 })
855 }
856}
857
858#[derive(Clone)]
859pub struct ResolvedTasks {
860 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
861 position: Anchor,
862}
863
864#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
865struct BufferOffset(usize);
866
867/// Addons allow storing per-editor state in other crates (e.g. Vim)
868pub trait Addon: 'static {
869 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
870
871 fn render_buffer_header_controls(
872 &self,
873 _: &ExcerptInfo,
874 _: &Window,
875 _: &App,
876 ) -> Option<AnyElement> {
877 None
878 }
879
880 fn to_any(&self) -> &dyn std::any::Any;
881
882 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
883 None
884 }
885}
886
887struct ChangeLocation {
888 current: Option<Vec<Anchor>>,
889 original: Vec<Anchor>,
890}
891impl ChangeLocation {
892 fn locations(&self) -> &[Anchor] {
893 self.current.as_ref().unwrap_or(&self.original)
894 }
895}
896
897/// A set of caret positions, registered when the editor was edited.
898pub struct ChangeList {
899 changes: Vec<ChangeLocation>,
900 /// Currently "selected" change.
901 position: Option<usize>,
902}
903
904impl ChangeList {
905 pub fn new() -> Self {
906 Self {
907 changes: Vec::new(),
908 position: None,
909 }
910 }
911
912 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
913 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
914 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
915 if self.changes.is_empty() {
916 return None;
917 }
918
919 let prev = self.position.unwrap_or(self.changes.len());
920 let next = if direction == Direction::Prev {
921 prev.saturating_sub(count)
922 } else {
923 (prev + count).min(self.changes.len() - 1)
924 };
925 self.position = Some(next);
926 self.changes.get(next).map(|change| change.locations())
927 }
928
929 /// Adds a new change to the list, resetting the change list position.
930 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
931 self.position.take();
932 if let Some(last) = self.changes.last_mut()
933 && group
934 {
935 last.current = Some(new_positions)
936 } else {
937 self.changes.push(ChangeLocation {
938 original: new_positions,
939 current: None,
940 });
941 }
942 }
943
944 pub fn last(&self) -> Option<&[Anchor]> {
945 self.changes.last().map(|change| change.locations())
946 }
947
948 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
949 self.changes.last().map(|change| change.original.as_slice())
950 }
951
952 pub fn invert_last_group(&mut self) {
953 if let Some(last) = self.changes.last_mut()
954 && let Some(current) = last.current.as_mut()
955 {
956 mem::swap(&mut last.original, current);
957 }
958 }
959}
960
961#[derive(Clone)]
962struct InlineBlamePopoverState {
963 scroll_handle: ScrollHandle,
964 commit_message: Option<ParsedCommitMessage>,
965 markdown: Entity<Markdown>,
966}
967
968struct InlineBlamePopover {
969 position: gpui::Point<Pixels>,
970 hide_task: Option<Task<()>>,
971 popover_bounds: Option<Bounds<Pixels>>,
972 popover_state: InlineBlamePopoverState,
973 keyboard_grace: bool,
974}
975
976enum SelectionDragState {
977 /// State when no drag related activity is detected.
978 None,
979 /// State when the mouse is down on a selection that is about to be dragged.
980 ReadyToDrag {
981 selection: Selection<Anchor>,
982 click_position: gpui::Point<Pixels>,
983 mouse_down_time: Instant,
984 },
985 /// State when the mouse is dragging the selection in the editor.
986 Dragging {
987 selection: Selection<Anchor>,
988 drop_cursor: Selection<Anchor>,
989 hide_drop_cursor: bool,
990 },
991}
992
993enum ColumnarSelectionState {
994 FromMouse {
995 selection_tail: Anchor,
996 display_point: Option<DisplayPoint>,
997 },
998 FromSelection {
999 selection_tail: Anchor,
1000 },
1001}
1002
1003/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1004/// a breakpoint on them.
1005#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1006struct PhantomBreakpointIndicator {
1007 display_row: DisplayRow,
1008 /// There's a small debounce between hovering over the line and showing the indicator.
1009 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1010 is_active: bool,
1011 collides_with_existing_breakpoint: bool,
1012}
1013
1014/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1015///
1016/// See the [module level documentation](self) for more information.
1017pub struct Editor {
1018 focus_handle: FocusHandle,
1019 last_focused_descendant: Option<WeakFocusHandle>,
1020 /// The text buffer being edited
1021 buffer: Entity<MultiBuffer>,
1022 /// Map of how text in the buffer should be displayed.
1023 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1024 pub display_map: Entity<DisplayMap>,
1025 placeholder_display_map: Option<Entity<DisplayMap>>,
1026 pub selections: SelectionsCollection,
1027 pub scroll_manager: ScrollManager,
1028 /// When inline assist editors are linked, they all render cursors because
1029 /// typing enters text into each of them, even the ones that aren't focused.
1030 pub(crate) show_cursor_when_unfocused: bool,
1031 columnar_selection_state: Option<ColumnarSelectionState>,
1032 add_selections_state: Option<AddSelectionsState>,
1033 select_next_state: Option<SelectNextState>,
1034 select_prev_state: Option<SelectNextState>,
1035 selection_history: SelectionHistory,
1036 defer_selection_effects: bool,
1037 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1038 autoclose_regions: Vec<AutocloseRegion>,
1039 snippet_stack: InvalidationStack<SnippetState>,
1040 select_syntax_node_history: SelectSyntaxNodeHistory,
1041 ime_transaction: Option<TransactionId>,
1042 pub diagnostics_max_severity: DiagnosticSeverity,
1043 active_diagnostics: ActiveDiagnostic,
1044 show_inline_diagnostics: bool,
1045 inline_diagnostics_update: Task<()>,
1046 inline_diagnostics_enabled: bool,
1047 diagnostics_enabled: bool,
1048 word_completions_enabled: bool,
1049 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1050 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1051 hard_wrap: Option<usize>,
1052 project: Option<Entity<Project>>,
1053 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1054 completion_provider: Option<Rc<dyn CompletionProvider>>,
1055 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1056 blink_manager: Entity<BlinkManager>,
1057 show_cursor_names: bool,
1058 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1059 pub show_local_selections: bool,
1060 mode: EditorMode,
1061 show_breadcrumbs: bool,
1062 show_gutter: bool,
1063 show_scrollbars: ScrollbarAxes,
1064 minimap_visibility: MinimapVisibility,
1065 offset_content: bool,
1066 disable_expand_excerpt_buttons: bool,
1067 show_line_numbers: Option<bool>,
1068 use_relative_line_numbers: Option<bool>,
1069 show_git_diff_gutter: Option<bool>,
1070 show_code_actions: Option<bool>,
1071 show_runnables: Option<bool>,
1072 show_breakpoints: Option<bool>,
1073 show_wrap_guides: Option<bool>,
1074 show_indent_guides: Option<bool>,
1075 highlight_order: usize,
1076 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1077 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1078 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1079 scrollbar_marker_state: ScrollbarMarkerState,
1080 active_indent_guides_state: ActiveIndentGuidesState,
1081 nav_history: Option<ItemNavHistory>,
1082 context_menu: RefCell<Option<CodeContextMenu>>,
1083 context_menu_options: Option<ContextMenuOptions>,
1084 mouse_context_menu: Option<MouseContextMenu>,
1085 completion_tasks: Vec<(CompletionId, Task<()>)>,
1086 inline_blame_popover: Option<InlineBlamePopover>,
1087 inline_blame_popover_show_task: Option<Task<()>>,
1088 signature_help_state: SignatureHelpState,
1089 auto_signature_help: Option<bool>,
1090 find_all_references_task_sources: Vec<Anchor>,
1091 next_completion_id: CompletionId,
1092 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1093 code_actions_task: Option<Task<Result<()>>>,
1094 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1095 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1096 document_highlights_task: Option<Task<()>>,
1097 linked_editing_range_task: Option<Task<Option<()>>>,
1098 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1099 pending_rename: Option<RenameState>,
1100 searchable: bool,
1101 cursor_shape: CursorShape,
1102 current_line_highlight: Option<CurrentLineHighlight>,
1103 collapse_matches: bool,
1104 autoindent_mode: Option<AutoindentMode>,
1105 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1106 input_enabled: bool,
1107 use_modal_editing: bool,
1108 read_only: bool,
1109 leader_id: Option<CollaboratorId>,
1110 remote_id: Option<ViewId>,
1111 pub hover_state: HoverState,
1112 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1113 gutter_hovered: bool,
1114 hovered_link_state: Option<HoveredLinkState>,
1115 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1116 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1117 active_edit_prediction: Option<EditPredictionState>,
1118 /// Used to prevent flickering as the user types while the menu is open
1119 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1120 edit_prediction_settings: EditPredictionSettings,
1121 edit_predictions_hidden_for_vim_mode: bool,
1122 show_edit_predictions_override: Option<bool>,
1123 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1124 edit_prediction_preview: EditPredictionPreview,
1125 edit_prediction_indent_conflict: bool,
1126 edit_prediction_requires_modifier_in_indent_conflict: bool,
1127 inlay_hint_cache: InlayHintCache,
1128 next_inlay_id: u32,
1129 next_color_inlay_id: u32,
1130 _subscriptions: Vec<Subscription>,
1131 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1132 gutter_dimensions: GutterDimensions,
1133 style: Option<EditorStyle>,
1134 text_style_refinement: Option<TextStyleRefinement>,
1135 next_editor_action_id: EditorActionId,
1136 editor_actions: Rc<
1137 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1138 >,
1139 use_autoclose: bool,
1140 use_auto_surround: bool,
1141 auto_replace_emoji_shortcode: bool,
1142 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1143 show_git_blame_gutter: bool,
1144 show_git_blame_inline: bool,
1145 show_git_blame_inline_delay_task: Option<Task<()>>,
1146 git_blame_inline_enabled: bool,
1147 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1148 serialize_dirty_buffers: bool,
1149 show_selection_menu: Option<bool>,
1150 blame: Option<Entity<GitBlame>>,
1151 blame_subscription: Option<Subscription>,
1152 custom_context_menu: Option<
1153 Box<
1154 dyn 'static
1155 + Fn(
1156 &mut Self,
1157 DisplayPoint,
1158 &mut Window,
1159 &mut Context<Self>,
1160 ) -> Option<Entity<ui::ContextMenu>>,
1161 >,
1162 >,
1163 last_bounds: Option<Bounds<Pixels>>,
1164 last_position_map: Option<Rc<PositionMap>>,
1165 expect_bounds_change: Option<Bounds<Pixels>>,
1166 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1167 tasks_update_task: Option<Task<()>>,
1168 breakpoint_store: Option<Entity<BreakpointStore>>,
1169 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1170 hovered_diff_hunk_row: Option<DisplayRow>,
1171 pull_diagnostics_task: Task<()>,
1172 in_project_search: bool,
1173 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1174 breadcrumb_header: Option<String>,
1175 focused_block: Option<FocusedBlock>,
1176 next_scroll_position: NextScrollCursorCenterTopBottom,
1177 addons: HashMap<TypeId, Box<dyn Addon>>,
1178 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1179 load_diff_task: Option<Shared<Task<()>>>,
1180 /// Whether we are temporarily displaying a diff other than git's
1181 temporary_diff_override: bool,
1182 selection_mark_mode: bool,
1183 toggle_fold_multiple_buffers: Task<()>,
1184 _scroll_cursor_center_top_bottom_task: Task<()>,
1185 serialize_selections: Task<()>,
1186 serialize_folds: Task<()>,
1187 mouse_cursor_hidden: bool,
1188 minimap: Option<Entity<Self>>,
1189 hide_mouse_mode: HideMouseMode,
1190 pub change_list: ChangeList,
1191 inline_value_cache: InlineValueCache,
1192 selection_drag_state: SelectionDragState,
1193 colors: Option<LspColorData>,
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: u16,
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 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 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(), 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::LanguageServerAdded(..)
1886 | project::Event::LanguageServerRemoved(..) => {
1887 if editor.tasks_update_task.is_none() {
1888 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1889 }
1890 }
1891 project::Event::SnippetEdit(id, snippet_edits) => {
1892 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1893 let focus_handle = editor.focus_handle(cx);
1894 if focus_handle.is_focused(window) {
1895 let snapshot = buffer.read(cx).snapshot();
1896 for (range, snippet) in snippet_edits {
1897 let editor_range =
1898 language::range_from_lsp(*range).to_offset(&snapshot);
1899 editor
1900 .insert_snippet(
1901 &[editor_range],
1902 snippet.clone(),
1903 window,
1904 cx,
1905 )
1906 .ok();
1907 }
1908 }
1909 }
1910 }
1911 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1912 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1913 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1914 }
1915 }
1916
1917 project::Event::EntryRenamed(transaction) => {
1918 let Some(workspace) = editor.workspace() else {
1919 return;
1920 };
1921 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1922 else {
1923 return;
1924 };
1925 if active_editor.entity_id() == cx.entity_id() {
1926 let edited_buffers_already_open = {
1927 let other_editors: Vec<Entity<Editor>> = workspace
1928 .read(cx)
1929 .panes()
1930 .iter()
1931 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1932 .filter(|editor| editor.entity_id() != cx.entity_id())
1933 .collect();
1934
1935 transaction.0.keys().all(|buffer| {
1936 other_editors.iter().any(|editor| {
1937 let multi_buffer = editor.read(cx).buffer();
1938 multi_buffer.read(cx).is_singleton()
1939 && multi_buffer.read(cx).as_singleton().map_or(
1940 false,
1941 |singleton| {
1942 singleton.entity_id() == buffer.entity_id()
1943 },
1944 )
1945 })
1946 })
1947 };
1948
1949 if !edited_buffers_already_open {
1950 let workspace = workspace.downgrade();
1951 let transaction = transaction.clone();
1952 cx.defer_in(window, move |_, window, cx| {
1953 cx.spawn_in(window, async move |editor, cx| {
1954 Self::open_project_transaction(
1955 &editor,
1956 workspace,
1957 transaction,
1958 "Rename".to_string(),
1959 cx,
1960 )
1961 .await
1962 .ok()
1963 })
1964 .detach();
1965 });
1966 }
1967 }
1968 }
1969
1970 _ => {}
1971 },
1972 ));
1973 if let Some(task_inventory) = project
1974 .read(cx)
1975 .task_store()
1976 .read(cx)
1977 .task_inventory()
1978 .cloned()
1979 {
1980 project_subscriptions.push(cx.observe_in(
1981 &task_inventory,
1982 window,
1983 |editor, _, window, cx| {
1984 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1985 },
1986 ));
1987 };
1988
1989 project_subscriptions.push(cx.subscribe_in(
1990 &project.read(cx).breakpoint_store(),
1991 window,
1992 |editor, _, event, window, cx| match event {
1993 BreakpointStoreEvent::ClearDebugLines => {
1994 editor.clear_row_highlights::<ActiveDebugLine>();
1995 editor.refresh_inline_values(cx);
1996 }
1997 BreakpointStoreEvent::SetDebugLine => {
1998 if editor.go_to_active_debug_line(window, cx) {
1999 cx.stop_propagation();
2000 }
2001
2002 editor.refresh_inline_values(cx);
2003 }
2004 _ => {}
2005 },
2006 ));
2007 let git_store = project.read(cx).git_store().clone();
2008 let project = project.clone();
2009 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2010 if let GitStoreEvent::RepositoryUpdated(
2011 _,
2012 RepositoryEvent::Updated {
2013 new_instance: true, ..
2014 },
2015 _,
2016 ) = event
2017 {
2018 this.load_diff_task = Some(
2019 update_uncommitted_diff_for_buffer(
2020 cx.entity(),
2021 &project,
2022 this.buffer.read(cx).all_buffers(),
2023 this.buffer.clone(),
2024 cx,
2025 )
2026 .shared(),
2027 );
2028 }
2029 }));
2030 }
2031
2032 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2033
2034 let inlay_hint_settings =
2035 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2036 let focus_handle = cx.focus_handle();
2037 if !is_minimap {
2038 cx.on_focus(&focus_handle, window, Self::handle_focus)
2039 .detach();
2040 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2041 .detach();
2042 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2043 .detach();
2044 cx.on_blur(&focus_handle, window, Self::handle_blur)
2045 .detach();
2046 cx.observe_pending_input(window, Self::observe_pending_input)
2047 .detach();
2048 }
2049
2050 let show_indent_guides =
2051 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2052 Some(false)
2053 } else {
2054 None
2055 };
2056
2057 let breakpoint_store = match (&mode, project.as_ref()) {
2058 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2059 _ => None,
2060 };
2061
2062 let mut code_action_providers = Vec::new();
2063 let mut load_uncommitted_diff = None;
2064 if let Some(project) = project.clone() {
2065 load_uncommitted_diff = Some(
2066 update_uncommitted_diff_for_buffer(
2067 cx.entity(),
2068 &project,
2069 buffer.read(cx).all_buffers(),
2070 buffer.clone(),
2071 cx,
2072 )
2073 .shared(),
2074 );
2075 code_action_providers.push(Rc::new(project) as Rc<_>);
2076 }
2077
2078 let mut editor = Self {
2079 focus_handle,
2080 show_cursor_when_unfocused: false,
2081 last_focused_descendant: None,
2082 buffer: buffer.clone(),
2083 display_map: display_map.clone(),
2084 placeholder_display_map: None,
2085 selections,
2086 scroll_manager: ScrollManager::new(cx),
2087 columnar_selection_state: None,
2088 add_selections_state: None,
2089 select_next_state: None,
2090 select_prev_state: None,
2091 selection_history: SelectionHistory::default(),
2092 defer_selection_effects: false,
2093 deferred_selection_effects_state: None,
2094 autoclose_regions: Vec::new(),
2095 snippet_stack: InvalidationStack::default(),
2096 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2097 ime_transaction: None,
2098 active_diagnostics: ActiveDiagnostic::None,
2099 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2100 inline_diagnostics_update: Task::ready(()),
2101 inline_diagnostics: Vec::new(),
2102 soft_wrap_mode_override,
2103 diagnostics_max_severity,
2104 hard_wrap: None,
2105 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2106 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2107 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2108 project,
2109 blink_manager: blink_manager.clone(),
2110 show_local_selections: true,
2111 show_scrollbars: ScrollbarAxes {
2112 horizontal: full_mode,
2113 vertical: full_mode,
2114 },
2115 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2116 offset_content: !matches!(mode, EditorMode::SingleLine),
2117 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2118 show_gutter: full_mode,
2119 show_line_numbers: (!full_mode).then_some(false),
2120 use_relative_line_numbers: None,
2121 disable_expand_excerpt_buttons: !full_mode,
2122 show_git_diff_gutter: None,
2123 show_code_actions: None,
2124 show_runnables: None,
2125 show_breakpoints: None,
2126 show_wrap_guides: None,
2127 show_indent_guides,
2128 highlight_order: 0,
2129 highlighted_rows: HashMap::default(),
2130 background_highlights: HashMap::default(),
2131 gutter_highlights: HashMap::default(),
2132 scrollbar_marker_state: ScrollbarMarkerState::default(),
2133 active_indent_guides_state: ActiveIndentGuidesState::default(),
2134 nav_history: None,
2135 context_menu: RefCell::new(None),
2136 context_menu_options: None,
2137 mouse_context_menu: None,
2138 completion_tasks: Vec::new(),
2139 inline_blame_popover: None,
2140 inline_blame_popover_show_task: None,
2141 signature_help_state: SignatureHelpState::default(),
2142 auto_signature_help: None,
2143 find_all_references_task_sources: Vec::new(),
2144 next_completion_id: 0,
2145 next_inlay_id: 0,
2146 code_action_providers,
2147 available_code_actions: None,
2148 code_actions_task: None,
2149 quick_selection_highlight_task: None,
2150 debounced_selection_highlight_task: None,
2151 document_highlights_task: None,
2152 linked_editing_range_task: None,
2153 pending_rename: None,
2154 searchable: !is_minimap,
2155 cursor_shape: EditorSettings::get_global(cx)
2156 .cursor_shape
2157 .unwrap_or_default(),
2158 current_line_highlight: None,
2159 autoindent_mode: Some(AutoindentMode::EachLine),
2160 collapse_matches: false,
2161 workspace: None,
2162 input_enabled: !is_minimap,
2163 use_modal_editing: full_mode,
2164 read_only: is_minimap,
2165 use_autoclose: true,
2166 use_auto_surround: true,
2167 auto_replace_emoji_shortcode: false,
2168 jsx_tag_auto_close_enabled_in_any_buffer: false,
2169 leader_id: None,
2170 remote_id: None,
2171 hover_state: HoverState::default(),
2172 pending_mouse_down: None,
2173 hovered_link_state: None,
2174 edit_prediction_provider: None,
2175 active_edit_prediction: None,
2176 stale_edit_prediction_in_menu: None,
2177 edit_prediction_preview: EditPredictionPreview::Inactive {
2178 released_too_fast: false,
2179 },
2180 inline_diagnostics_enabled: full_mode,
2181 diagnostics_enabled: full_mode,
2182 word_completions_enabled: full_mode,
2183 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2184 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2185 gutter_hovered: false,
2186 pixel_position_of_newest_cursor: None,
2187 last_bounds: None,
2188 last_position_map: None,
2189 expect_bounds_change: None,
2190 gutter_dimensions: GutterDimensions::default(),
2191 style: None,
2192 show_cursor_names: false,
2193 hovered_cursors: HashMap::default(),
2194 next_editor_action_id: EditorActionId::default(),
2195 editor_actions: Rc::default(),
2196 edit_predictions_hidden_for_vim_mode: false,
2197 show_edit_predictions_override: None,
2198 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2199 edit_prediction_settings: EditPredictionSettings::Disabled,
2200 edit_prediction_indent_conflict: false,
2201 edit_prediction_requires_modifier_in_indent_conflict: true,
2202 custom_context_menu: None,
2203 show_git_blame_gutter: false,
2204 show_git_blame_inline: false,
2205 show_selection_menu: None,
2206 show_git_blame_inline_delay_task: None,
2207 git_blame_inline_enabled: full_mode
2208 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2209 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2210 serialize_dirty_buffers: !is_minimap
2211 && ProjectSettings::get_global(cx)
2212 .session
2213 .restore_unsaved_buffers,
2214 blame: None,
2215 blame_subscription: None,
2216 tasks: BTreeMap::default(),
2217
2218 breakpoint_store,
2219 gutter_breakpoint_indicator: (None, None),
2220 hovered_diff_hunk_row: None,
2221 _subscriptions: (!is_minimap)
2222 .then(|| {
2223 vec![
2224 cx.observe(&buffer, Self::on_buffer_changed),
2225 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2226 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2227 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2228 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2229 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2230 cx.observe_window_activation(window, |editor, window, cx| {
2231 let active = window.is_window_active();
2232 editor.blink_manager.update(cx, |blink_manager, cx| {
2233 if active {
2234 blink_manager.enable(cx);
2235 } else {
2236 blink_manager.disable(cx);
2237 }
2238 });
2239 if active {
2240 editor.show_mouse_cursor(cx);
2241 }
2242 }),
2243 ]
2244 })
2245 .unwrap_or_default(),
2246 tasks_update_task: None,
2247 pull_diagnostics_task: Task::ready(()),
2248 colors: None,
2249 next_color_inlay_id: 0,
2250 linked_edit_ranges: Default::default(),
2251 in_project_search: false,
2252 previous_search_ranges: None,
2253 breadcrumb_header: None,
2254 focused_block: None,
2255 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2256 addons: HashMap::default(),
2257 registered_buffers: HashMap::default(),
2258 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2259 selection_mark_mode: false,
2260 toggle_fold_multiple_buffers: Task::ready(()),
2261 serialize_selections: Task::ready(()),
2262 serialize_folds: Task::ready(()),
2263 text_style_refinement: None,
2264 load_diff_task: load_uncommitted_diff,
2265 temporary_diff_override: false,
2266 mouse_cursor_hidden: false,
2267 minimap: None,
2268 hide_mouse_mode: EditorSettings::get_global(cx)
2269 .hide_mouse
2270 .unwrap_or_default(),
2271 change_list: ChangeList::new(),
2272 mode,
2273 selection_drag_state: SelectionDragState::None,
2274 folding_newlines: Task::ready(()),
2275 lookup_key: None,
2276 };
2277
2278 if is_minimap {
2279 return editor;
2280 }
2281
2282 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2283 editor
2284 ._subscriptions
2285 .push(cx.observe(breakpoints, |_, _, cx| {
2286 cx.notify();
2287 }));
2288 }
2289 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2290 editor._subscriptions.extend(project_subscriptions);
2291
2292 editor._subscriptions.push(cx.subscribe_in(
2293 &cx.entity(),
2294 window,
2295 |editor, _, e: &EditorEvent, window, cx| match e {
2296 EditorEvent::ScrollPositionChanged { local, .. } => {
2297 if *local {
2298 let new_anchor = editor.scroll_manager.anchor();
2299 let snapshot = editor.snapshot(window, cx);
2300 editor.update_restoration_data(cx, move |data| {
2301 data.scroll_position = (
2302 new_anchor.top_row(&snapshot.buffer_snapshot),
2303 new_anchor.offset,
2304 );
2305 });
2306 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2307 editor.inline_blame_popover.take();
2308 }
2309 }
2310 EditorEvent::Edited { .. } => {
2311 if !vim_enabled(cx) {
2312 let (map, selections) = editor.selections.all_adjusted_display(cx);
2313 let pop_state = editor
2314 .change_list
2315 .last()
2316 .map(|previous| {
2317 previous.len() == selections.len()
2318 && previous.iter().enumerate().all(|(ix, p)| {
2319 p.to_display_point(&map).row()
2320 == selections[ix].head().row()
2321 })
2322 })
2323 .unwrap_or(false);
2324 let new_positions = selections
2325 .into_iter()
2326 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2327 .collect();
2328 editor
2329 .change_list
2330 .push_to_change_list(pop_state, new_positions);
2331 }
2332 }
2333 _ => (),
2334 },
2335 ));
2336
2337 if let Some(dap_store) = editor
2338 .project
2339 .as_ref()
2340 .map(|project| project.read(cx).dap_store())
2341 {
2342 let weak_editor = cx.weak_entity();
2343
2344 editor
2345 ._subscriptions
2346 .push(
2347 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2348 let session_entity = cx.entity();
2349 weak_editor
2350 .update(cx, |editor, cx| {
2351 editor._subscriptions.push(
2352 cx.subscribe(&session_entity, Self::on_debug_session_event),
2353 );
2354 })
2355 .ok();
2356 }),
2357 );
2358
2359 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2360 editor
2361 ._subscriptions
2362 .push(cx.subscribe(&session, Self::on_debug_session_event));
2363 }
2364 }
2365
2366 // skip adding the initial selection to selection history
2367 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2368 editor.end_selection(window, cx);
2369 editor.selection_history.mode = SelectionHistoryMode::Normal;
2370
2371 editor.scroll_manager.show_scrollbars(window, cx);
2372 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2373
2374 if full_mode {
2375 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2376 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2377
2378 if editor.git_blame_inline_enabled {
2379 editor.start_git_blame_inline(false, window, cx);
2380 }
2381
2382 editor.go_to_active_debug_line(window, cx);
2383
2384 if let Some(buffer) = buffer.read(cx).as_singleton()
2385 && let Some(project) = editor.project()
2386 {
2387 let handle = project.update(cx, |project, cx| {
2388 project.register_buffer_with_language_servers(&buffer, cx)
2389 });
2390 editor
2391 .registered_buffers
2392 .insert(buffer.read(cx).remote_id(), handle);
2393 }
2394
2395 editor.minimap =
2396 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2397 editor.colors = Some(LspColorData::new(cx));
2398 editor.update_lsp_data(false, None, window, cx);
2399 }
2400
2401 if editor.mode.is_full() {
2402 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2403 }
2404
2405 editor
2406 }
2407
2408 pub fn deploy_mouse_context_menu(
2409 &mut self,
2410 position: gpui::Point<Pixels>,
2411 context_menu: Entity<ContextMenu>,
2412 window: &mut Window,
2413 cx: &mut Context<Self>,
2414 ) {
2415 self.mouse_context_menu = Some(MouseContextMenu::new(
2416 self,
2417 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2418 context_menu,
2419 window,
2420 cx,
2421 ));
2422 }
2423
2424 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2425 self.mouse_context_menu
2426 .as_ref()
2427 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2428 }
2429
2430 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2431 if self
2432 .selections
2433 .pending_anchor()
2434 .is_some_and(|pending_selection| {
2435 let snapshot = self.buffer().read(cx).snapshot(cx);
2436 pending_selection.range().includes(range, &snapshot)
2437 })
2438 {
2439 return true;
2440 }
2441
2442 self.selections
2443 .disjoint_in_range::<usize>(range.clone(), cx)
2444 .into_iter()
2445 .any(|selection| {
2446 // This is needed to cover a corner case, if we just check for an existing
2447 // selection in the fold range, having a cursor at the start of the fold
2448 // marks it as selected. Non-empty selections don't cause this.
2449 let length = selection.end - selection.start;
2450 length > 0
2451 })
2452 }
2453
2454 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2455 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2456 }
2457
2458 fn key_context_internal(
2459 &self,
2460 has_active_edit_prediction: bool,
2461 window: &Window,
2462 cx: &App,
2463 ) -> KeyContext {
2464 let mut key_context = KeyContext::new_with_defaults();
2465 key_context.add("Editor");
2466 let mode = match self.mode {
2467 EditorMode::SingleLine => "single_line",
2468 EditorMode::AutoHeight { .. } => "auto_height",
2469 EditorMode::Minimap { .. } => "minimap",
2470 EditorMode::Full { .. } => "full",
2471 };
2472
2473 if EditorSettings::jupyter_enabled(cx) {
2474 key_context.add("jupyter");
2475 }
2476
2477 key_context.set("mode", mode);
2478 if self.pending_rename.is_some() {
2479 key_context.add("renaming");
2480 }
2481
2482 match self.context_menu.borrow().as_ref() {
2483 Some(CodeContextMenu::Completions(menu)) => {
2484 if menu.visible() {
2485 key_context.add("menu");
2486 key_context.add("showing_completions");
2487 }
2488 }
2489 Some(CodeContextMenu::CodeActions(menu)) => {
2490 if menu.visible() {
2491 key_context.add("menu");
2492 key_context.add("showing_code_actions")
2493 }
2494 }
2495 None => {}
2496 }
2497
2498 if self.signature_help_state.has_multiple_signatures() {
2499 key_context.add("showing_signature_help");
2500 }
2501
2502 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2503 if !self.focus_handle(cx).contains_focused(window, cx)
2504 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2505 {
2506 for addon in self.addons.values() {
2507 addon.extend_key_context(&mut key_context, cx)
2508 }
2509 }
2510
2511 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2512 if let Some(extension) = singleton_buffer
2513 .read(cx)
2514 .file()
2515 .and_then(|file| file.path().extension())
2516 {
2517 key_context.set("extension", extension.to_string());
2518 }
2519 } else {
2520 key_context.add("multibuffer");
2521 }
2522
2523 if has_active_edit_prediction {
2524 if self.edit_prediction_in_conflict() {
2525 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2526 } else {
2527 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2528 key_context.add("copilot_suggestion");
2529 }
2530 }
2531
2532 if self.selection_mark_mode {
2533 key_context.add("selection_mode");
2534 }
2535
2536 key_context
2537 }
2538
2539 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2540 self.last_bounds.as_ref()
2541 }
2542
2543 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2544 if self.mouse_cursor_hidden {
2545 self.mouse_cursor_hidden = false;
2546 cx.notify();
2547 }
2548 }
2549
2550 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2551 let hide_mouse_cursor = match origin {
2552 HideMouseCursorOrigin::TypingAction => {
2553 matches!(
2554 self.hide_mouse_mode,
2555 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2556 )
2557 }
2558 HideMouseCursorOrigin::MovementAction => {
2559 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2560 }
2561 };
2562 if self.mouse_cursor_hidden != hide_mouse_cursor {
2563 self.mouse_cursor_hidden = hide_mouse_cursor;
2564 cx.notify();
2565 }
2566 }
2567
2568 pub fn edit_prediction_in_conflict(&self) -> bool {
2569 if !self.show_edit_predictions_in_menu() {
2570 return false;
2571 }
2572
2573 let showing_completions = self
2574 .context_menu
2575 .borrow()
2576 .as_ref()
2577 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2578
2579 showing_completions
2580 || self.edit_prediction_requires_modifier()
2581 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2582 // bindings to insert tab characters.
2583 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2584 }
2585
2586 pub fn accept_edit_prediction_keybind(
2587 &self,
2588 accept_partial: bool,
2589 window: &Window,
2590 cx: &App,
2591 ) -> AcceptEditPredictionBinding {
2592 let key_context = self.key_context_internal(true, window, cx);
2593 let in_conflict = self.edit_prediction_in_conflict();
2594
2595 let bindings = if accept_partial {
2596 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2597 } else {
2598 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2599 };
2600
2601 // TODO: if the binding contains multiple keystrokes, display all of them, not
2602 // just the first one.
2603 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2604 !in_conflict
2605 || binding
2606 .keystrokes()
2607 .first()
2608 .is_some_and(|keystroke| keystroke.modifiers().modified())
2609 }))
2610 }
2611
2612 pub fn new_file(
2613 workspace: &mut Workspace,
2614 _: &workspace::NewFile,
2615 window: &mut Window,
2616 cx: &mut Context<Workspace>,
2617 ) {
2618 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2619 "Failed to create buffer",
2620 window,
2621 cx,
2622 |e, _, _| match e.error_code() {
2623 ErrorCode::RemoteUpgradeRequired => Some(format!(
2624 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2625 e.error_tag("required").unwrap_or("the latest version")
2626 )),
2627 _ => None,
2628 },
2629 );
2630 }
2631
2632 pub fn new_in_workspace(
2633 workspace: &mut Workspace,
2634 window: &mut Window,
2635 cx: &mut Context<Workspace>,
2636 ) -> Task<Result<Entity<Editor>>> {
2637 let project = workspace.project().clone();
2638 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2639
2640 cx.spawn_in(window, async move |workspace, cx| {
2641 let buffer = create.await?;
2642 workspace.update_in(cx, |workspace, window, cx| {
2643 let editor =
2644 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2645 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2646 editor
2647 })
2648 })
2649 }
2650
2651 fn new_file_vertical(
2652 workspace: &mut Workspace,
2653 _: &workspace::NewFileSplitVertical,
2654 window: &mut Window,
2655 cx: &mut Context<Workspace>,
2656 ) {
2657 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2658 }
2659
2660 fn new_file_horizontal(
2661 workspace: &mut Workspace,
2662 _: &workspace::NewFileSplitHorizontal,
2663 window: &mut Window,
2664 cx: &mut Context<Workspace>,
2665 ) {
2666 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2667 }
2668
2669 fn new_file_in_direction(
2670 workspace: &mut Workspace,
2671 direction: SplitDirection,
2672 window: &mut Window,
2673 cx: &mut Context<Workspace>,
2674 ) {
2675 let project = workspace.project().clone();
2676 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2677
2678 cx.spawn_in(window, async move |workspace, cx| {
2679 let buffer = create.await?;
2680 workspace.update_in(cx, move |workspace, window, cx| {
2681 workspace.split_item(
2682 direction,
2683 Box::new(
2684 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2685 ),
2686 window,
2687 cx,
2688 )
2689 })?;
2690 anyhow::Ok(())
2691 })
2692 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2693 match e.error_code() {
2694 ErrorCode::RemoteUpgradeRequired => Some(format!(
2695 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2696 e.error_tag("required").unwrap_or("the latest version")
2697 )),
2698 _ => None,
2699 }
2700 });
2701 }
2702
2703 pub fn leader_id(&self) -> Option<CollaboratorId> {
2704 self.leader_id
2705 }
2706
2707 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2708 &self.buffer
2709 }
2710
2711 pub fn project(&self) -> Option<&Entity<Project>> {
2712 self.project.as_ref()
2713 }
2714
2715 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2716 self.workspace.as_ref()?.0.upgrade()
2717 }
2718
2719 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2720 self.buffer().read(cx).title(cx)
2721 }
2722
2723 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2724 let git_blame_gutter_max_author_length = self
2725 .render_git_blame_gutter(cx)
2726 .then(|| {
2727 if let Some(blame) = self.blame.as_ref() {
2728 let max_author_length =
2729 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2730 Some(max_author_length)
2731 } else {
2732 None
2733 }
2734 })
2735 .flatten();
2736
2737 EditorSnapshot {
2738 mode: self.mode.clone(),
2739 show_gutter: self.show_gutter,
2740 show_line_numbers: self.show_line_numbers,
2741 show_git_diff_gutter: self.show_git_diff_gutter,
2742 show_code_actions: self.show_code_actions,
2743 show_runnables: self.show_runnables,
2744 show_breakpoints: self.show_breakpoints,
2745 git_blame_gutter_max_author_length,
2746 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2747 placeholder_display_snapshot: self
2748 .placeholder_display_map
2749 .as_ref()
2750 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2751 scroll_anchor: self.scroll_manager.anchor(),
2752 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2753 is_focused: self.focus_handle.is_focused(window),
2754 current_line_highlight: self
2755 .current_line_highlight
2756 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2757 gutter_hovered: self.gutter_hovered,
2758 }
2759 }
2760
2761 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2762 self.buffer.read(cx).language_at(point, cx)
2763 }
2764
2765 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2766 self.buffer.read(cx).read(cx).file_at(point).cloned()
2767 }
2768
2769 pub fn active_excerpt(
2770 &self,
2771 cx: &App,
2772 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2773 self.buffer
2774 .read(cx)
2775 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2776 }
2777
2778 pub fn mode(&self) -> &EditorMode {
2779 &self.mode
2780 }
2781
2782 pub fn set_mode(&mut self, mode: EditorMode) {
2783 self.mode = mode;
2784 }
2785
2786 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2787 self.collaboration_hub.as_deref()
2788 }
2789
2790 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2791 self.collaboration_hub = Some(hub);
2792 }
2793
2794 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2795 self.in_project_search = in_project_search;
2796 }
2797
2798 pub fn set_custom_context_menu(
2799 &mut self,
2800 f: impl 'static
2801 + Fn(
2802 &mut Self,
2803 DisplayPoint,
2804 &mut Window,
2805 &mut Context<Self>,
2806 ) -> Option<Entity<ui::ContextMenu>>,
2807 ) {
2808 self.custom_context_menu = Some(Box::new(f))
2809 }
2810
2811 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2812 self.completion_provider = provider;
2813 }
2814
2815 #[cfg(any(test, feature = "test-support"))]
2816 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2817 self.completion_provider.clone()
2818 }
2819
2820 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2821 self.semantics_provider.clone()
2822 }
2823
2824 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2825 self.semantics_provider = provider;
2826 }
2827
2828 pub fn set_edit_prediction_provider<T>(
2829 &mut self,
2830 provider: Option<Entity<T>>,
2831 window: &mut Window,
2832 cx: &mut Context<Self>,
2833 ) where
2834 T: EditPredictionProvider,
2835 {
2836 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2837 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2838 if this.focus_handle.is_focused(window) {
2839 this.update_visible_edit_prediction(window, cx);
2840 }
2841 }),
2842 provider: Arc::new(provider),
2843 });
2844 self.update_edit_prediction_settings(cx);
2845 self.refresh_edit_prediction(false, false, window, cx);
2846 }
2847
2848 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2849 self.placeholder_display_map
2850 .as_ref()
2851 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2852 }
2853
2854 pub fn set_placeholder_text(
2855 &mut self,
2856 placeholder_text: &str,
2857 window: &mut Window,
2858 cx: &mut Context<Self>,
2859 ) {
2860 let multibuffer = cx
2861 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2862
2863 let style = window.text_style();
2864
2865 self.placeholder_display_map = Some(cx.new(|cx| {
2866 DisplayMap::new(
2867 multibuffer,
2868 style.font(),
2869 style.font_size.to_pixels(window.rem_size()),
2870 None,
2871 FILE_HEADER_HEIGHT,
2872 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2873 Default::default(),
2874 DiagnosticSeverity::Off,
2875 cx,
2876 )
2877 }));
2878 cx.notify();
2879 }
2880
2881 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2882 self.cursor_shape = cursor_shape;
2883
2884 // Disrupt blink for immediate user feedback that the cursor shape has changed
2885 self.blink_manager.update(cx, BlinkManager::show_cursor);
2886
2887 cx.notify();
2888 }
2889
2890 pub fn set_current_line_highlight(
2891 &mut self,
2892 current_line_highlight: Option<CurrentLineHighlight>,
2893 ) {
2894 self.current_line_highlight = current_line_highlight;
2895 }
2896
2897 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2898 self.collapse_matches = collapse_matches;
2899 }
2900
2901 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2902 let buffers = self.buffer.read(cx).all_buffers();
2903 let Some(project) = self.project.as_ref() else {
2904 return;
2905 };
2906 project.update(cx, |project, cx| {
2907 for buffer in buffers {
2908 self.registered_buffers
2909 .entry(buffer.read(cx).remote_id())
2910 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2911 }
2912 })
2913 }
2914
2915 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2916 if self.collapse_matches {
2917 return range.start..range.start;
2918 }
2919 range.clone()
2920 }
2921
2922 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2923 if self.display_map.read(cx).clip_at_line_ends != clip {
2924 self.display_map
2925 .update(cx, |map, _| map.clip_at_line_ends = clip);
2926 }
2927 }
2928
2929 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2930 self.input_enabled = input_enabled;
2931 }
2932
2933 pub fn set_edit_predictions_hidden_for_vim_mode(
2934 &mut self,
2935 hidden: bool,
2936 window: &mut Window,
2937 cx: &mut Context<Self>,
2938 ) {
2939 if hidden != self.edit_predictions_hidden_for_vim_mode {
2940 self.edit_predictions_hidden_for_vim_mode = hidden;
2941 if hidden {
2942 self.update_visible_edit_prediction(window, cx);
2943 } else {
2944 self.refresh_edit_prediction(true, false, window, cx);
2945 }
2946 }
2947 }
2948
2949 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2950 self.menu_edit_predictions_policy = value;
2951 }
2952
2953 pub fn set_autoindent(&mut self, autoindent: bool) {
2954 if autoindent {
2955 self.autoindent_mode = Some(AutoindentMode::EachLine);
2956 } else {
2957 self.autoindent_mode = None;
2958 }
2959 }
2960
2961 pub fn read_only(&self, cx: &App) -> bool {
2962 self.read_only || self.buffer.read(cx).read_only()
2963 }
2964
2965 pub fn set_read_only(&mut self, read_only: bool) {
2966 self.read_only = read_only;
2967 }
2968
2969 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2970 self.use_autoclose = autoclose;
2971 }
2972
2973 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2974 self.use_auto_surround = auto_surround;
2975 }
2976
2977 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2978 self.auto_replace_emoji_shortcode = auto_replace;
2979 }
2980
2981 pub fn toggle_edit_predictions(
2982 &mut self,
2983 _: &ToggleEditPrediction,
2984 window: &mut Window,
2985 cx: &mut Context<Self>,
2986 ) {
2987 if self.show_edit_predictions_override.is_some() {
2988 self.set_show_edit_predictions(None, window, cx);
2989 } else {
2990 let show_edit_predictions = !self.edit_predictions_enabled();
2991 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2992 }
2993 }
2994
2995 pub fn set_show_edit_predictions(
2996 &mut self,
2997 show_edit_predictions: Option<bool>,
2998 window: &mut Window,
2999 cx: &mut Context<Self>,
3000 ) {
3001 self.show_edit_predictions_override = show_edit_predictions;
3002 self.update_edit_prediction_settings(cx);
3003
3004 if let Some(false) = show_edit_predictions {
3005 self.discard_edit_prediction(false, cx);
3006 } else {
3007 self.refresh_edit_prediction(false, true, window, cx);
3008 }
3009 }
3010
3011 fn edit_predictions_disabled_in_scope(
3012 &self,
3013 buffer: &Entity<Buffer>,
3014 buffer_position: language::Anchor,
3015 cx: &App,
3016 ) -> bool {
3017 let snapshot = buffer.read(cx).snapshot();
3018 let settings = snapshot.settings_at(buffer_position, cx);
3019
3020 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3021 return false;
3022 };
3023
3024 scope.override_name().is_some_and(|scope_name| {
3025 settings
3026 .edit_predictions_disabled_in
3027 .iter()
3028 .any(|s| s == scope_name)
3029 })
3030 }
3031
3032 pub fn set_use_modal_editing(&mut self, to: bool) {
3033 self.use_modal_editing = to;
3034 }
3035
3036 pub fn use_modal_editing(&self) -> bool {
3037 self.use_modal_editing
3038 }
3039
3040 fn selections_did_change(
3041 &mut self,
3042 local: bool,
3043 old_cursor_position: &Anchor,
3044 effects: SelectionEffects,
3045 window: &mut Window,
3046 cx: &mut Context<Self>,
3047 ) {
3048 window.invalidate_character_coordinates();
3049
3050 // Copy selections to primary selection buffer
3051 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3052 if local {
3053 let selections = self.selections.all::<usize>(cx);
3054 let buffer_handle = self.buffer.read(cx).read(cx);
3055
3056 let mut text = String::new();
3057 for (index, selection) in selections.iter().enumerate() {
3058 let text_for_selection = buffer_handle
3059 .text_for_range(selection.start..selection.end)
3060 .collect::<String>();
3061
3062 text.push_str(&text_for_selection);
3063 if index != selections.len() - 1 {
3064 text.push('\n');
3065 }
3066 }
3067
3068 if !text.is_empty() {
3069 cx.write_to_primary(ClipboardItem::new_string(text));
3070 }
3071 }
3072
3073 let selection_anchors = self.selections.disjoint_anchors_arc();
3074
3075 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3076 self.buffer.update(cx, |buffer, cx| {
3077 buffer.set_active_selections(
3078 &selection_anchors,
3079 self.selections.line_mode(),
3080 self.cursor_shape,
3081 cx,
3082 )
3083 });
3084 }
3085 let display_map = self
3086 .display_map
3087 .update(cx, |display_map, cx| display_map.snapshot(cx));
3088 let buffer = &display_map.buffer_snapshot;
3089 if self.selections.count() == 1 {
3090 self.add_selections_state = None;
3091 }
3092 self.select_next_state = None;
3093 self.select_prev_state = None;
3094 self.select_syntax_node_history.try_clear();
3095 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3096 self.snippet_stack.invalidate(&selection_anchors, buffer);
3097 self.take_rename(false, window, cx);
3098
3099 let newest_selection = self.selections.newest_anchor();
3100 let new_cursor_position = newest_selection.head();
3101 let selection_start = newest_selection.start;
3102
3103 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3104 self.push_to_nav_history(
3105 *old_cursor_position,
3106 Some(new_cursor_position.to_point(buffer)),
3107 false,
3108 effects.nav_history == Some(true),
3109 cx,
3110 );
3111 }
3112
3113 if local {
3114 if let Some(buffer_id) = new_cursor_position.buffer_id
3115 && !self.registered_buffers.contains_key(&buffer_id)
3116 && let Some(project) = self.project.as_ref()
3117 {
3118 project.update(cx, |project, cx| {
3119 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3120 return;
3121 };
3122 self.registered_buffers.insert(
3123 buffer_id,
3124 project.register_buffer_with_language_servers(&buffer, cx),
3125 );
3126 })
3127 }
3128
3129 let mut context_menu = self.context_menu.borrow_mut();
3130 let completion_menu = match context_menu.as_ref() {
3131 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3132 Some(CodeContextMenu::CodeActions(_)) => {
3133 *context_menu = None;
3134 None
3135 }
3136 None => None,
3137 };
3138 let completion_position = completion_menu.map(|menu| menu.initial_position);
3139 drop(context_menu);
3140
3141 if effects.completions
3142 && let Some(completion_position) = completion_position
3143 {
3144 let start_offset = selection_start.to_offset(buffer);
3145 let position_matches = start_offset == completion_position.to_offset(buffer);
3146 let continue_showing = if position_matches {
3147 if self.snippet_stack.is_empty() {
3148 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3149 == Some(CharKind::Word)
3150 } else {
3151 // Snippet choices can be shown even when the cursor is in whitespace.
3152 // Dismissing the menu with actions like backspace is handled by
3153 // invalidation regions.
3154 true
3155 }
3156 } else {
3157 false
3158 };
3159
3160 if continue_showing {
3161 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3162 } else {
3163 self.hide_context_menu(window, cx);
3164 }
3165 }
3166
3167 hide_hover(self, cx);
3168
3169 if old_cursor_position.to_display_point(&display_map).row()
3170 != new_cursor_position.to_display_point(&display_map).row()
3171 {
3172 self.available_code_actions.take();
3173 }
3174 self.refresh_code_actions(window, cx);
3175 self.refresh_document_highlights(cx);
3176 self.refresh_selected_text_highlights(false, window, cx);
3177 refresh_matching_bracket_highlights(self, window, cx);
3178 self.update_visible_edit_prediction(window, cx);
3179 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3180 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3181 self.inline_blame_popover.take();
3182 if self.git_blame_inline_enabled {
3183 self.start_inline_blame_timer(window, cx);
3184 }
3185 }
3186
3187 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3188 cx.emit(EditorEvent::SelectionsChanged { local });
3189
3190 let selections = &self.selections.disjoint_anchors_arc();
3191 if selections.len() == 1 {
3192 cx.emit(SearchEvent::ActiveMatchChanged)
3193 }
3194 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3195 let inmemory_selections = selections
3196 .iter()
3197 .map(|s| {
3198 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3199 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3200 })
3201 .collect();
3202 self.update_restoration_data(cx, |data| {
3203 data.selections = inmemory_selections;
3204 });
3205
3206 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3207 && let Some(workspace_id) =
3208 self.workspace.as_ref().and_then(|workspace| workspace.1)
3209 {
3210 let snapshot = self.buffer().read(cx).snapshot(cx);
3211 let selections = selections.clone();
3212 let background_executor = cx.background_executor().clone();
3213 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3214 self.serialize_selections = cx.background_spawn(async move {
3215 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3216 let db_selections = selections
3217 .iter()
3218 .map(|selection| {
3219 (
3220 selection.start.to_offset(&snapshot),
3221 selection.end.to_offset(&snapshot),
3222 )
3223 })
3224 .collect();
3225
3226 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3227 .await
3228 .with_context(|| {
3229 format!(
3230 "persisting editor selections for editor {editor_id}, \
3231 workspace {workspace_id:?}"
3232 )
3233 })
3234 .log_err();
3235 });
3236 }
3237 }
3238
3239 cx.notify();
3240 }
3241
3242 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3243 use text::ToOffset as _;
3244 use text::ToPoint as _;
3245
3246 if self.mode.is_minimap()
3247 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3248 {
3249 return;
3250 }
3251
3252 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3253 return;
3254 };
3255
3256 let snapshot = singleton.read(cx).snapshot();
3257 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3258 let display_snapshot = display_map.snapshot(cx);
3259
3260 display_snapshot
3261 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3262 .map(|fold| {
3263 fold.range.start.text_anchor.to_point(&snapshot)
3264 ..fold.range.end.text_anchor.to_point(&snapshot)
3265 })
3266 .collect()
3267 });
3268 self.update_restoration_data(cx, |data| {
3269 data.folds = inmemory_folds;
3270 });
3271
3272 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3273 return;
3274 };
3275 let background_executor = cx.background_executor().clone();
3276 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3277 let db_folds = self.display_map.update(cx, |display_map, cx| {
3278 display_map
3279 .snapshot(cx)
3280 .folds_in_range(0..snapshot.len())
3281 .map(|fold| {
3282 (
3283 fold.range.start.text_anchor.to_offset(&snapshot),
3284 fold.range.end.text_anchor.to_offset(&snapshot),
3285 )
3286 })
3287 .collect()
3288 });
3289 self.serialize_folds = cx.background_spawn(async move {
3290 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3291 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3292 .await
3293 .with_context(|| {
3294 format!(
3295 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3296 )
3297 })
3298 .log_err();
3299 });
3300 }
3301
3302 pub fn sync_selections(
3303 &mut self,
3304 other: Entity<Editor>,
3305 cx: &mut Context<Self>,
3306 ) -> gpui::Subscription {
3307 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3308 if !other_selections.is_empty() {
3309 self.selections.change_with(cx, |selections| {
3310 selections.select_anchors(other_selections);
3311 });
3312 }
3313
3314 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3315 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3316 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3317 if other_selections.is_empty() {
3318 return;
3319 }
3320 this.selections.change_with(cx, |selections| {
3321 selections.select_anchors(other_selections);
3322 });
3323 }
3324 });
3325
3326 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3327 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3328 let these_selections = this.selections.disjoint_anchors().to_vec();
3329 if these_selections.is_empty() {
3330 return;
3331 }
3332 other.update(cx, |other_editor, cx| {
3333 other_editor.selections.change_with(cx, |selections| {
3334 selections.select_anchors(these_selections);
3335 })
3336 });
3337 }
3338 });
3339
3340 Subscription::join(other_subscription, this_subscription)
3341 }
3342
3343 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3344 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3345 /// effects of selection change occur at the end of the transaction.
3346 pub fn change_selections<R>(
3347 &mut self,
3348 effects: SelectionEffects,
3349 window: &mut Window,
3350 cx: &mut Context<Self>,
3351 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3352 ) -> R {
3353 if let Some(state) = &mut self.deferred_selection_effects_state {
3354 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3355 state.effects.completions = effects.completions;
3356 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3357 let (changed, result) = self.selections.change_with(cx, change);
3358 state.changed |= changed;
3359 return result;
3360 }
3361 let mut state = DeferredSelectionEffectsState {
3362 changed: false,
3363 effects,
3364 old_cursor_position: self.selections.newest_anchor().head(),
3365 history_entry: SelectionHistoryEntry {
3366 selections: self.selections.disjoint_anchors_arc(),
3367 select_next_state: self.select_next_state.clone(),
3368 select_prev_state: self.select_prev_state.clone(),
3369 add_selections_state: self.add_selections_state.clone(),
3370 },
3371 };
3372 let (changed, result) = self.selections.change_with(cx, change);
3373 state.changed = state.changed || changed;
3374 if self.defer_selection_effects {
3375 self.deferred_selection_effects_state = Some(state);
3376 } else {
3377 self.apply_selection_effects(state, window, cx);
3378 }
3379 result
3380 }
3381
3382 /// Defers the effects of selection change, so that the effects of multiple calls to
3383 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3384 /// to selection history and the state of popovers based on selection position aren't
3385 /// erroneously updated.
3386 pub fn with_selection_effects_deferred<R>(
3387 &mut self,
3388 window: &mut Window,
3389 cx: &mut Context<Self>,
3390 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3391 ) -> R {
3392 let already_deferred = self.defer_selection_effects;
3393 self.defer_selection_effects = true;
3394 let result = update(self, window, cx);
3395 if !already_deferred {
3396 self.defer_selection_effects = false;
3397 if let Some(state) = self.deferred_selection_effects_state.take() {
3398 self.apply_selection_effects(state, window, cx);
3399 }
3400 }
3401 result
3402 }
3403
3404 fn apply_selection_effects(
3405 &mut self,
3406 state: DeferredSelectionEffectsState,
3407 window: &mut Window,
3408 cx: &mut Context<Self>,
3409 ) {
3410 if state.changed {
3411 self.selection_history.push(state.history_entry);
3412
3413 if let Some(autoscroll) = state.effects.scroll {
3414 self.request_autoscroll(autoscroll, cx);
3415 }
3416
3417 let old_cursor_position = &state.old_cursor_position;
3418
3419 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3420
3421 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3422 self.show_signature_help(&ShowSignatureHelp, window, cx);
3423 }
3424 }
3425 }
3426
3427 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3428 where
3429 I: IntoIterator<Item = (Range<S>, T)>,
3430 S: ToOffset,
3431 T: Into<Arc<str>>,
3432 {
3433 if self.read_only(cx) {
3434 return;
3435 }
3436
3437 self.buffer
3438 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3439 }
3440
3441 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3442 where
3443 I: IntoIterator<Item = (Range<S>, T)>,
3444 S: ToOffset,
3445 T: Into<Arc<str>>,
3446 {
3447 if self.read_only(cx) {
3448 return;
3449 }
3450
3451 self.buffer.update(cx, |buffer, cx| {
3452 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3453 });
3454 }
3455
3456 pub fn edit_with_block_indent<I, S, T>(
3457 &mut self,
3458 edits: I,
3459 original_indent_columns: Vec<Option<u32>>,
3460 cx: &mut Context<Self>,
3461 ) where
3462 I: IntoIterator<Item = (Range<S>, T)>,
3463 S: ToOffset,
3464 T: Into<Arc<str>>,
3465 {
3466 if self.read_only(cx) {
3467 return;
3468 }
3469
3470 self.buffer.update(cx, |buffer, cx| {
3471 buffer.edit(
3472 edits,
3473 Some(AutoindentMode::Block {
3474 original_indent_columns,
3475 }),
3476 cx,
3477 )
3478 });
3479 }
3480
3481 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3482 self.hide_context_menu(window, cx);
3483
3484 match phase {
3485 SelectPhase::Begin {
3486 position,
3487 add,
3488 click_count,
3489 } => self.begin_selection(position, add, click_count, window, cx),
3490 SelectPhase::BeginColumnar {
3491 position,
3492 goal_column,
3493 reset,
3494 mode,
3495 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3496 SelectPhase::Extend {
3497 position,
3498 click_count,
3499 } => self.extend_selection(position, click_count, window, cx),
3500 SelectPhase::Update {
3501 position,
3502 goal_column,
3503 scroll_delta,
3504 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3505 SelectPhase::End => self.end_selection(window, cx),
3506 }
3507 }
3508
3509 fn extend_selection(
3510 &mut self,
3511 position: DisplayPoint,
3512 click_count: usize,
3513 window: &mut Window,
3514 cx: &mut Context<Self>,
3515 ) {
3516 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3517 let tail = self.selections.newest::<usize>(cx).tail();
3518 self.begin_selection(position, false, click_count, window, cx);
3519
3520 let position = position.to_offset(&display_map, Bias::Left);
3521 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3522
3523 let mut pending_selection = self
3524 .selections
3525 .pending_anchor()
3526 .cloned()
3527 .expect("extend_selection not called with pending selection");
3528 if position >= tail {
3529 pending_selection.start = tail_anchor;
3530 } else {
3531 pending_selection.end = tail_anchor;
3532 pending_selection.reversed = true;
3533 }
3534
3535 let mut pending_mode = self.selections.pending_mode().unwrap();
3536 match &mut pending_mode {
3537 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3538 _ => {}
3539 }
3540
3541 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3542 SelectionEffects::scroll(Autoscroll::fit())
3543 } else {
3544 SelectionEffects::no_scroll()
3545 };
3546
3547 self.change_selections(effects, window, cx, |s| {
3548 s.set_pending(pending_selection.clone(), pending_mode)
3549 });
3550 }
3551
3552 fn begin_selection(
3553 &mut self,
3554 position: DisplayPoint,
3555 add: bool,
3556 click_count: usize,
3557 window: &mut Window,
3558 cx: &mut Context<Self>,
3559 ) {
3560 if !self.focus_handle.is_focused(window) {
3561 self.last_focused_descendant = None;
3562 window.focus(&self.focus_handle);
3563 }
3564
3565 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3566 let buffer = &display_map.buffer_snapshot;
3567 let position = display_map.clip_point(position, Bias::Left);
3568
3569 let start;
3570 let end;
3571 let mode;
3572 let mut auto_scroll;
3573 match click_count {
3574 1 => {
3575 start = buffer.anchor_before(position.to_point(&display_map));
3576 end = start;
3577 mode = SelectMode::Character;
3578 auto_scroll = true;
3579 }
3580 2 => {
3581 let position = display_map
3582 .clip_point(position, Bias::Left)
3583 .to_offset(&display_map, Bias::Left);
3584 let (range, _) = buffer.surrounding_word(position, None);
3585 start = buffer.anchor_before(range.start);
3586 end = buffer.anchor_before(range.end);
3587 mode = SelectMode::Word(start..end);
3588 auto_scroll = true;
3589 }
3590 3 => {
3591 let position = display_map
3592 .clip_point(position, Bias::Left)
3593 .to_point(&display_map);
3594 let line_start = display_map.prev_line_boundary(position).0;
3595 let next_line_start = buffer.clip_point(
3596 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3597 Bias::Left,
3598 );
3599 start = buffer.anchor_before(line_start);
3600 end = buffer.anchor_before(next_line_start);
3601 mode = SelectMode::Line(start..end);
3602 auto_scroll = true;
3603 }
3604 _ => {
3605 start = buffer.anchor_before(0);
3606 end = buffer.anchor_before(buffer.len());
3607 mode = SelectMode::All;
3608 auto_scroll = false;
3609 }
3610 }
3611 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3612
3613 let point_to_delete: Option<usize> = {
3614 let selected_points: Vec<Selection<Point>> =
3615 self.selections.disjoint_in_range(start..end, cx);
3616
3617 if !add || click_count > 1 {
3618 None
3619 } else if !selected_points.is_empty() {
3620 Some(selected_points[0].id)
3621 } else {
3622 let clicked_point_already_selected =
3623 self.selections.disjoint_anchors().iter().find(|selection| {
3624 selection.start.to_point(buffer) == start.to_point(buffer)
3625 || selection.end.to_point(buffer) == end.to_point(buffer)
3626 });
3627
3628 clicked_point_already_selected.map(|selection| selection.id)
3629 }
3630 };
3631
3632 let selections_count = self.selections.count();
3633 let effects = if auto_scroll {
3634 SelectionEffects::default()
3635 } else {
3636 SelectionEffects::no_scroll()
3637 };
3638
3639 self.change_selections(effects, window, cx, |s| {
3640 if let Some(point_to_delete) = point_to_delete {
3641 s.delete(point_to_delete);
3642
3643 if selections_count == 1 {
3644 s.set_pending_anchor_range(start..end, mode);
3645 }
3646 } else {
3647 if !add {
3648 s.clear_disjoint();
3649 }
3650
3651 s.set_pending_anchor_range(start..end, mode);
3652 }
3653 });
3654 }
3655
3656 fn begin_columnar_selection(
3657 &mut self,
3658 position: DisplayPoint,
3659 goal_column: u32,
3660 reset: bool,
3661 mode: ColumnarMode,
3662 window: &mut Window,
3663 cx: &mut Context<Self>,
3664 ) {
3665 if !self.focus_handle.is_focused(window) {
3666 self.last_focused_descendant = None;
3667 window.focus(&self.focus_handle);
3668 }
3669
3670 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3671
3672 if reset {
3673 let pointer_position = display_map
3674 .buffer_snapshot
3675 .anchor_before(position.to_point(&display_map));
3676
3677 self.change_selections(
3678 SelectionEffects::scroll(Autoscroll::newest()),
3679 window,
3680 cx,
3681 |s| {
3682 s.clear_disjoint();
3683 s.set_pending_anchor_range(
3684 pointer_position..pointer_position,
3685 SelectMode::Character,
3686 );
3687 },
3688 );
3689 };
3690
3691 let tail = self.selections.newest::<Point>(cx).tail();
3692 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3693 self.columnar_selection_state = match mode {
3694 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3695 selection_tail: selection_anchor,
3696 display_point: if reset {
3697 if position.column() != goal_column {
3698 Some(DisplayPoint::new(position.row(), goal_column))
3699 } else {
3700 None
3701 }
3702 } else {
3703 None
3704 },
3705 }),
3706 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3707 selection_tail: selection_anchor,
3708 }),
3709 };
3710
3711 if !reset {
3712 self.select_columns(position, goal_column, &display_map, window, cx);
3713 }
3714 }
3715
3716 fn update_selection(
3717 &mut self,
3718 position: DisplayPoint,
3719 goal_column: u32,
3720 scroll_delta: gpui::Point<f32>,
3721 window: &mut Window,
3722 cx: &mut Context<Self>,
3723 ) {
3724 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3725
3726 if self.columnar_selection_state.is_some() {
3727 self.select_columns(position, goal_column, &display_map, window, cx);
3728 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3729 let buffer = &display_map.buffer_snapshot;
3730 let head;
3731 let tail;
3732 let mode = self.selections.pending_mode().unwrap();
3733 match &mode {
3734 SelectMode::Character => {
3735 head = position.to_point(&display_map);
3736 tail = pending.tail().to_point(buffer);
3737 }
3738 SelectMode::Word(original_range) => {
3739 let offset = display_map
3740 .clip_point(position, Bias::Left)
3741 .to_offset(&display_map, Bias::Left);
3742 let original_range = original_range.to_offset(buffer);
3743
3744 let head_offset = if buffer.is_inside_word(offset, None)
3745 || original_range.contains(&offset)
3746 {
3747 let (word_range, _) = buffer.surrounding_word(offset, None);
3748 if word_range.start < original_range.start {
3749 word_range.start
3750 } else {
3751 word_range.end
3752 }
3753 } else {
3754 offset
3755 };
3756
3757 head = head_offset.to_point(buffer);
3758 if head_offset <= original_range.start {
3759 tail = original_range.end.to_point(buffer);
3760 } else {
3761 tail = original_range.start.to_point(buffer);
3762 }
3763 }
3764 SelectMode::Line(original_range) => {
3765 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3766
3767 let position = display_map
3768 .clip_point(position, Bias::Left)
3769 .to_point(&display_map);
3770 let line_start = display_map.prev_line_boundary(position).0;
3771 let next_line_start = buffer.clip_point(
3772 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3773 Bias::Left,
3774 );
3775
3776 if line_start < original_range.start {
3777 head = line_start
3778 } else {
3779 head = next_line_start
3780 }
3781
3782 if head <= original_range.start {
3783 tail = original_range.end;
3784 } else {
3785 tail = original_range.start;
3786 }
3787 }
3788 SelectMode::All => {
3789 return;
3790 }
3791 };
3792
3793 if head < tail {
3794 pending.start = buffer.anchor_before(head);
3795 pending.end = buffer.anchor_before(tail);
3796 pending.reversed = true;
3797 } else {
3798 pending.start = buffer.anchor_before(tail);
3799 pending.end = buffer.anchor_before(head);
3800 pending.reversed = false;
3801 }
3802
3803 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3804 s.set_pending(pending.clone(), mode);
3805 });
3806 } else {
3807 log::error!("update_selection dispatched with no pending selection");
3808 return;
3809 }
3810
3811 self.apply_scroll_delta(scroll_delta, window, cx);
3812 cx.notify();
3813 }
3814
3815 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3816 self.columnar_selection_state.take();
3817 if self.selections.pending_anchor().is_some() {
3818 let selections = self.selections.all::<usize>(cx);
3819 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3820 s.select(selections);
3821 s.clear_pending();
3822 });
3823 }
3824 }
3825
3826 fn select_columns(
3827 &mut self,
3828 head: DisplayPoint,
3829 goal_column: u32,
3830 display_map: &DisplaySnapshot,
3831 window: &mut Window,
3832 cx: &mut Context<Self>,
3833 ) {
3834 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3835 return;
3836 };
3837
3838 let tail = match columnar_state {
3839 ColumnarSelectionState::FromMouse {
3840 selection_tail,
3841 display_point,
3842 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3843 ColumnarSelectionState::FromSelection { selection_tail } => {
3844 selection_tail.to_display_point(display_map)
3845 }
3846 };
3847
3848 let start_row = cmp::min(tail.row(), head.row());
3849 let end_row = cmp::max(tail.row(), head.row());
3850 let start_column = cmp::min(tail.column(), goal_column);
3851 let end_column = cmp::max(tail.column(), goal_column);
3852 let reversed = start_column < tail.column();
3853
3854 let selection_ranges = (start_row.0..=end_row.0)
3855 .map(DisplayRow)
3856 .filter_map(|row| {
3857 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3858 || start_column <= display_map.line_len(row))
3859 && !display_map.is_block_line(row)
3860 {
3861 let start = display_map
3862 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3863 .to_point(display_map);
3864 let end = display_map
3865 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3866 .to_point(display_map);
3867 if reversed {
3868 Some(end..start)
3869 } else {
3870 Some(start..end)
3871 }
3872 } else {
3873 None
3874 }
3875 })
3876 .collect::<Vec<_>>();
3877
3878 let ranges = match columnar_state {
3879 ColumnarSelectionState::FromMouse { .. } => {
3880 let mut non_empty_ranges = selection_ranges
3881 .iter()
3882 .filter(|selection_range| selection_range.start != selection_range.end)
3883 .peekable();
3884 if non_empty_ranges.peek().is_some() {
3885 non_empty_ranges.cloned().collect()
3886 } else {
3887 selection_ranges
3888 }
3889 }
3890 _ => selection_ranges,
3891 };
3892
3893 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3894 s.select_ranges(ranges);
3895 });
3896 cx.notify();
3897 }
3898
3899 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3900 self.selections
3901 .all_adjusted(cx)
3902 .iter()
3903 .any(|selection| !selection.is_empty())
3904 }
3905
3906 pub fn has_pending_nonempty_selection(&self) -> bool {
3907 let pending_nonempty_selection = match self.selections.pending_anchor() {
3908 Some(Selection { start, end, .. }) => start != end,
3909 None => false,
3910 };
3911
3912 pending_nonempty_selection
3913 || (self.columnar_selection_state.is_some()
3914 && self.selections.disjoint_anchors().len() > 1)
3915 }
3916
3917 pub fn has_pending_selection(&self) -> bool {
3918 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3919 }
3920
3921 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3922 self.selection_mark_mode = false;
3923 self.selection_drag_state = SelectionDragState::None;
3924
3925 if self.clear_expanded_diff_hunks(cx) {
3926 cx.notify();
3927 return;
3928 }
3929 if self.dismiss_menus_and_popups(true, window, cx) {
3930 return;
3931 }
3932
3933 if self.mode.is_full()
3934 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3935 {
3936 return;
3937 }
3938
3939 cx.propagate();
3940 }
3941
3942 pub fn dismiss_menus_and_popups(
3943 &mut self,
3944 is_user_requested: bool,
3945 window: &mut Window,
3946 cx: &mut Context<Self>,
3947 ) -> bool {
3948 if self.take_rename(false, window, cx).is_some() {
3949 return true;
3950 }
3951
3952 if hide_hover(self, cx) {
3953 return true;
3954 }
3955
3956 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3957 return true;
3958 }
3959
3960 if self.hide_context_menu(window, cx).is_some() {
3961 return true;
3962 }
3963
3964 if self.mouse_context_menu.take().is_some() {
3965 return true;
3966 }
3967
3968 if is_user_requested && self.discard_edit_prediction(true, cx) {
3969 return true;
3970 }
3971
3972 if self.snippet_stack.pop().is_some() {
3973 return true;
3974 }
3975
3976 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3977 self.dismiss_diagnostics(cx);
3978 return true;
3979 }
3980
3981 false
3982 }
3983
3984 fn linked_editing_ranges_for(
3985 &self,
3986 selection: Range<text::Anchor>,
3987 cx: &App,
3988 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3989 if self.linked_edit_ranges.is_empty() {
3990 return None;
3991 }
3992 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3993 selection.end.buffer_id.and_then(|end_buffer_id| {
3994 if selection.start.buffer_id != Some(end_buffer_id) {
3995 return None;
3996 }
3997 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3998 let snapshot = buffer.read(cx).snapshot();
3999 self.linked_edit_ranges
4000 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4001 .map(|ranges| (ranges, snapshot, buffer))
4002 })?;
4003 use text::ToOffset as TO;
4004 // find offset from the start of current range to current cursor position
4005 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4006
4007 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4008 let start_difference = start_offset - start_byte_offset;
4009 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4010 let end_difference = end_offset - start_byte_offset;
4011 // Current range has associated linked ranges.
4012 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4013 for range in linked_ranges.iter() {
4014 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4015 let end_offset = start_offset + end_difference;
4016 let start_offset = start_offset + start_difference;
4017 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4018 continue;
4019 }
4020 if self.selections.disjoint_anchor_ranges().any(|s| {
4021 if s.start.buffer_id != selection.start.buffer_id
4022 || s.end.buffer_id != selection.end.buffer_id
4023 {
4024 return false;
4025 }
4026 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4027 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4028 }) {
4029 continue;
4030 }
4031 let start = buffer_snapshot.anchor_after(start_offset);
4032 let end = buffer_snapshot.anchor_after(end_offset);
4033 linked_edits
4034 .entry(buffer.clone())
4035 .or_default()
4036 .push(start..end);
4037 }
4038 Some(linked_edits)
4039 }
4040
4041 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4042 let text: Arc<str> = text.into();
4043
4044 if self.read_only(cx) {
4045 return;
4046 }
4047
4048 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4049
4050 let selections = self.selections.all_adjusted(cx);
4051 let mut bracket_inserted = false;
4052 let mut edits = Vec::new();
4053 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4054 let mut new_selections = Vec::with_capacity(selections.len());
4055 let mut new_autoclose_regions = Vec::new();
4056 let snapshot = self.buffer.read(cx).read(cx);
4057 let mut clear_linked_edit_ranges = false;
4058
4059 for (selection, autoclose_region) in
4060 self.selections_with_autoclose_regions(selections, &snapshot)
4061 {
4062 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4063 // Determine if the inserted text matches the opening or closing
4064 // bracket of any of this language's bracket pairs.
4065 let mut bracket_pair = None;
4066 let mut is_bracket_pair_start = false;
4067 let mut is_bracket_pair_end = false;
4068 if !text.is_empty() {
4069 let mut bracket_pair_matching_end = None;
4070 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4071 // and they are removing the character that triggered IME popup.
4072 for (pair, enabled) in scope.brackets() {
4073 if !pair.close && !pair.surround {
4074 continue;
4075 }
4076
4077 if enabled && pair.start.ends_with(text.as_ref()) {
4078 let prefix_len = pair.start.len() - text.len();
4079 let preceding_text_matches_prefix = prefix_len == 0
4080 || (selection.start.column >= (prefix_len as u32)
4081 && snapshot.contains_str_at(
4082 Point::new(
4083 selection.start.row,
4084 selection.start.column - (prefix_len as u32),
4085 ),
4086 &pair.start[..prefix_len],
4087 ));
4088 if preceding_text_matches_prefix {
4089 bracket_pair = Some(pair.clone());
4090 is_bracket_pair_start = true;
4091 break;
4092 }
4093 }
4094 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4095 {
4096 // take first bracket pair matching end, but don't break in case a later bracket
4097 // pair matches start
4098 bracket_pair_matching_end = Some(pair.clone());
4099 }
4100 }
4101 if let Some(end) = bracket_pair_matching_end
4102 && bracket_pair.is_none()
4103 {
4104 bracket_pair = Some(end);
4105 is_bracket_pair_end = true;
4106 }
4107 }
4108
4109 if let Some(bracket_pair) = bracket_pair {
4110 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4111 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4112 let auto_surround =
4113 self.use_auto_surround && snapshot_settings.use_auto_surround;
4114 if selection.is_empty() {
4115 if is_bracket_pair_start {
4116 // If the inserted text is a suffix of an opening bracket and the
4117 // selection is preceded by the rest of the opening bracket, then
4118 // insert the closing bracket.
4119 let following_text_allows_autoclose = snapshot
4120 .chars_at(selection.start)
4121 .next()
4122 .is_none_or(|c| scope.should_autoclose_before(c));
4123
4124 let preceding_text_allows_autoclose = selection.start.column == 0
4125 || snapshot
4126 .reversed_chars_at(selection.start)
4127 .next()
4128 .is_none_or(|c| {
4129 bracket_pair.start != bracket_pair.end
4130 || !snapshot
4131 .char_classifier_at(selection.start)
4132 .is_word(c)
4133 });
4134
4135 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4136 && bracket_pair.start.len() == 1
4137 {
4138 let target = bracket_pair.start.chars().next().unwrap();
4139 let current_line_count = snapshot
4140 .reversed_chars_at(selection.start)
4141 .take_while(|&c| c != '\n')
4142 .filter(|&c| c == target)
4143 .count();
4144 current_line_count % 2 == 1
4145 } else {
4146 false
4147 };
4148
4149 if autoclose
4150 && bracket_pair.close
4151 && following_text_allows_autoclose
4152 && preceding_text_allows_autoclose
4153 && !is_closing_quote
4154 {
4155 let anchor = snapshot.anchor_before(selection.end);
4156 new_selections.push((selection.map(|_| anchor), text.len()));
4157 new_autoclose_regions.push((
4158 anchor,
4159 text.len(),
4160 selection.id,
4161 bracket_pair.clone(),
4162 ));
4163 edits.push((
4164 selection.range(),
4165 format!("{}{}", text, bracket_pair.end).into(),
4166 ));
4167 bracket_inserted = true;
4168 continue;
4169 }
4170 }
4171
4172 if let Some(region) = autoclose_region {
4173 // If the selection is followed by an auto-inserted closing bracket,
4174 // then don't insert that closing bracket again; just move the selection
4175 // past the closing bracket.
4176 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4177 && text.as_ref() == region.pair.end.as_str()
4178 && snapshot.contains_str_at(region.range.end, text.as_ref());
4179 if should_skip {
4180 let anchor = snapshot.anchor_after(selection.end);
4181 new_selections
4182 .push((selection.map(|_| anchor), region.pair.end.len()));
4183 continue;
4184 }
4185 }
4186
4187 let always_treat_brackets_as_autoclosed = snapshot
4188 .language_settings_at(selection.start, cx)
4189 .always_treat_brackets_as_autoclosed;
4190 if always_treat_brackets_as_autoclosed
4191 && is_bracket_pair_end
4192 && snapshot.contains_str_at(selection.end, text.as_ref())
4193 {
4194 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4195 // and the inserted text is a closing bracket and the selection is followed
4196 // by the closing bracket then move the selection past the closing bracket.
4197 let anchor = snapshot.anchor_after(selection.end);
4198 new_selections.push((selection.map(|_| anchor), text.len()));
4199 continue;
4200 }
4201 }
4202 // If an opening bracket is 1 character long and is typed while
4203 // text is selected, then surround that text with the bracket pair.
4204 else if auto_surround
4205 && bracket_pair.surround
4206 && is_bracket_pair_start
4207 && bracket_pair.start.chars().count() == 1
4208 {
4209 edits.push((selection.start..selection.start, text.clone()));
4210 edits.push((
4211 selection.end..selection.end,
4212 bracket_pair.end.as_str().into(),
4213 ));
4214 bracket_inserted = true;
4215 new_selections.push((
4216 Selection {
4217 id: selection.id,
4218 start: snapshot.anchor_after(selection.start),
4219 end: snapshot.anchor_before(selection.end),
4220 reversed: selection.reversed,
4221 goal: selection.goal,
4222 },
4223 0,
4224 ));
4225 continue;
4226 }
4227 }
4228 }
4229
4230 if self.auto_replace_emoji_shortcode
4231 && selection.is_empty()
4232 && text.as_ref().ends_with(':')
4233 && let Some(possible_emoji_short_code) =
4234 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4235 && !possible_emoji_short_code.is_empty()
4236 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4237 {
4238 let emoji_shortcode_start = Point::new(
4239 selection.start.row,
4240 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4241 );
4242
4243 // Remove shortcode from buffer
4244 edits.push((
4245 emoji_shortcode_start..selection.start,
4246 "".to_string().into(),
4247 ));
4248 new_selections.push((
4249 Selection {
4250 id: selection.id,
4251 start: snapshot.anchor_after(emoji_shortcode_start),
4252 end: snapshot.anchor_before(selection.start),
4253 reversed: selection.reversed,
4254 goal: selection.goal,
4255 },
4256 0,
4257 ));
4258
4259 // Insert emoji
4260 let selection_start_anchor = snapshot.anchor_after(selection.start);
4261 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4262 edits.push((selection.start..selection.end, emoji.to_string().into()));
4263
4264 continue;
4265 }
4266
4267 // If not handling any auto-close operation, then just replace the selected
4268 // text with the given input and move the selection to the end of the
4269 // newly inserted text.
4270 let anchor = snapshot.anchor_after(selection.end);
4271 if !self.linked_edit_ranges.is_empty() {
4272 let start_anchor = snapshot.anchor_before(selection.start);
4273
4274 let is_word_char = text.chars().next().is_none_or(|char| {
4275 let classifier = snapshot
4276 .char_classifier_at(start_anchor.to_offset(&snapshot))
4277 .scope_context(Some(CharScopeContext::LinkedEdit));
4278 classifier.is_word(char)
4279 });
4280
4281 if is_word_char {
4282 if let Some(ranges) = self
4283 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4284 {
4285 for (buffer, edits) in ranges {
4286 linked_edits
4287 .entry(buffer.clone())
4288 .or_default()
4289 .extend(edits.into_iter().map(|range| (range, text.clone())));
4290 }
4291 }
4292 } else {
4293 clear_linked_edit_ranges = true;
4294 }
4295 }
4296
4297 new_selections.push((selection.map(|_| anchor), 0));
4298 edits.push((selection.start..selection.end, text.clone()));
4299 }
4300
4301 drop(snapshot);
4302
4303 self.transact(window, cx, |this, window, cx| {
4304 if clear_linked_edit_ranges {
4305 this.linked_edit_ranges.clear();
4306 }
4307 let initial_buffer_versions =
4308 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4309
4310 this.buffer.update(cx, |buffer, cx| {
4311 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4312 });
4313 for (buffer, edits) in linked_edits {
4314 buffer.update(cx, |buffer, cx| {
4315 let snapshot = buffer.snapshot();
4316 let edits = edits
4317 .into_iter()
4318 .map(|(range, text)| {
4319 use text::ToPoint as TP;
4320 let end_point = TP::to_point(&range.end, &snapshot);
4321 let start_point = TP::to_point(&range.start, &snapshot);
4322 (start_point..end_point, text)
4323 })
4324 .sorted_by_key(|(range, _)| range.start);
4325 buffer.edit(edits, None, cx);
4326 })
4327 }
4328 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4329 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4330 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4331 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4332 .zip(new_selection_deltas)
4333 .map(|(selection, delta)| Selection {
4334 id: selection.id,
4335 start: selection.start + delta,
4336 end: selection.end + delta,
4337 reversed: selection.reversed,
4338 goal: SelectionGoal::None,
4339 })
4340 .collect::<Vec<_>>();
4341
4342 let mut i = 0;
4343 for (position, delta, selection_id, pair) in new_autoclose_regions {
4344 let position = position.to_offset(&map.buffer_snapshot) + delta;
4345 let start = map.buffer_snapshot.anchor_before(position);
4346 let end = map.buffer_snapshot.anchor_after(position);
4347 while let Some(existing_state) = this.autoclose_regions.get(i) {
4348 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4349 Ordering::Less => i += 1,
4350 Ordering::Greater => break,
4351 Ordering::Equal => {
4352 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4353 Ordering::Less => i += 1,
4354 Ordering::Equal => break,
4355 Ordering::Greater => break,
4356 }
4357 }
4358 }
4359 }
4360 this.autoclose_regions.insert(
4361 i,
4362 AutocloseRegion {
4363 selection_id,
4364 range: start..end,
4365 pair,
4366 },
4367 );
4368 }
4369
4370 let had_active_edit_prediction = this.has_active_edit_prediction();
4371 this.change_selections(
4372 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4373 window,
4374 cx,
4375 |s| s.select(new_selections),
4376 );
4377
4378 if !bracket_inserted
4379 && let Some(on_type_format_task) =
4380 this.trigger_on_type_formatting(text.to_string(), window, cx)
4381 {
4382 on_type_format_task.detach_and_log_err(cx);
4383 }
4384
4385 let editor_settings = EditorSettings::get_global(cx);
4386 if bracket_inserted
4387 && (editor_settings.auto_signature_help
4388 || editor_settings.show_signature_help_after_edits)
4389 {
4390 this.show_signature_help(&ShowSignatureHelp, window, cx);
4391 }
4392
4393 let trigger_in_words =
4394 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4395 if this.hard_wrap.is_some() {
4396 let latest: Range<Point> = this.selections.newest(cx).range();
4397 if latest.is_empty()
4398 && this
4399 .buffer()
4400 .read(cx)
4401 .snapshot(cx)
4402 .line_len(MultiBufferRow(latest.start.row))
4403 == latest.start.column
4404 {
4405 this.rewrap_impl(
4406 RewrapOptions {
4407 override_language_settings: true,
4408 preserve_existing_whitespace: true,
4409 },
4410 cx,
4411 )
4412 }
4413 }
4414 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4415 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4416 this.refresh_edit_prediction(true, false, window, cx);
4417 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4418 });
4419 }
4420
4421 fn find_possible_emoji_shortcode_at_position(
4422 snapshot: &MultiBufferSnapshot,
4423 position: Point,
4424 ) -> Option<String> {
4425 let mut chars = Vec::new();
4426 let mut found_colon = false;
4427 for char in snapshot.reversed_chars_at(position).take(100) {
4428 // Found a possible emoji shortcode in the middle of the buffer
4429 if found_colon {
4430 if char.is_whitespace() {
4431 chars.reverse();
4432 return Some(chars.iter().collect());
4433 }
4434 // If the previous character is not a whitespace, we are in the middle of a word
4435 // and we only want to complete the shortcode if the word is made up of other emojis
4436 let mut containing_word = String::new();
4437 for ch in snapshot
4438 .reversed_chars_at(position)
4439 .skip(chars.len() + 1)
4440 .take(100)
4441 {
4442 if ch.is_whitespace() {
4443 break;
4444 }
4445 containing_word.push(ch);
4446 }
4447 let containing_word = containing_word.chars().rev().collect::<String>();
4448 if util::word_consists_of_emojis(containing_word.as_str()) {
4449 chars.reverse();
4450 return Some(chars.iter().collect());
4451 }
4452 }
4453
4454 if char.is_whitespace() || !char.is_ascii() {
4455 return None;
4456 }
4457 if char == ':' {
4458 found_colon = true;
4459 } else {
4460 chars.push(char);
4461 }
4462 }
4463 // Found a possible emoji shortcode at the beginning of the buffer
4464 chars.reverse();
4465 Some(chars.iter().collect())
4466 }
4467
4468 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4469 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4470 self.transact(window, cx, |this, window, cx| {
4471 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4472 let selections = this.selections.all::<usize>(cx);
4473 let multi_buffer = this.buffer.read(cx);
4474 let buffer = multi_buffer.snapshot(cx);
4475 selections
4476 .iter()
4477 .map(|selection| {
4478 let start_point = selection.start.to_point(&buffer);
4479 let mut existing_indent =
4480 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4481 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4482 let start = selection.start;
4483 let end = selection.end;
4484 let selection_is_empty = start == end;
4485 let language_scope = buffer.language_scope_at(start);
4486 let (
4487 comment_delimiter,
4488 doc_delimiter,
4489 insert_extra_newline,
4490 indent_on_newline,
4491 indent_on_extra_newline,
4492 ) = if let Some(language) = &language_scope {
4493 let mut insert_extra_newline =
4494 insert_extra_newline_brackets(&buffer, start..end, language)
4495 || insert_extra_newline_tree_sitter(&buffer, start..end);
4496
4497 // Comment extension on newline is allowed only for cursor selections
4498 let comment_delimiter = maybe!({
4499 if !selection_is_empty {
4500 return None;
4501 }
4502
4503 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4504 return None;
4505 }
4506
4507 let delimiters = language.line_comment_prefixes();
4508 let max_len_of_delimiter =
4509 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4510 let (snapshot, range) =
4511 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4512
4513 let num_of_whitespaces = snapshot
4514 .chars_for_range(range.clone())
4515 .take_while(|c| c.is_whitespace())
4516 .count();
4517 let comment_candidate = snapshot
4518 .chars_for_range(range.clone())
4519 .skip(num_of_whitespaces)
4520 .take(max_len_of_delimiter)
4521 .collect::<String>();
4522 let (delimiter, trimmed_len) = delimiters
4523 .iter()
4524 .filter_map(|delimiter| {
4525 let prefix = delimiter.trim_end();
4526 if comment_candidate.starts_with(prefix) {
4527 Some((delimiter, prefix.len()))
4528 } else {
4529 None
4530 }
4531 })
4532 .max_by_key(|(_, len)| *len)?;
4533
4534 if let Some(BlockCommentConfig {
4535 start: block_start, ..
4536 }) = language.block_comment()
4537 {
4538 let block_start_trimmed = block_start.trim_end();
4539 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4540 let line_content = snapshot
4541 .chars_for_range(range)
4542 .skip(num_of_whitespaces)
4543 .take(block_start_trimmed.len())
4544 .collect::<String>();
4545
4546 if line_content.starts_with(block_start_trimmed) {
4547 return None;
4548 }
4549 }
4550 }
4551
4552 let cursor_is_placed_after_comment_marker =
4553 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4554 if cursor_is_placed_after_comment_marker {
4555 Some(delimiter.clone())
4556 } else {
4557 None
4558 }
4559 });
4560
4561 let mut indent_on_newline = IndentSize::spaces(0);
4562 let mut indent_on_extra_newline = IndentSize::spaces(0);
4563
4564 let doc_delimiter = maybe!({
4565 if !selection_is_empty {
4566 return None;
4567 }
4568
4569 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4570 return None;
4571 }
4572
4573 let BlockCommentConfig {
4574 start: start_tag,
4575 end: end_tag,
4576 prefix: delimiter,
4577 tab_size: len,
4578 } = language.documentation_comment()?;
4579 let is_within_block_comment = buffer
4580 .language_scope_at(start_point)
4581 .is_some_and(|scope| scope.override_name() == Some("comment"));
4582 if !is_within_block_comment {
4583 return None;
4584 }
4585
4586 let (snapshot, range) =
4587 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4588
4589 let num_of_whitespaces = snapshot
4590 .chars_for_range(range.clone())
4591 .take_while(|c| c.is_whitespace())
4592 .count();
4593
4594 // 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.
4595 let column = start_point.column;
4596 let cursor_is_after_start_tag = {
4597 let start_tag_len = start_tag.len();
4598 let start_tag_line = snapshot
4599 .chars_for_range(range.clone())
4600 .skip(num_of_whitespaces)
4601 .take(start_tag_len)
4602 .collect::<String>();
4603 if start_tag_line.starts_with(start_tag.as_ref()) {
4604 num_of_whitespaces + start_tag_len <= column as usize
4605 } else {
4606 false
4607 }
4608 };
4609
4610 let cursor_is_after_delimiter = {
4611 let delimiter_trim = delimiter.trim_end();
4612 let delimiter_line = snapshot
4613 .chars_for_range(range.clone())
4614 .skip(num_of_whitespaces)
4615 .take(delimiter_trim.len())
4616 .collect::<String>();
4617 if delimiter_line.starts_with(delimiter_trim) {
4618 num_of_whitespaces + delimiter_trim.len() <= column as usize
4619 } else {
4620 false
4621 }
4622 };
4623
4624 let cursor_is_before_end_tag_if_exists = {
4625 let mut char_position = 0u32;
4626 let mut end_tag_offset = None;
4627
4628 'outer: for chunk in snapshot.text_for_range(range) {
4629 if let Some(byte_pos) = chunk.find(&**end_tag) {
4630 let chars_before_match =
4631 chunk[..byte_pos].chars().count() as u32;
4632 end_tag_offset =
4633 Some(char_position + chars_before_match);
4634 break 'outer;
4635 }
4636 char_position += chunk.chars().count() as u32;
4637 }
4638
4639 if let Some(end_tag_offset) = end_tag_offset {
4640 let cursor_is_before_end_tag = column <= end_tag_offset;
4641 if cursor_is_after_start_tag {
4642 if cursor_is_before_end_tag {
4643 insert_extra_newline = true;
4644 }
4645 let cursor_is_at_start_of_end_tag =
4646 column == end_tag_offset;
4647 if cursor_is_at_start_of_end_tag {
4648 indent_on_extra_newline.len = *len;
4649 }
4650 }
4651 cursor_is_before_end_tag
4652 } else {
4653 true
4654 }
4655 };
4656
4657 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4658 && cursor_is_before_end_tag_if_exists
4659 {
4660 if cursor_is_after_start_tag {
4661 indent_on_newline.len = *len;
4662 }
4663 Some(delimiter.clone())
4664 } else {
4665 None
4666 }
4667 });
4668
4669 (
4670 comment_delimiter,
4671 doc_delimiter,
4672 insert_extra_newline,
4673 indent_on_newline,
4674 indent_on_extra_newline,
4675 )
4676 } else {
4677 (
4678 None,
4679 None,
4680 false,
4681 IndentSize::default(),
4682 IndentSize::default(),
4683 )
4684 };
4685
4686 let prevent_auto_indent = doc_delimiter.is_some();
4687 let delimiter = comment_delimiter.or(doc_delimiter);
4688
4689 let capacity_for_delimiter =
4690 delimiter.as_deref().map(str::len).unwrap_or_default();
4691 let mut new_text = String::with_capacity(
4692 1 + capacity_for_delimiter
4693 + existing_indent.len as usize
4694 + indent_on_newline.len as usize
4695 + indent_on_extra_newline.len as usize,
4696 );
4697 new_text.push('\n');
4698 new_text.extend(existing_indent.chars());
4699 new_text.extend(indent_on_newline.chars());
4700
4701 if let Some(delimiter) = &delimiter {
4702 new_text.push_str(delimiter);
4703 }
4704
4705 if insert_extra_newline {
4706 new_text.push('\n');
4707 new_text.extend(existing_indent.chars());
4708 new_text.extend(indent_on_extra_newline.chars());
4709 }
4710
4711 let anchor = buffer.anchor_after(end);
4712 let new_selection = selection.map(|_| anchor);
4713 (
4714 ((start..end, new_text), prevent_auto_indent),
4715 (insert_extra_newline, new_selection),
4716 )
4717 })
4718 .unzip()
4719 };
4720
4721 let mut auto_indent_edits = Vec::new();
4722 let mut edits = Vec::new();
4723 for (edit, prevent_auto_indent) in edits_with_flags {
4724 if prevent_auto_indent {
4725 edits.push(edit);
4726 } else {
4727 auto_indent_edits.push(edit);
4728 }
4729 }
4730 if !edits.is_empty() {
4731 this.edit(edits, cx);
4732 }
4733 if !auto_indent_edits.is_empty() {
4734 this.edit_with_autoindent(auto_indent_edits, cx);
4735 }
4736
4737 let buffer = this.buffer.read(cx).snapshot(cx);
4738 let new_selections = selection_info
4739 .into_iter()
4740 .map(|(extra_newline_inserted, new_selection)| {
4741 let mut cursor = new_selection.end.to_point(&buffer);
4742 if extra_newline_inserted {
4743 cursor.row -= 1;
4744 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4745 }
4746 new_selection.map(|_| cursor)
4747 })
4748 .collect();
4749
4750 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4751 this.refresh_edit_prediction(true, false, window, cx);
4752 });
4753 }
4754
4755 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4756 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4757
4758 let buffer = self.buffer.read(cx);
4759 let snapshot = buffer.snapshot(cx);
4760
4761 let mut edits = Vec::new();
4762 let mut rows = Vec::new();
4763
4764 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4765 let cursor = selection.head();
4766 let row = cursor.row;
4767
4768 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4769
4770 let newline = "\n".to_string();
4771 edits.push((start_of_line..start_of_line, newline));
4772
4773 rows.push(row + rows_inserted as u32);
4774 }
4775
4776 self.transact(window, cx, |editor, window, cx| {
4777 editor.edit(edits, cx);
4778
4779 editor.change_selections(Default::default(), window, cx, |s| {
4780 let mut index = 0;
4781 s.move_cursors_with(|map, _, _| {
4782 let row = rows[index];
4783 index += 1;
4784
4785 let point = Point::new(row, 0);
4786 let boundary = map.next_line_boundary(point).1;
4787 let clipped = map.clip_point(boundary, Bias::Left);
4788
4789 (clipped, SelectionGoal::None)
4790 });
4791 });
4792
4793 let mut indent_edits = Vec::new();
4794 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4795 for row in rows {
4796 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4797 for (row, indent) in indents {
4798 if indent.len == 0 {
4799 continue;
4800 }
4801
4802 let text = match indent.kind {
4803 IndentKind::Space => " ".repeat(indent.len as usize),
4804 IndentKind::Tab => "\t".repeat(indent.len as usize),
4805 };
4806 let point = Point::new(row.0, 0);
4807 indent_edits.push((point..point, text));
4808 }
4809 }
4810 editor.edit(indent_edits, cx);
4811 });
4812 }
4813
4814 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4816
4817 let buffer = self.buffer.read(cx);
4818 let snapshot = buffer.snapshot(cx);
4819
4820 let mut edits = Vec::new();
4821 let mut rows = Vec::new();
4822 let mut rows_inserted = 0;
4823
4824 for selection in self.selections.all_adjusted(cx) {
4825 let cursor = selection.head();
4826 let row = cursor.row;
4827
4828 let point = Point::new(row + 1, 0);
4829 let start_of_line = snapshot.clip_point(point, Bias::Left);
4830
4831 let newline = "\n".to_string();
4832 edits.push((start_of_line..start_of_line, newline));
4833
4834 rows_inserted += 1;
4835 rows.push(row + rows_inserted);
4836 }
4837
4838 self.transact(window, cx, |editor, window, cx| {
4839 editor.edit(edits, cx);
4840
4841 editor.change_selections(Default::default(), window, cx, |s| {
4842 let mut index = 0;
4843 s.move_cursors_with(|map, _, _| {
4844 let row = rows[index];
4845 index += 1;
4846
4847 let point = Point::new(row, 0);
4848 let boundary = map.next_line_boundary(point).1;
4849 let clipped = map.clip_point(boundary, Bias::Left);
4850
4851 (clipped, SelectionGoal::None)
4852 });
4853 });
4854
4855 let mut indent_edits = Vec::new();
4856 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4857 for row in rows {
4858 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4859 for (row, indent) in indents {
4860 if indent.len == 0 {
4861 continue;
4862 }
4863
4864 let text = match indent.kind {
4865 IndentKind::Space => " ".repeat(indent.len as usize),
4866 IndentKind::Tab => "\t".repeat(indent.len as usize),
4867 };
4868 let point = Point::new(row.0, 0);
4869 indent_edits.push((point..point, text));
4870 }
4871 }
4872 editor.edit(indent_edits, cx);
4873 });
4874 }
4875
4876 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4877 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4878 original_indent_columns: Vec::new(),
4879 });
4880 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4881 }
4882
4883 fn insert_with_autoindent_mode(
4884 &mut self,
4885 text: &str,
4886 autoindent_mode: Option<AutoindentMode>,
4887 window: &mut Window,
4888 cx: &mut Context<Self>,
4889 ) {
4890 if self.read_only(cx) {
4891 return;
4892 }
4893
4894 let text: Arc<str> = text.into();
4895 self.transact(window, cx, |this, window, cx| {
4896 let old_selections = this.selections.all_adjusted(cx);
4897 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4898 let anchors = {
4899 let snapshot = buffer.read(cx);
4900 old_selections
4901 .iter()
4902 .map(|s| {
4903 let anchor = snapshot.anchor_after(s.head());
4904 s.map(|_| anchor)
4905 })
4906 .collect::<Vec<_>>()
4907 };
4908 buffer.edit(
4909 old_selections
4910 .iter()
4911 .map(|s| (s.start..s.end, text.clone())),
4912 autoindent_mode,
4913 cx,
4914 );
4915 anchors
4916 });
4917
4918 this.change_selections(Default::default(), window, cx, |s| {
4919 s.select_anchors(selection_anchors);
4920 });
4921
4922 cx.notify();
4923 });
4924 }
4925
4926 fn trigger_completion_on_input(
4927 &mut self,
4928 text: &str,
4929 trigger_in_words: bool,
4930 window: &mut Window,
4931 cx: &mut Context<Self>,
4932 ) {
4933 let completions_source = self
4934 .context_menu
4935 .borrow()
4936 .as_ref()
4937 .and_then(|menu| match menu {
4938 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4939 CodeContextMenu::CodeActions(_) => None,
4940 });
4941
4942 match completions_source {
4943 Some(CompletionsMenuSource::Words { .. }) => {
4944 self.open_or_update_completions_menu(
4945 Some(CompletionsMenuSource::Words {
4946 ignore_threshold: false,
4947 }),
4948 None,
4949 window,
4950 cx,
4951 );
4952 }
4953 Some(CompletionsMenuSource::Normal)
4954 | Some(CompletionsMenuSource::SnippetChoices)
4955 | None
4956 if self.is_completion_trigger(
4957 text,
4958 trigger_in_words,
4959 completions_source.is_some(),
4960 cx,
4961 ) =>
4962 {
4963 self.show_completions(
4964 &ShowCompletions {
4965 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4966 },
4967 window,
4968 cx,
4969 )
4970 }
4971 _ => {
4972 self.hide_context_menu(window, cx);
4973 }
4974 }
4975 }
4976
4977 fn is_completion_trigger(
4978 &self,
4979 text: &str,
4980 trigger_in_words: bool,
4981 menu_is_open: bool,
4982 cx: &mut Context<Self>,
4983 ) -> bool {
4984 let position = self.selections.newest_anchor().head();
4985 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4986 return false;
4987 };
4988
4989 if let Some(completion_provider) = &self.completion_provider {
4990 completion_provider.is_completion_trigger(
4991 &buffer,
4992 position.text_anchor,
4993 text,
4994 trigger_in_words,
4995 menu_is_open,
4996 cx,
4997 )
4998 } else {
4999 false
5000 }
5001 }
5002
5003 /// If any empty selections is touching the start of its innermost containing autoclose
5004 /// region, expand it to select the brackets.
5005 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5006 let selections = self.selections.all::<usize>(cx);
5007 let buffer = self.buffer.read(cx).read(cx);
5008 let new_selections = self
5009 .selections_with_autoclose_regions(selections, &buffer)
5010 .map(|(mut selection, region)| {
5011 if !selection.is_empty() {
5012 return selection;
5013 }
5014
5015 if let Some(region) = region {
5016 let mut range = region.range.to_offset(&buffer);
5017 if selection.start == range.start && range.start >= region.pair.start.len() {
5018 range.start -= region.pair.start.len();
5019 if buffer.contains_str_at(range.start, ®ion.pair.start)
5020 && buffer.contains_str_at(range.end, ®ion.pair.end)
5021 {
5022 range.end += region.pair.end.len();
5023 selection.start = range.start;
5024 selection.end = range.end;
5025
5026 return selection;
5027 }
5028 }
5029 }
5030
5031 let always_treat_brackets_as_autoclosed = buffer
5032 .language_settings_at(selection.start, cx)
5033 .always_treat_brackets_as_autoclosed;
5034
5035 if !always_treat_brackets_as_autoclosed {
5036 return selection;
5037 }
5038
5039 if let Some(scope) = buffer.language_scope_at(selection.start) {
5040 for (pair, enabled) in scope.brackets() {
5041 if !enabled || !pair.close {
5042 continue;
5043 }
5044
5045 if buffer.contains_str_at(selection.start, &pair.end) {
5046 let pair_start_len = pair.start.len();
5047 if buffer.contains_str_at(
5048 selection.start.saturating_sub(pair_start_len),
5049 &pair.start,
5050 ) {
5051 selection.start -= pair_start_len;
5052 selection.end += pair.end.len();
5053
5054 return selection;
5055 }
5056 }
5057 }
5058 }
5059
5060 selection
5061 })
5062 .collect();
5063
5064 drop(buffer);
5065 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5066 selections.select(new_selections)
5067 });
5068 }
5069
5070 /// Iterate the given selections, and for each one, find the smallest surrounding
5071 /// autoclose region. This uses the ordering of the selections and the autoclose
5072 /// regions to avoid repeated comparisons.
5073 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5074 &'a self,
5075 selections: impl IntoIterator<Item = Selection<D>>,
5076 buffer: &'a MultiBufferSnapshot,
5077 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5078 let mut i = 0;
5079 let mut regions = self.autoclose_regions.as_slice();
5080 selections.into_iter().map(move |selection| {
5081 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5082
5083 let mut enclosing = None;
5084 while let Some(pair_state) = regions.get(i) {
5085 if pair_state.range.end.to_offset(buffer) < range.start {
5086 regions = ®ions[i + 1..];
5087 i = 0;
5088 } else if pair_state.range.start.to_offset(buffer) > range.end {
5089 break;
5090 } else {
5091 if pair_state.selection_id == selection.id {
5092 enclosing = Some(pair_state);
5093 }
5094 i += 1;
5095 }
5096 }
5097
5098 (selection, enclosing)
5099 })
5100 }
5101
5102 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5103 fn invalidate_autoclose_regions(
5104 &mut self,
5105 mut selections: &[Selection<Anchor>],
5106 buffer: &MultiBufferSnapshot,
5107 ) {
5108 self.autoclose_regions.retain(|state| {
5109 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5110 return false;
5111 }
5112
5113 let mut i = 0;
5114 while let Some(selection) = selections.get(i) {
5115 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5116 selections = &selections[1..];
5117 continue;
5118 }
5119 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5120 break;
5121 }
5122 if selection.id == state.selection_id {
5123 return true;
5124 } else {
5125 i += 1;
5126 }
5127 }
5128 false
5129 });
5130 }
5131
5132 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5133 let offset = position.to_offset(buffer);
5134 let (word_range, kind) =
5135 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5136 if offset > word_range.start && kind == Some(CharKind::Word) {
5137 Some(
5138 buffer
5139 .text_for_range(word_range.start..offset)
5140 .collect::<String>(),
5141 )
5142 } else {
5143 None
5144 }
5145 }
5146
5147 pub fn toggle_inline_values(
5148 &mut self,
5149 _: &ToggleInlineValues,
5150 _: &mut Window,
5151 cx: &mut Context<Self>,
5152 ) {
5153 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5154
5155 self.refresh_inline_values(cx);
5156 }
5157
5158 pub fn toggle_inlay_hints(
5159 &mut self,
5160 _: &ToggleInlayHints,
5161 _: &mut Window,
5162 cx: &mut Context<Self>,
5163 ) {
5164 self.refresh_inlay_hints(
5165 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5166 cx,
5167 );
5168 }
5169
5170 pub fn inlay_hints_enabled(&self) -> bool {
5171 self.inlay_hint_cache.enabled
5172 }
5173
5174 pub fn inline_values_enabled(&self) -> bool {
5175 self.inline_value_cache.enabled
5176 }
5177
5178 #[cfg(any(test, feature = "test-support"))]
5179 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5180 self.display_map
5181 .read(cx)
5182 .current_inlays()
5183 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5184 .cloned()
5185 .collect()
5186 }
5187
5188 #[cfg(any(test, feature = "test-support"))]
5189 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5190 self.display_map
5191 .read(cx)
5192 .current_inlays()
5193 .cloned()
5194 .collect()
5195 }
5196
5197 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5198 if self.semantics_provider.is_none() || !self.mode.is_full() {
5199 return;
5200 }
5201
5202 let reason_description = reason.description();
5203 let ignore_debounce = matches!(
5204 reason,
5205 InlayHintRefreshReason::SettingsChange(_)
5206 | InlayHintRefreshReason::Toggle(_)
5207 | InlayHintRefreshReason::ExcerptsRemoved(_)
5208 | InlayHintRefreshReason::ModifiersChanged(_)
5209 );
5210 let (invalidate_cache, required_languages) = match reason {
5211 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5212 match self.inlay_hint_cache.modifiers_override(enabled) {
5213 Some(enabled) => {
5214 if enabled {
5215 (InvalidationStrategy::RefreshRequested, None)
5216 } else {
5217 self.splice_inlays(
5218 &self
5219 .visible_inlay_hints(cx)
5220 .iter()
5221 .map(|inlay| inlay.id)
5222 .collect::<Vec<InlayId>>(),
5223 Vec::new(),
5224 cx,
5225 );
5226 return;
5227 }
5228 }
5229 None => return,
5230 }
5231 }
5232 InlayHintRefreshReason::Toggle(enabled) => {
5233 if self.inlay_hint_cache.toggle(enabled) {
5234 if enabled {
5235 (InvalidationStrategy::RefreshRequested, None)
5236 } else {
5237 self.splice_inlays(
5238 &self
5239 .visible_inlay_hints(cx)
5240 .iter()
5241 .map(|inlay| inlay.id)
5242 .collect::<Vec<InlayId>>(),
5243 Vec::new(),
5244 cx,
5245 );
5246 return;
5247 }
5248 } else {
5249 return;
5250 }
5251 }
5252 InlayHintRefreshReason::SettingsChange(new_settings) => {
5253 match self.inlay_hint_cache.update_settings(
5254 &self.buffer,
5255 new_settings,
5256 self.visible_inlay_hints(cx),
5257 cx,
5258 ) {
5259 ControlFlow::Break(Some(InlaySplice {
5260 to_remove,
5261 to_insert,
5262 })) => {
5263 self.splice_inlays(&to_remove, to_insert, cx);
5264 return;
5265 }
5266 ControlFlow::Break(None) => return,
5267 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5268 }
5269 }
5270 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5271 if let Some(InlaySplice {
5272 to_remove,
5273 to_insert,
5274 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5275 {
5276 self.splice_inlays(&to_remove, to_insert, cx);
5277 }
5278 self.display_map.update(cx, |display_map, _| {
5279 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5280 });
5281 return;
5282 }
5283 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5284 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5285 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5286 }
5287 InlayHintRefreshReason::RefreshRequested => {
5288 (InvalidationStrategy::RefreshRequested, None)
5289 }
5290 };
5291
5292 if let Some(InlaySplice {
5293 to_remove,
5294 to_insert,
5295 }) = self.inlay_hint_cache.spawn_hint_refresh(
5296 reason_description,
5297 self.visible_excerpts(required_languages.as_ref(), cx),
5298 invalidate_cache,
5299 ignore_debounce,
5300 cx,
5301 ) {
5302 self.splice_inlays(&to_remove, to_insert, cx);
5303 }
5304 }
5305
5306 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5307 self.display_map
5308 .read(cx)
5309 .current_inlays()
5310 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5311 .cloned()
5312 .collect()
5313 }
5314
5315 pub fn visible_excerpts(
5316 &self,
5317 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5318 cx: &mut Context<Editor>,
5319 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5320 let Some(project) = self.project() else {
5321 return HashMap::default();
5322 };
5323 let project = project.read(cx);
5324 let multi_buffer = self.buffer().read(cx);
5325 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5326 let multi_buffer_visible_start = self
5327 .scroll_manager
5328 .anchor()
5329 .anchor
5330 .to_point(&multi_buffer_snapshot);
5331 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5332 multi_buffer_visible_start
5333 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5334 Bias::Left,
5335 );
5336 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5337 multi_buffer_snapshot
5338 .range_to_buffer_ranges(multi_buffer_visible_range)
5339 .into_iter()
5340 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5341 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5342 let buffer_file = project::File::from_dyn(buffer.file())?;
5343 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5344 let worktree_entry = buffer_worktree
5345 .read(cx)
5346 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5347 if worktree_entry.is_ignored {
5348 return None;
5349 }
5350
5351 let language = buffer.language()?;
5352 if let Some(restrict_to_languages) = restrict_to_languages
5353 && !restrict_to_languages.contains(language)
5354 {
5355 return None;
5356 }
5357 Some((
5358 excerpt_id,
5359 (
5360 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5361 buffer.version().clone(),
5362 excerpt_visible_range,
5363 ),
5364 ))
5365 })
5366 .collect()
5367 }
5368
5369 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5370 TextLayoutDetails {
5371 text_system: window.text_system().clone(),
5372 editor_style: self.style.clone().unwrap(),
5373 rem_size: window.rem_size(),
5374 scroll_anchor: self.scroll_manager.anchor(),
5375 visible_rows: self.visible_line_count(),
5376 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5377 }
5378 }
5379
5380 pub fn splice_inlays(
5381 &self,
5382 to_remove: &[InlayId],
5383 to_insert: Vec<Inlay>,
5384 cx: &mut Context<Self>,
5385 ) {
5386 self.display_map.update(cx, |display_map, cx| {
5387 display_map.splice_inlays(to_remove, to_insert, cx)
5388 });
5389 cx.notify();
5390 }
5391
5392 fn trigger_on_type_formatting(
5393 &self,
5394 input: String,
5395 window: &mut Window,
5396 cx: &mut Context<Self>,
5397 ) -> Option<Task<Result<()>>> {
5398 if input.len() != 1 {
5399 return None;
5400 }
5401
5402 let project = self.project()?;
5403 let position = self.selections.newest_anchor().head();
5404 let (buffer, buffer_position) = self
5405 .buffer
5406 .read(cx)
5407 .text_anchor_for_position(position, cx)?;
5408
5409 let settings = language_settings::language_settings(
5410 buffer
5411 .read(cx)
5412 .language_at(buffer_position)
5413 .map(|l| l.name()),
5414 buffer.read(cx).file(),
5415 cx,
5416 );
5417 if !settings.use_on_type_format {
5418 return None;
5419 }
5420
5421 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5422 // hence we do LSP request & edit on host side only — add formats to host's history.
5423 let push_to_lsp_host_history = true;
5424 // If this is not the host, append its history with new edits.
5425 let push_to_client_history = project.read(cx).is_via_collab();
5426
5427 let on_type_formatting = project.update(cx, |project, cx| {
5428 project.on_type_format(
5429 buffer.clone(),
5430 buffer_position,
5431 input,
5432 push_to_lsp_host_history,
5433 cx,
5434 )
5435 });
5436 Some(cx.spawn_in(window, async move |editor, cx| {
5437 if let Some(transaction) = on_type_formatting.await? {
5438 if push_to_client_history {
5439 buffer
5440 .update(cx, |buffer, _| {
5441 buffer.push_transaction(transaction, Instant::now());
5442 buffer.finalize_last_transaction();
5443 })
5444 .ok();
5445 }
5446 editor.update(cx, |editor, cx| {
5447 editor.refresh_document_highlights(cx);
5448 })?;
5449 }
5450 Ok(())
5451 }))
5452 }
5453
5454 pub fn show_word_completions(
5455 &mut self,
5456 _: &ShowWordCompletions,
5457 window: &mut Window,
5458 cx: &mut Context<Self>,
5459 ) {
5460 self.open_or_update_completions_menu(
5461 Some(CompletionsMenuSource::Words {
5462 ignore_threshold: true,
5463 }),
5464 None,
5465 window,
5466 cx,
5467 );
5468 }
5469
5470 pub fn show_completions(
5471 &mut self,
5472 options: &ShowCompletions,
5473 window: &mut Window,
5474 cx: &mut Context<Self>,
5475 ) {
5476 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5477 }
5478
5479 fn open_or_update_completions_menu(
5480 &mut self,
5481 requested_source: Option<CompletionsMenuSource>,
5482 trigger: Option<&str>,
5483 window: &mut Window,
5484 cx: &mut Context<Self>,
5485 ) {
5486 if self.pending_rename.is_some() {
5487 return;
5488 }
5489
5490 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5491
5492 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5493 // inserted and selected. To handle that case, the start of the selection is used so that
5494 // the menu starts with all choices.
5495 let position = self
5496 .selections
5497 .newest_anchor()
5498 .start
5499 .bias_right(&multibuffer_snapshot);
5500 if position.diff_base_anchor.is_some() {
5501 return;
5502 }
5503 let buffer_position = multibuffer_snapshot.anchor_before(position);
5504 let Some(buffer) = buffer_position
5505 .buffer_id
5506 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5507 else {
5508 return;
5509 };
5510 let buffer_snapshot = buffer.read(cx).snapshot();
5511
5512 let query: Option<Arc<String>> =
5513 Self::completion_query(&multibuffer_snapshot, buffer_position)
5514 .map(|query| query.into());
5515
5516 drop(multibuffer_snapshot);
5517
5518 // Hide the current completions menu when query is empty. Without this, cached
5519 // completions from before the trigger char may be reused (#32774).
5520 if query.is_none() {
5521 let menu_is_open = matches!(
5522 self.context_menu.borrow().as_ref(),
5523 Some(CodeContextMenu::Completions(_))
5524 );
5525 if menu_is_open {
5526 self.hide_context_menu(window, cx);
5527 }
5528 }
5529
5530 let mut ignore_word_threshold = false;
5531 let provider = match requested_source {
5532 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5533 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5534 ignore_word_threshold = ignore_threshold;
5535 None
5536 }
5537 Some(CompletionsMenuSource::SnippetChoices) => {
5538 log::error!("bug: SnippetChoices requested_source is not handled");
5539 None
5540 }
5541 };
5542
5543 let sort_completions = provider
5544 .as_ref()
5545 .is_some_and(|provider| provider.sort_completions());
5546
5547 let filter_completions = provider
5548 .as_ref()
5549 .is_none_or(|provider| provider.filter_completions());
5550
5551 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5552 if filter_completions {
5553 menu.filter(query.clone(), provider.clone(), window, cx);
5554 }
5555 // When `is_incomplete` is false, no need to re-query completions when the current query
5556 // is a suffix of the initial query.
5557 if !menu.is_incomplete {
5558 // If the new query is a suffix of the old query (typing more characters) and
5559 // the previous result was complete, the existing completions can be filtered.
5560 //
5561 // Note that this is always true for snippet completions.
5562 let query_matches = match (&menu.initial_query, &query) {
5563 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5564 (None, _) => true,
5565 _ => false,
5566 };
5567 if query_matches {
5568 let position_matches = if menu.initial_position == position {
5569 true
5570 } else {
5571 let snapshot = self.buffer.read(cx).read(cx);
5572 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5573 };
5574 if position_matches {
5575 return;
5576 }
5577 }
5578 }
5579 };
5580
5581 let trigger_kind = match trigger {
5582 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5583 CompletionTriggerKind::TRIGGER_CHARACTER
5584 }
5585 _ => CompletionTriggerKind::INVOKED,
5586 };
5587 let completion_context = CompletionContext {
5588 trigger_character: trigger.and_then(|trigger| {
5589 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5590 Some(String::from(trigger))
5591 } else {
5592 None
5593 }
5594 }),
5595 trigger_kind,
5596 };
5597
5598 let Anchor {
5599 excerpt_id: buffer_excerpt_id,
5600 text_anchor: buffer_position,
5601 ..
5602 } = buffer_position;
5603
5604 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5605 buffer_snapshot.surrounding_word(buffer_position, None)
5606 {
5607 let word_to_exclude = buffer_snapshot
5608 .text_for_range(word_range.clone())
5609 .collect::<String>();
5610 (
5611 buffer_snapshot.anchor_before(word_range.start)
5612 ..buffer_snapshot.anchor_after(buffer_position),
5613 Some(word_to_exclude),
5614 )
5615 } else {
5616 (buffer_position..buffer_position, None)
5617 };
5618
5619 let language = buffer_snapshot
5620 .language_at(buffer_position)
5621 .map(|language| language.name());
5622
5623 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5624 .completions
5625 .clone();
5626
5627 let show_completion_documentation = buffer_snapshot
5628 .settings_at(buffer_position, cx)
5629 .show_completion_documentation;
5630
5631 // The document can be large, so stay in reasonable bounds when searching for words,
5632 // otherwise completion pop-up might be slow to appear.
5633 const WORD_LOOKUP_ROWS: u32 = 5_000;
5634 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5635 let min_word_search = buffer_snapshot.clip_point(
5636 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5637 Bias::Left,
5638 );
5639 let max_word_search = buffer_snapshot.clip_point(
5640 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5641 Bias::Right,
5642 );
5643 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5644 ..buffer_snapshot.point_to_offset(max_word_search);
5645
5646 let skip_digits = query
5647 .as_ref()
5648 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5649
5650 let omit_word_completions = !self.word_completions_enabled
5651 || (!ignore_word_threshold
5652 && match &query {
5653 Some(query) => query.chars().count() < completion_settings.words_min_length,
5654 None => completion_settings.words_min_length != 0,
5655 });
5656
5657 let (mut words, provider_responses) = match &provider {
5658 Some(provider) => {
5659 let provider_responses = provider.completions(
5660 buffer_excerpt_id,
5661 &buffer,
5662 buffer_position,
5663 completion_context,
5664 window,
5665 cx,
5666 );
5667
5668 let words = match (omit_word_completions, completion_settings.words) {
5669 (true, _) | (_, WordsCompletionMode::Disabled) => {
5670 Task::ready(BTreeMap::default())
5671 }
5672 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5673 .background_spawn(async move {
5674 buffer_snapshot.words_in_range(WordsQuery {
5675 fuzzy_contents: None,
5676 range: word_search_range,
5677 skip_digits,
5678 })
5679 }),
5680 };
5681
5682 (words, provider_responses)
5683 }
5684 None => {
5685 let words = if omit_word_completions {
5686 Task::ready(BTreeMap::default())
5687 } else {
5688 cx.background_spawn(async move {
5689 buffer_snapshot.words_in_range(WordsQuery {
5690 fuzzy_contents: None,
5691 range: word_search_range,
5692 skip_digits,
5693 })
5694 })
5695 };
5696 (words, Task::ready(Ok(Vec::new())))
5697 }
5698 };
5699
5700 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5701
5702 let id = post_inc(&mut self.next_completion_id);
5703 let task = cx.spawn_in(window, async move |editor, cx| {
5704 let Ok(()) = editor.update(cx, |this, _| {
5705 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5706 }) else {
5707 return;
5708 };
5709
5710 // TODO: Ideally completions from different sources would be selectively re-queried, so
5711 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5712 let mut completions = Vec::new();
5713 let mut is_incomplete = false;
5714 let mut display_options: Option<CompletionDisplayOptions> = None;
5715 if let Some(provider_responses) = provider_responses.await.log_err()
5716 && !provider_responses.is_empty()
5717 {
5718 for response in provider_responses {
5719 completions.extend(response.completions);
5720 is_incomplete = is_incomplete || response.is_incomplete;
5721 match display_options.as_mut() {
5722 None => {
5723 display_options = Some(response.display_options);
5724 }
5725 Some(options) => options.merge(&response.display_options),
5726 }
5727 }
5728 if completion_settings.words == WordsCompletionMode::Fallback {
5729 words = Task::ready(BTreeMap::default());
5730 }
5731 }
5732 let display_options = display_options.unwrap_or_default();
5733
5734 let mut words = words.await;
5735 if let Some(word_to_exclude) = &word_to_exclude {
5736 words.remove(word_to_exclude);
5737 }
5738 for lsp_completion in &completions {
5739 words.remove(&lsp_completion.new_text);
5740 }
5741 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5742 replace_range: word_replace_range.clone(),
5743 new_text: word.clone(),
5744 label: CodeLabel::plain(word, None),
5745 icon_path: None,
5746 documentation: None,
5747 source: CompletionSource::BufferWord {
5748 word_range,
5749 resolved: false,
5750 },
5751 insert_text_mode: Some(InsertTextMode::AS_IS),
5752 confirm: None,
5753 }));
5754
5755 let menu = if completions.is_empty() {
5756 None
5757 } else {
5758 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5759 let languages = editor
5760 .workspace
5761 .as_ref()
5762 .and_then(|(workspace, _)| workspace.upgrade())
5763 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5764 let menu = CompletionsMenu::new(
5765 id,
5766 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5767 sort_completions,
5768 show_completion_documentation,
5769 position,
5770 query.clone(),
5771 is_incomplete,
5772 buffer.clone(),
5773 completions.into(),
5774 display_options,
5775 snippet_sort_order,
5776 languages,
5777 language,
5778 cx,
5779 );
5780
5781 let query = if filter_completions { query } else { None };
5782 let matches_task = if let Some(query) = query {
5783 menu.do_async_filtering(query, cx)
5784 } else {
5785 Task::ready(menu.unfiltered_matches())
5786 };
5787 (menu, matches_task)
5788 }) else {
5789 return;
5790 };
5791
5792 let matches = matches_task.await;
5793
5794 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5795 // Newer menu already set, so exit.
5796 if let Some(CodeContextMenu::Completions(prev_menu)) =
5797 editor.context_menu.borrow().as_ref()
5798 && prev_menu.id > id
5799 {
5800 return;
5801 };
5802
5803 // Only valid to take prev_menu because it the new menu is immediately set
5804 // below, or the menu is hidden.
5805 if let Some(CodeContextMenu::Completions(prev_menu)) =
5806 editor.context_menu.borrow_mut().take()
5807 {
5808 let position_matches =
5809 if prev_menu.initial_position == menu.initial_position {
5810 true
5811 } else {
5812 let snapshot = editor.buffer.read(cx).read(cx);
5813 prev_menu.initial_position.to_offset(&snapshot)
5814 == menu.initial_position.to_offset(&snapshot)
5815 };
5816 if position_matches {
5817 // Preserve markdown cache before `set_filter_results` because it will
5818 // try to populate the documentation cache.
5819 menu.preserve_markdown_cache(prev_menu);
5820 }
5821 };
5822
5823 menu.set_filter_results(matches, provider, window, cx);
5824 }) else {
5825 return;
5826 };
5827
5828 menu.visible().then_some(menu)
5829 };
5830
5831 editor
5832 .update_in(cx, |editor, window, cx| {
5833 if editor.focus_handle.is_focused(window)
5834 && let Some(menu) = menu
5835 {
5836 *editor.context_menu.borrow_mut() =
5837 Some(CodeContextMenu::Completions(menu));
5838
5839 crate::hover_popover::hide_hover(editor, cx);
5840 if editor.show_edit_predictions_in_menu() {
5841 editor.update_visible_edit_prediction(window, cx);
5842 } else {
5843 editor.discard_edit_prediction(false, cx);
5844 }
5845
5846 cx.notify();
5847 return;
5848 }
5849
5850 if editor.completion_tasks.len() <= 1 {
5851 // If there are no more completion tasks and the last menu was empty, we should hide it.
5852 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5853 // If it was already hidden and we don't show edit predictions in the menu,
5854 // we should also show the edit prediction when available.
5855 if was_hidden && editor.show_edit_predictions_in_menu() {
5856 editor.update_visible_edit_prediction(window, cx);
5857 }
5858 }
5859 })
5860 .ok();
5861 });
5862
5863 self.completion_tasks.push((id, task));
5864 }
5865
5866 #[cfg(feature = "test-support")]
5867 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5868 let menu = self.context_menu.borrow();
5869 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5870 let completions = menu.completions.borrow();
5871 Some(completions.to_vec())
5872 } else {
5873 None
5874 }
5875 }
5876
5877 pub fn with_completions_menu_matching_id<R>(
5878 &self,
5879 id: CompletionId,
5880 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5881 ) -> R {
5882 let mut context_menu = self.context_menu.borrow_mut();
5883 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5884 return f(None);
5885 };
5886 if completions_menu.id != id {
5887 return f(None);
5888 }
5889 f(Some(completions_menu))
5890 }
5891
5892 pub fn confirm_completion(
5893 &mut self,
5894 action: &ConfirmCompletion,
5895 window: &mut Window,
5896 cx: &mut Context<Self>,
5897 ) -> Option<Task<Result<()>>> {
5898 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5899 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5900 }
5901
5902 pub fn confirm_completion_insert(
5903 &mut self,
5904 _: &ConfirmCompletionInsert,
5905 window: &mut Window,
5906 cx: &mut Context<Self>,
5907 ) -> Option<Task<Result<()>>> {
5908 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5909 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5910 }
5911
5912 pub fn confirm_completion_replace(
5913 &mut self,
5914 _: &ConfirmCompletionReplace,
5915 window: &mut Window,
5916 cx: &mut Context<Self>,
5917 ) -> Option<Task<Result<()>>> {
5918 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5919 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5920 }
5921
5922 pub fn compose_completion(
5923 &mut self,
5924 action: &ComposeCompletion,
5925 window: &mut Window,
5926 cx: &mut Context<Self>,
5927 ) -> Option<Task<Result<()>>> {
5928 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5929 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5930 }
5931
5932 fn do_completion(
5933 &mut self,
5934 item_ix: Option<usize>,
5935 intent: CompletionIntent,
5936 window: &mut Window,
5937 cx: &mut Context<Editor>,
5938 ) -> Option<Task<Result<()>>> {
5939 use language::ToOffset as _;
5940
5941 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5942 else {
5943 return None;
5944 };
5945
5946 let candidate_id = {
5947 let entries = completions_menu.entries.borrow();
5948 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5949 if self.show_edit_predictions_in_menu() {
5950 self.discard_edit_prediction(true, cx);
5951 }
5952 mat.candidate_id
5953 };
5954
5955 let completion = completions_menu
5956 .completions
5957 .borrow()
5958 .get(candidate_id)?
5959 .clone();
5960 cx.stop_propagation();
5961
5962 let buffer_handle = completions_menu.buffer.clone();
5963
5964 let CompletionEdit {
5965 new_text,
5966 snippet,
5967 replace_range,
5968 } = process_completion_for_edit(
5969 &completion,
5970 intent,
5971 &buffer_handle,
5972 &completions_menu.initial_position.text_anchor,
5973 cx,
5974 );
5975
5976 let buffer = buffer_handle.read(cx);
5977 let snapshot = self.buffer.read(cx).snapshot(cx);
5978 let newest_anchor = self.selections.newest_anchor();
5979 let replace_range_multibuffer = {
5980 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5981 let multibuffer_anchor = snapshot
5982 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5983 .unwrap()
5984 ..snapshot
5985 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5986 .unwrap();
5987 multibuffer_anchor.start.to_offset(&snapshot)
5988 ..multibuffer_anchor.end.to_offset(&snapshot)
5989 };
5990 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5991 return None;
5992 }
5993
5994 let old_text = buffer
5995 .text_for_range(replace_range.clone())
5996 .collect::<String>();
5997 let lookbehind = newest_anchor
5998 .start
5999 .text_anchor
6000 .to_offset(buffer)
6001 .saturating_sub(replace_range.start);
6002 let lookahead = replace_range
6003 .end
6004 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6005 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6006 let suffix = &old_text[lookbehind.min(old_text.len())..];
6007
6008 let selections = self.selections.all::<usize>(cx);
6009 let mut ranges = Vec::new();
6010 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6011
6012 for selection in &selections {
6013 let range = if selection.id == newest_anchor.id {
6014 replace_range_multibuffer.clone()
6015 } else {
6016 let mut range = selection.range();
6017
6018 // if prefix is present, don't duplicate it
6019 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6020 range.start = range.start.saturating_sub(lookbehind);
6021
6022 // if suffix is also present, mimic the newest cursor and replace it
6023 if selection.id != newest_anchor.id
6024 && snapshot.contains_str_at(range.end, suffix)
6025 {
6026 range.end += lookahead;
6027 }
6028 }
6029 range
6030 };
6031
6032 ranges.push(range.clone());
6033
6034 if !self.linked_edit_ranges.is_empty() {
6035 let start_anchor = snapshot.anchor_before(range.start);
6036 let end_anchor = snapshot.anchor_after(range.end);
6037 if let Some(ranges) = self
6038 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6039 {
6040 for (buffer, edits) in ranges {
6041 linked_edits
6042 .entry(buffer.clone())
6043 .or_default()
6044 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6045 }
6046 }
6047 }
6048 }
6049
6050 let common_prefix_len = old_text
6051 .chars()
6052 .zip(new_text.chars())
6053 .take_while(|(a, b)| a == b)
6054 .map(|(a, _)| a.len_utf8())
6055 .sum::<usize>();
6056
6057 cx.emit(EditorEvent::InputHandled {
6058 utf16_range_to_replace: None,
6059 text: new_text[common_prefix_len..].into(),
6060 });
6061
6062 self.transact(window, cx, |editor, window, cx| {
6063 if let Some(mut snippet) = snippet {
6064 snippet.text = new_text.to_string();
6065 editor
6066 .insert_snippet(&ranges, snippet, window, cx)
6067 .log_err();
6068 } else {
6069 editor.buffer.update(cx, |multi_buffer, cx| {
6070 let auto_indent = match completion.insert_text_mode {
6071 Some(InsertTextMode::AS_IS) => None,
6072 _ => editor.autoindent_mode.clone(),
6073 };
6074 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6075 multi_buffer.edit(edits, auto_indent, cx);
6076 });
6077 }
6078 for (buffer, edits) in linked_edits {
6079 buffer.update(cx, |buffer, cx| {
6080 let snapshot = buffer.snapshot();
6081 let edits = edits
6082 .into_iter()
6083 .map(|(range, text)| {
6084 use text::ToPoint as TP;
6085 let end_point = TP::to_point(&range.end, &snapshot);
6086 let start_point = TP::to_point(&range.start, &snapshot);
6087 (start_point..end_point, text)
6088 })
6089 .sorted_by_key(|(range, _)| range.start);
6090 buffer.edit(edits, None, cx);
6091 })
6092 }
6093
6094 editor.refresh_edit_prediction(true, false, window, cx);
6095 });
6096 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6097
6098 let show_new_completions_on_confirm = completion
6099 .confirm
6100 .as_ref()
6101 .is_some_and(|confirm| confirm(intent, window, cx));
6102 if show_new_completions_on_confirm {
6103 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6104 }
6105
6106 let provider = self.completion_provider.as_ref()?;
6107 drop(completion);
6108 let apply_edits = provider.apply_additional_edits_for_completion(
6109 buffer_handle,
6110 completions_menu.completions.clone(),
6111 candidate_id,
6112 true,
6113 cx,
6114 );
6115
6116 let editor_settings = EditorSettings::get_global(cx);
6117 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6118 // After the code completion is finished, users often want to know what signatures are needed.
6119 // so we should automatically call signature_help
6120 self.show_signature_help(&ShowSignatureHelp, window, cx);
6121 }
6122
6123 Some(cx.foreground_executor().spawn(async move {
6124 apply_edits.await?;
6125 Ok(())
6126 }))
6127 }
6128
6129 pub fn toggle_code_actions(
6130 &mut self,
6131 action: &ToggleCodeActions,
6132 window: &mut Window,
6133 cx: &mut Context<Self>,
6134 ) {
6135 let quick_launch = action.quick_launch;
6136 let mut context_menu = self.context_menu.borrow_mut();
6137 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6138 if code_actions.deployed_from == action.deployed_from {
6139 // Toggle if we're selecting the same one
6140 *context_menu = None;
6141 cx.notify();
6142 return;
6143 } else {
6144 // Otherwise, clear it and start a new one
6145 *context_menu = None;
6146 cx.notify();
6147 }
6148 }
6149 drop(context_menu);
6150 let snapshot = self.snapshot(window, cx);
6151 let deployed_from = action.deployed_from.clone();
6152 let action = action.clone();
6153 self.completion_tasks.clear();
6154 self.discard_edit_prediction(false, cx);
6155
6156 let multibuffer_point = match &action.deployed_from {
6157 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6158 DisplayPoint::new(*row, 0).to_point(&snapshot)
6159 }
6160 _ => self.selections.newest::<Point>(cx).head(),
6161 };
6162 let Some((buffer, buffer_row)) = snapshot
6163 .buffer_snapshot
6164 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6165 .and_then(|(buffer_snapshot, range)| {
6166 self.buffer()
6167 .read(cx)
6168 .buffer(buffer_snapshot.remote_id())
6169 .map(|buffer| (buffer, range.start.row))
6170 })
6171 else {
6172 return;
6173 };
6174 let buffer_id = buffer.read(cx).remote_id();
6175 let tasks = self
6176 .tasks
6177 .get(&(buffer_id, buffer_row))
6178 .map(|t| Arc::new(t.to_owned()));
6179
6180 if !self.focus_handle.is_focused(window) {
6181 return;
6182 }
6183 let project = self.project.clone();
6184
6185 let code_actions_task = match deployed_from {
6186 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6187 _ => self.code_actions(buffer_row, window, cx),
6188 };
6189
6190 let runnable_task = match deployed_from {
6191 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6192 _ => {
6193 let mut task_context_task = Task::ready(None);
6194 if let Some(tasks) = &tasks
6195 && let Some(project) = project
6196 {
6197 task_context_task =
6198 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6199 }
6200
6201 cx.spawn_in(window, {
6202 let buffer = buffer.clone();
6203 async move |editor, cx| {
6204 let task_context = task_context_task.await;
6205
6206 let resolved_tasks =
6207 tasks
6208 .zip(task_context.clone())
6209 .map(|(tasks, task_context)| ResolvedTasks {
6210 templates: tasks.resolve(&task_context).collect(),
6211 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6212 multibuffer_point.row,
6213 tasks.column,
6214 )),
6215 });
6216 let debug_scenarios = editor
6217 .update(cx, |editor, cx| {
6218 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6219 })?
6220 .await;
6221 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6222 }
6223 })
6224 }
6225 };
6226
6227 cx.spawn_in(window, async move |editor, cx| {
6228 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6229 let code_actions = code_actions_task.await;
6230 let spawn_straight_away = quick_launch
6231 && resolved_tasks
6232 .as_ref()
6233 .is_some_and(|tasks| tasks.templates.len() == 1)
6234 && code_actions
6235 .as_ref()
6236 .is_none_or(|actions| actions.is_empty())
6237 && debug_scenarios.is_empty();
6238
6239 editor.update_in(cx, |editor, window, cx| {
6240 crate::hover_popover::hide_hover(editor, cx);
6241 let actions = CodeActionContents::new(
6242 resolved_tasks,
6243 code_actions,
6244 debug_scenarios,
6245 task_context.unwrap_or_default(),
6246 );
6247
6248 // Don't show the menu if there are no actions available
6249 if actions.is_empty() {
6250 cx.notify();
6251 return Task::ready(Ok(()));
6252 }
6253
6254 *editor.context_menu.borrow_mut() =
6255 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6256 buffer,
6257 actions,
6258 selected_item: Default::default(),
6259 scroll_handle: UniformListScrollHandle::default(),
6260 deployed_from,
6261 }));
6262 cx.notify();
6263 if spawn_straight_away
6264 && let Some(task) = editor.confirm_code_action(
6265 &ConfirmCodeAction { item_ix: Some(0) },
6266 window,
6267 cx,
6268 )
6269 {
6270 return task;
6271 }
6272
6273 Task::ready(Ok(()))
6274 })
6275 })
6276 .detach_and_log_err(cx);
6277 }
6278
6279 fn debug_scenarios(
6280 &mut self,
6281 resolved_tasks: &Option<ResolvedTasks>,
6282 buffer: &Entity<Buffer>,
6283 cx: &mut App,
6284 ) -> Task<Vec<task::DebugScenario>> {
6285 maybe!({
6286 let project = self.project()?;
6287 let dap_store = project.read(cx).dap_store();
6288 let mut scenarios = vec![];
6289 let resolved_tasks = resolved_tasks.as_ref()?;
6290 let buffer = buffer.read(cx);
6291 let language = buffer.language()?;
6292 let file = buffer.file();
6293 let debug_adapter = language_settings(language.name().into(), file, cx)
6294 .debuggers
6295 .first()
6296 .map(SharedString::from)
6297 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6298
6299 dap_store.update(cx, |dap_store, cx| {
6300 for (_, task) in &resolved_tasks.templates {
6301 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6302 task.original_task().clone(),
6303 debug_adapter.clone().into(),
6304 task.display_label().to_owned().into(),
6305 cx,
6306 );
6307 scenarios.push(maybe_scenario);
6308 }
6309 });
6310 Some(cx.background_spawn(async move {
6311 futures::future::join_all(scenarios)
6312 .await
6313 .into_iter()
6314 .flatten()
6315 .collect::<Vec<_>>()
6316 }))
6317 })
6318 .unwrap_or_else(|| Task::ready(vec![]))
6319 }
6320
6321 fn code_actions(
6322 &mut self,
6323 buffer_row: u32,
6324 window: &mut Window,
6325 cx: &mut Context<Self>,
6326 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6327 let mut task = self.code_actions_task.take();
6328 cx.spawn_in(window, async move |editor, cx| {
6329 while let Some(prev_task) = task {
6330 prev_task.await.log_err();
6331 task = editor
6332 .update(cx, |this, _| this.code_actions_task.take())
6333 .ok()?;
6334 }
6335
6336 editor
6337 .update(cx, |editor, cx| {
6338 editor
6339 .available_code_actions
6340 .clone()
6341 .and_then(|(location, code_actions)| {
6342 let snapshot = location.buffer.read(cx).snapshot();
6343 let point_range = location.range.to_point(&snapshot);
6344 let point_range = point_range.start.row..=point_range.end.row;
6345 if point_range.contains(&buffer_row) {
6346 Some(code_actions)
6347 } else {
6348 None
6349 }
6350 })
6351 })
6352 .ok()
6353 .flatten()
6354 })
6355 }
6356
6357 pub fn confirm_code_action(
6358 &mut self,
6359 action: &ConfirmCodeAction,
6360 window: &mut Window,
6361 cx: &mut Context<Self>,
6362 ) -> Option<Task<Result<()>>> {
6363 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6364
6365 let actions_menu =
6366 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6367 menu
6368 } else {
6369 return None;
6370 };
6371
6372 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6373 let action = actions_menu.actions.get(action_ix)?;
6374 let title = action.label();
6375 let buffer = actions_menu.buffer;
6376 let workspace = self.workspace()?;
6377
6378 match action {
6379 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6380 workspace.update(cx, |workspace, cx| {
6381 workspace.schedule_resolved_task(
6382 task_source_kind,
6383 resolved_task,
6384 false,
6385 window,
6386 cx,
6387 );
6388
6389 Some(Task::ready(Ok(())))
6390 })
6391 }
6392 CodeActionsItem::CodeAction {
6393 excerpt_id,
6394 action,
6395 provider,
6396 } => {
6397 let apply_code_action =
6398 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6399 let workspace = workspace.downgrade();
6400 Some(cx.spawn_in(window, async move |editor, cx| {
6401 let project_transaction = apply_code_action.await?;
6402 Self::open_project_transaction(
6403 &editor,
6404 workspace,
6405 project_transaction,
6406 title,
6407 cx,
6408 )
6409 .await
6410 }))
6411 }
6412 CodeActionsItem::DebugScenario(scenario) => {
6413 let context = actions_menu.actions.context;
6414
6415 workspace.update(cx, |workspace, cx| {
6416 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6417 workspace.start_debug_session(
6418 scenario,
6419 context,
6420 Some(buffer),
6421 None,
6422 window,
6423 cx,
6424 );
6425 });
6426 Some(Task::ready(Ok(())))
6427 }
6428 }
6429 }
6430
6431 pub async fn open_project_transaction(
6432 editor: &WeakEntity<Editor>,
6433 workspace: WeakEntity<Workspace>,
6434 transaction: ProjectTransaction,
6435 title: String,
6436 cx: &mut AsyncWindowContext,
6437 ) -> Result<()> {
6438 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6439 cx.update(|_, cx| {
6440 entries.sort_unstable_by_key(|(buffer, _)| {
6441 buffer.read(cx).file().map(|f| f.path().clone())
6442 });
6443 })?;
6444 if entries.is_empty() {
6445 return Ok(());
6446 }
6447
6448 // If the project transaction's edits are all contained within this editor, then
6449 // avoid opening a new editor to display them.
6450
6451 if let [(buffer, transaction)] = &*entries {
6452 let excerpt = editor.update(cx, |editor, cx| {
6453 editor
6454 .buffer()
6455 .read(cx)
6456 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6457 })?;
6458 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6459 && excerpted_buffer == *buffer
6460 {
6461 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6462 let excerpt_range = excerpt_range.to_offset(buffer);
6463 buffer
6464 .edited_ranges_for_transaction::<usize>(transaction)
6465 .all(|range| {
6466 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6467 })
6468 })?;
6469
6470 if all_edits_within_excerpt {
6471 return Ok(());
6472 }
6473 }
6474 }
6475
6476 let mut ranges_to_highlight = Vec::new();
6477 let excerpt_buffer = cx.new(|cx| {
6478 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6479 for (buffer_handle, transaction) in &entries {
6480 let edited_ranges = buffer_handle
6481 .read(cx)
6482 .edited_ranges_for_transaction::<Point>(transaction)
6483 .collect::<Vec<_>>();
6484 let (ranges, _) = multibuffer.set_excerpts_for_path(
6485 PathKey::for_buffer(buffer_handle, cx),
6486 buffer_handle.clone(),
6487 edited_ranges,
6488 multibuffer_context_lines(cx),
6489 cx,
6490 );
6491
6492 ranges_to_highlight.extend(ranges);
6493 }
6494 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6495 multibuffer
6496 })?;
6497
6498 workspace.update_in(cx, |workspace, window, cx| {
6499 let project = workspace.project().clone();
6500 let editor =
6501 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6502 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6503 editor.update(cx, |editor, cx| {
6504 editor.highlight_background::<Self>(
6505 &ranges_to_highlight,
6506 |theme| theme.colors().editor_highlighted_line_background,
6507 cx,
6508 );
6509 });
6510 })?;
6511
6512 Ok(())
6513 }
6514
6515 pub fn clear_code_action_providers(&mut self) {
6516 self.code_action_providers.clear();
6517 self.available_code_actions.take();
6518 }
6519
6520 pub fn add_code_action_provider(
6521 &mut self,
6522 provider: Rc<dyn CodeActionProvider>,
6523 window: &mut Window,
6524 cx: &mut Context<Self>,
6525 ) {
6526 if self
6527 .code_action_providers
6528 .iter()
6529 .any(|existing_provider| existing_provider.id() == provider.id())
6530 {
6531 return;
6532 }
6533
6534 self.code_action_providers.push(provider);
6535 self.refresh_code_actions(window, cx);
6536 }
6537
6538 pub fn remove_code_action_provider(
6539 &mut self,
6540 id: Arc<str>,
6541 window: &mut Window,
6542 cx: &mut Context<Self>,
6543 ) {
6544 self.code_action_providers
6545 .retain(|provider| provider.id() != id);
6546 self.refresh_code_actions(window, cx);
6547 }
6548
6549 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6550 !self.code_action_providers.is_empty()
6551 && EditorSettings::get_global(cx).toolbar.code_actions
6552 }
6553
6554 pub fn has_available_code_actions(&self) -> bool {
6555 self.available_code_actions
6556 .as_ref()
6557 .is_some_and(|(_, actions)| !actions.is_empty())
6558 }
6559
6560 fn render_inline_code_actions(
6561 &self,
6562 icon_size: ui::IconSize,
6563 display_row: DisplayRow,
6564 is_active: bool,
6565 cx: &mut Context<Self>,
6566 ) -> AnyElement {
6567 let show_tooltip = !self.context_menu_visible();
6568 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6569 .icon_size(icon_size)
6570 .shape(ui::IconButtonShape::Square)
6571 .icon_color(ui::Color::Hidden)
6572 .toggle_state(is_active)
6573 .when(show_tooltip, |this| {
6574 this.tooltip({
6575 let focus_handle = self.focus_handle.clone();
6576 move |window, cx| {
6577 Tooltip::for_action_in(
6578 "Toggle Code Actions",
6579 &ToggleCodeActions {
6580 deployed_from: None,
6581 quick_launch: false,
6582 },
6583 &focus_handle,
6584 window,
6585 cx,
6586 )
6587 }
6588 })
6589 })
6590 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6591 window.focus(&editor.focus_handle(cx));
6592 editor.toggle_code_actions(
6593 &crate::actions::ToggleCodeActions {
6594 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6595 display_row,
6596 )),
6597 quick_launch: false,
6598 },
6599 window,
6600 cx,
6601 );
6602 }))
6603 .into_any_element()
6604 }
6605
6606 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6607 &self.context_menu
6608 }
6609
6610 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6611 let newest_selection = self.selections.newest_anchor().clone();
6612 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6613 let buffer = self.buffer.read(cx);
6614 if newest_selection.head().diff_base_anchor.is_some() {
6615 return None;
6616 }
6617 let (start_buffer, start) =
6618 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6619 let (end_buffer, end) =
6620 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6621 if start_buffer != end_buffer {
6622 return None;
6623 }
6624
6625 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6626 cx.background_executor()
6627 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6628 .await;
6629
6630 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6631 let providers = this.code_action_providers.clone();
6632 let tasks = this
6633 .code_action_providers
6634 .iter()
6635 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6636 .collect::<Vec<_>>();
6637 (providers, tasks)
6638 })?;
6639
6640 let mut actions = Vec::new();
6641 for (provider, provider_actions) in
6642 providers.into_iter().zip(future::join_all(tasks).await)
6643 {
6644 if let Some(provider_actions) = provider_actions.log_err() {
6645 actions.extend(provider_actions.into_iter().map(|action| {
6646 AvailableCodeAction {
6647 excerpt_id: newest_selection.start.excerpt_id,
6648 action,
6649 provider: provider.clone(),
6650 }
6651 }));
6652 }
6653 }
6654
6655 this.update(cx, |this, cx| {
6656 this.available_code_actions = if actions.is_empty() {
6657 None
6658 } else {
6659 Some((
6660 Location {
6661 buffer: start_buffer,
6662 range: start..end,
6663 },
6664 actions.into(),
6665 ))
6666 };
6667 cx.notify();
6668 })
6669 }));
6670 None
6671 }
6672
6673 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6674 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6675 self.show_git_blame_inline = false;
6676
6677 self.show_git_blame_inline_delay_task =
6678 Some(cx.spawn_in(window, async move |this, cx| {
6679 cx.background_executor().timer(delay).await;
6680
6681 this.update(cx, |this, cx| {
6682 this.show_git_blame_inline = true;
6683 cx.notify();
6684 })
6685 .log_err();
6686 }));
6687 }
6688 }
6689
6690 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6691 let snapshot = self.snapshot(window, cx);
6692 let cursor = self.selections.newest::<Point>(cx).head();
6693 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6694 else {
6695 return;
6696 };
6697
6698 let Some(blame) = self.blame.as_ref() else {
6699 return;
6700 };
6701
6702 let row_info = RowInfo {
6703 buffer_id: Some(buffer.remote_id()),
6704 buffer_row: Some(point.row),
6705 ..Default::default()
6706 };
6707 let Some((buffer, blame_entry)) = blame
6708 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6709 .flatten()
6710 else {
6711 return;
6712 };
6713
6714 let anchor = self.selections.newest_anchor().head();
6715 let position = self.to_pixel_point(anchor, &snapshot, window);
6716 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6717 self.show_blame_popover(
6718 buffer,
6719 &blame_entry,
6720 position + last_bounds.origin,
6721 true,
6722 cx,
6723 );
6724 };
6725 }
6726
6727 fn show_blame_popover(
6728 &mut self,
6729 buffer: BufferId,
6730 blame_entry: &BlameEntry,
6731 position: gpui::Point<Pixels>,
6732 ignore_timeout: bool,
6733 cx: &mut Context<Self>,
6734 ) {
6735 if let Some(state) = &mut self.inline_blame_popover {
6736 state.hide_task.take();
6737 } else {
6738 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6739 let blame_entry = blame_entry.clone();
6740 let show_task = cx.spawn(async move |editor, cx| {
6741 if !ignore_timeout {
6742 cx.background_executor()
6743 .timer(std::time::Duration::from_millis(blame_popover_delay))
6744 .await;
6745 }
6746 editor
6747 .update(cx, |editor, cx| {
6748 editor.inline_blame_popover_show_task.take();
6749 let Some(blame) = editor.blame.as_ref() else {
6750 return;
6751 };
6752 let blame = blame.read(cx);
6753 let details = blame.details_for_entry(buffer, &blame_entry);
6754 let markdown = cx.new(|cx| {
6755 Markdown::new(
6756 details
6757 .as_ref()
6758 .map(|message| message.message.clone())
6759 .unwrap_or_default(),
6760 None,
6761 None,
6762 cx,
6763 )
6764 });
6765 editor.inline_blame_popover = Some(InlineBlamePopover {
6766 position,
6767 hide_task: None,
6768 popover_bounds: None,
6769 popover_state: InlineBlamePopoverState {
6770 scroll_handle: ScrollHandle::new(),
6771 commit_message: details,
6772 markdown,
6773 },
6774 keyboard_grace: ignore_timeout,
6775 });
6776 cx.notify();
6777 })
6778 .ok();
6779 });
6780 self.inline_blame_popover_show_task = Some(show_task);
6781 }
6782 }
6783
6784 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6785 self.inline_blame_popover_show_task.take();
6786 if let Some(state) = &mut self.inline_blame_popover {
6787 let hide_task = cx.spawn(async move |editor, cx| {
6788 cx.background_executor()
6789 .timer(std::time::Duration::from_millis(100))
6790 .await;
6791 editor
6792 .update(cx, |editor, cx| {
6793 editor.inline_blame_popover.take();
6794 cx.notify();
6795 })
6796 .ok();
6797 });
6798 state.hide_task = Some(hide_task);
6799 }
6800 }
6801
6802 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6803 if self.pending_rename.is_some() {
6804 return None;
6805 }
6806
6807 let provider = self.semantics_provider.clone()?;
6808 let buffer = self.buffer.read(cx);
6809 let newest_selection = self.selections.newest_anchor().clone();
6810 let cursor_position = newest_selection.head();
6811 let (cursor_buffer, cursor_buffer_position) =
6812 buffer.text_anchor_for_position(cursor_position, cx)?;
6813 let (tail_buffer, tail_buffer_position) =
6814 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6815 if cursor_buffer != tail_buffer {
6816 return None;
6817 }
6818
6819 let snapshot = cursor_buffer.read(cx).snapshot();
6820 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6821 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6822 if start_word_range != end_word_range {
6823 self.document_highlights_task.take();
6824 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6825 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6826 return None;
6827 }
6828
6829 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6830 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6831 cx.background_executor()
6832 .timer(Duration::from_millis(debounce))
6833 .await;
6834
6835 let highlights = if let Some(highlights) = cx
6836 .update(|cx| {
6837 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6838 })
6839 .ok()
6840 .flatten()
6841 {
6842 highlights.await.log_err()
6843 } else {
6844 None
6845 };
6846
6847 if let Some(highlights) = highlights {
6848 this.update(cx, |this, cx| {
6849 if this.pending_rename.is_some() {
6850 return;
6851 }
6852
6853 let buffer = this.buffer.read(cx);
6854 if buffer
6855 .text_anchor_for_position(cursor_position, cx)
6856 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6857 {
6858 return;
6859 }
6860
6861 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6862 let mut write_ranges = Vec::new();
6863 let mut read_ranges = Vec::new();
6864 for highlight in highlights {
6865 let buffer_id = cursor_buffer.read(cx).remote_id();
6866 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6867 {
6868 let start = highlight
6869 .range
6870 .start
6871 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6872 let end = highlight
6873 .range
6874 .end
6875 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6876 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6877 continue;
6878 }
6879
6880 let range = Anchor::range_in_buffer(excerpt_id, buffer_id, start..end);
6881 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6882 write_ranges.push(range);
6883 } else {
6884 read_ranges.push(range);
6885 }
6886 }
6887 }
6888
6889 this.highlight_background::<DocumentHighlightRead>(
6890 &read_ranges,
6891 |theme| theme.colors().editor_document_highlight_read_background,
6892 cx,
6893 );
6894 this.highlight_background::<DocumentHighlightWrite>(
6895 &write_ranges,
6896 |theme| theme.colors().editor_document_highlight_write_background,
6897 cx,
6898 );
6899 cx.notify();
6900 })
6901 .log_err();
6902 }
6903 }));
6904 None
6905 }
6906
6907 fn prepare_highlight_query_from_selection(
6908 &mut self,
6909 cx: &mut Context<Editor>,
6910 ) -> Option<(String, Range<Anchor>)> {
6911 if matches!(self.mode, EditorMode::SingleLine) {
6912 return None;
6913 }
6914 if !EditorSettings::get_global(cx).selection_highlight {
6915 return None;
6916 }
6917 if self.selections.count() != 1 || self.selections.line_mode() {
6918 return None;
6919 }
6920 let selection = self.selections.newest::<Point>(cx);
6921 if selection.is_empty() || selection.start.row != selection.end.row {
6922 return None;
6923 }
6924 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6925 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6926 let query = multi_buffer_snapshot
6927 .text_for_range(selection_anchor_range.clone())
6928 .collect::<String>();
6929 if query.trim().is_empty() {
6930 return None;
6931 }
6932 Some((query, selection_anchor_range))
6933 }
6934
6935 fn update_selection_occurrence_highlights(
6936 &mut self,
6937 query_text: String,
6938 query_range: Range<Anchor>,
6939 multi_buffer_range_to_query: Range<Point>,
6940 use_debounce: bool,
6941 window: &mut Window,
6942 cx: &mut Context<Editor>,
6943 ) -> Task<()> {
6944 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6945 cx.spawn_in(window, async move |editor, cx| {
6946 if use_debounce {
6947 cx.background_executor()
6948 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6949 .await;
6950 }
6951 let match_task = cx.background_spawn(async move {
6952 let buffer_ranges = multi_buffer_snapshot
6953 .range_to_buffer_ranges(multi_buffer_range_to_query)
6954 .into_iter()
6955 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6956 let mut match_ranges = Vec::new();
6957 let Ok(regex) = project::search::SearchQuery::text(
6958 query_text.clone(),
6959 false,
6960 false,
6961 false,
6962 Default::default(),
6963 Default::default(),
6964 false,
6965 None,
6966 ) else {
6967 return Vec::default();
6968 };
6969 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6970 match_ranges.extend(
6971 regex
6972 .search(buffer_snapshot, Some(search_range.clone()))
6973 .await
6974 .into_iter()
6975 .filter_map(|match_range| {
6976 let match_start = buffer_snapshot
6977 .anchor_after(search_range.start + match_range.start);
6978 let match_end = buffer_snapshot
6979 .anchor_before(search_range.start + match_range.end);
6980 let match_anchor_range = Anchor::range_in_buffer(
6981 excerpt_id,
6982 buffer_snapshot.remote_id(),
6983 match_start..match_end,
6984 );
6985 (match_anchor_range != query_range).then_some(match_anchor_range)
6986 }),
6987 );
6988 }
6989 match_ranges
6990 });
6991 let match_ranges = match_task.await;
6992 editor
6993 .update_in(cx, |editor, _, cx| {
6994 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6995 if !match_ranges.is_empty() {
6996 editor.highlight_background::<SelectedTextHighlight>(
6997 &match_ranges,
6998 |theme| theme.colors().editor_document_highlight_bracket_background,
6999 cx,
7000 )
7001 }
7002 })
7003 .log_err();
7004 })
7005 }
7006
7007 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7008 struct NewlineFold;
7009 let type_id = std::any::TypeId::of::<NewlineFold>();
7010 if !self.mode.is_single_line() {
7011 return;
7012 }
7013 let snapshot = self.snapshot(window, cx);
7014 if snapshot.buffer_snapshot.max_point().row == 0 {
7015 return;
7016 }
7017 let task = cx.background_spawn(async move {
7018 let new_newlines = snapshot
7019 .buffer_chars_at(0)
7020 .filter_map(|(c, i)| {
7021 if c == '\n' {
7022 Some(
7023 snapshot.buffer_snapshot.anchor_after(i)
7024 ..snapshot.buffer_snapshot.anchor_before(i + 1),
7025 )
7026 } else {
7027 None
7028 }
7029 })
7030 .collect::<Vec<_>>();
7031 let existing_newlines = snapshot
7032 .folds_in_range(0..snapshot.buffer_snapshot.len())
7033 .filter_map(|fold| {
7034 if fold.placeholder.type_tag == Some(type_id) {
7035 Some(fold.range.start..fold.range.end)
7036 } else {
7037 None
7038 }
7039 })
7040 .collect::<Vec<_>>();
7041
7042 (new_newlines, existing_newlines)
7043 });
7044 self.folding_newlines = cx.spawn(async move |this, cx| {
7045 let (new_newlines, existing_newlines) = task.await;
7046 if new_newlines == existing_newlines {
7047 return;
7048 }
7049 let placeholder = FoldPlaceholder {
7050 render: Arc::new(move |_, _, cx| {
7051 div()
7052 .bg(cx.theme().status().hint_background)
7053 .border_b_1()
7054 .size_full()
7055 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7056 .border_color(cx.theme().status().hint)
7057 .child("\\n")
7058 .into_any()
7059 }),
7060 constrain_width: false,
7061 merge_adjacent: false,
7062 type_tag: Some(type_id),
7063 };
7064 let creases = new_newlines
7065 .into_iter()
7066 .map(|range| Crease::simple(range, placeholder.clone()))
7067 .collect();
7068 this.update(cx, |this, cx| {
7069 this.display_map.update(cx, |display_map, cx| {
7070 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7071 display_map.fold(creases, cx);
7072 });
7073 })
7074 .ok();
7075 });
7076 }
7077
7078 fn refresh_selected_text_highlights(
7079 &mut self,
7080 on_buffer_edit: bool,
7081 window: &mut Window,
7082 cx: &mut Context<Editor>,
7083 ) {
7084 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7085 else {
7086 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7087 self.quick_selection_highlight_task.take();
7088 self.debounced_selection_highlight_task.take();
7089 return;
7090 };
7091 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7092 if on_buffer_edit
7093 || self
7094 .quick_selection_highlight_task
7095 .as_ref()
7096 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7097 {
7098 let multi_buffer_visible_start = self
7099 .scroll_manager
7100 .anchor()
7101 .anchor
7102 .to_point(&multi_buffer_snapshot);
7103 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7104 multi_buffer_visible_start
7105 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7106 Bias::Left,
7107 );
7108 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7109 self.quick_selection_highlight_task = Some((
7110 query_range.clone(),
7111 self.update_selection_occurrence_highlights(
7112 query_text.clone(),
7113 query_range.clone(),
7114 multi_buffer_visible_range,
7115 false,
7116 window,
7117 cx,
7118 ),
7119 ));
7120 }
7121 if on_buffer_edit
7122 || self
7123 .debounced_selection_highlight_task
7124 .as_ref()
7125 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7126 {
7127 let multi_buffer_start = multi_buffer_snapshot
7128 .anchor_before(0)
7129 .to_point(&multi_buffer_snapshot);
7130 let multi_buffer_end = multi_buffer_snapshot
7131 .anchor_after(multi_buffer_snapshot.len())
7132 .to_point(&multi_buffer_snapshot);
7133 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7134 self.debounced_selection_highlight_task = Some((
7135 query_range.clone(),
7136 self.update_selection_occurrence_highlights(
7137 query_text,
7138 query_range,
7139 multi_buffer_full_range,
7140 true,
7141 window,
7142 cx,
7143 ),
7144 ));
7145 }
7146 }
7147
7148 pub fn refresh_edit_prediction(
7149 &mut self,
7150 debounce: bool,
7151 user_requested: bool,
7152 window: &mut Window,
7153 cx: &mut Context<Self>,
7154 ) -> Option<()> {
7155 if DisableAiSettings::get_global(cx).disable_ai {
7156 return None;
7157 }
7158
7159 let provider = self.edit_prediction_provider()?;
7160 let cursor = self.selections.newest_anchor().head();
7161 let (buffer, cursor_buffer_position) =
7162 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7163
7164 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7165 self.discard_edit_prediction(false, cx);
7166 return None;
7167 }
7168
7169 self.update_visible_edit_prediction(window, cx);
7170
7171 if !user_requested
7172 && (!self.should_show_edit_predictions()
7173 || !self.is_focused(window)
7174 || buffer.read(cx).is_empty())
7175 {
7176 self.discard_edit_prediction(false, cx);
7177 return None;
7178 }
7179
7180 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7181 Some(())
7182 }
7183
7184 fn show_edit_predictions_in_menu(&self) -> bool {
7185 match self.edit_prediction_settings {
7186 EditPredictionSettings::Disabled => false,
7187 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7188 }
7189 }
7190
7191 pub fn edit_predictions_enabled(&self) -> bool {
7192 match self.edit_prediction_settings {
7193 EditPredictionSettings::Disabled => false,
7194 EditPredictionSettings::Enabled { .. } => true,
7195 }
7196 }
7197
7198 fn edit_prediction_requires_modifier(&self) -> bool {
7199 match self.edit_prediction_settings {
7200 EditPredictionSettings::Disabled => false,
7201 EditPredictionSettings::Enabled {
7202 preview_requires_modifier,
7203 ..
7204 } => preview_requires_modifier,
7205 }
7206 }
7207
7208 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7209 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7210 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7211 self.discard_edit_prediction(false, cx);
7212 } else {
7213 let selection = self.selections.newest_anchor();
7214 let cursor = selection.head();
7215
7216 if let Some((buffer, cursor_buffer_position)) =
7217 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7218 {
7219 self.edit_prediction_settings =
7220 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7221 }
7222 }
7223 }
7224
7225 fn edit_prediction_settings_at_position(
7226 &self,
7227 buffer: &Entity<Buffer>,
7228 buffer_position: language::Anchor,
7229 cx: &App,
7230 ) -> EditPredictionSettings {
7231 if !self.mode.is_full()
7232 || !self.show_edit_predictions_override.unwrap_or(true)
7233 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7234 {
7235 return EditPredictionSettings::Disabled;
7236 }
7237
7238 let buffer = buffer.read(cx);
7239
7240 let file = buffer.file();
7241
7242 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7243 return EditPredictionSettings::Disabled;
7244 };
7245
7246 let by_provider = matches!(
7247 self.menu_edit_predictions_policy,
7248 MenuEditPredictionsPolicy::ByProvider
7249 );
7250
7251 let show_in_menu = by_provider
7252 && self
7253 .edit_prediction_provider
7254 .as_ref()
7255 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7256
7257 let preview_requires_modifier =
7258 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7259
7260 EditPredictionSettings::Enabled {
7261 show_in_menu,
7262 preview_requires_modifier,
7263 }
7264 }
7265
7266 fn should_show_edit_predictions(&self) -> bool {
7267 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7268 }
7269
7270 pub fn edit_prediction_preview_is_active(&self) -> bool {
7271 matches!(
7272 self.edit_prediction_preview,
7273 EditPredictionPreview::Active { .. }
7274 )
7275 }
7276
7277 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7278 let cursor = self.selections.newest_anchor().head();
7279 if let Some((buffer, cursor_position)) =
7280 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7281 {
7282 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7283 } else {
7284 false
7285 }
7286 }
7287
7288 pub fn supports_minimap(&self, cx: &App) -> bool {
7289 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7290 }
7291
7292 fn edit_predictions_enabled_in_buffer(
7293 &self,
7294 buffer: &Entity<Buffer>,
7295 buffer_position: language::Anchor,
7296 cx: &App,
7297 ) -> bool {
7298 maybe!({
7299 if self.read_only(cx) {
7300 return Some(false);
7301 }
7302 let provider = self.edit_prediction_provider()?;
7303 if !provider.is_enabled(buffer, buffer_position, cx) {
7304 return Some(false);
7305 }
7306 let buffer = buffer.read(cx);
7307 let Some(file) = buffer.file() else {
7308 return Some(true);
7309 };
7310 let settings = all_language_settings(Some(file), cx);
7311 Some(settings.edit_predictions_enabled_for_file(file, cx))
7312 })
7313 .unwrap_or(false)
7314 }
7315
7316 fn cycle_edit_prediction(
7317 &mut self,
7318 direction: Direction,
7319 window: &mut Window,
7320 cx: &mut Context<Self>,
7321 ) -> Option<()> {
7322 let provider = self.edit_prediction_provider()?;
7323 let cursor = self.selections.newest_anchor().head();
7324 let (buffer, cursor_buffer_position) =
7325 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7326 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7327 return None;
7328 }
7329
7330 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7331 self.update_visible_edit_prediction(window, cx);
7332
7333 Some(())
7334 }
7335
7336 pub fn show_edit_prediction(
7337 &mut self,
7338 _: &ShowEditPrediction,
7339 window: &mut Window,
7340 cx: &mut Context<Self>,
7341 ) {
7342 if !self.has_active_edit_prediction() {
7343 self.refresh_edit_prediction(false, true, window, cx);
7344 return;
7345 }
7346
7347 self.update_visible_edit_prediction(window, cx);
7348 }
7349
7350 pub fn display_cursor_names(
7351 &mut self,
7352 _: &DisplayCursorNames,
7353 window: &mut Window,
7354 cx: &mut Context<Self>,
7355 ) {
7356 self.show_cursor_names(window, cx);
7357 }
7358
7359 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7360 self.show_cursor_names = true;
7361 cx.notify();
7362 cx.spawn_in(window, async move |this, cx| {
7363 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7364 this.update(cx, |this, cx| {
7365 this.show_cursor_names = false;
7366 cx.notify()
7367 })
7368 .ok()
7369 })
7370 .detach();
7371 }
7372
7373 pub fn next_edit_prediction(
7374 &mut self,
7375 _: &NextEditPrediction,
7376 window: &mut Window,
7377 cx: &mut Context<Self>,
7378 ) {
7379 if self.has_active_edit_prediction() {
7380 self.cycle_edit_prediction(Direction::Next, window, cx);
7381 } else {
7382 let is_copilot_disabled = self
7383 .refresh_edit_prediction(false, true, window, cx)
7384 .is_none();
7385 if is_copilot_disabled {
7386 cx.propagate();
7387 }
7388 }
7389 }
7390
7391 pub fn previous_edit_prediction(
7392 &mut self,
7393 _: &PreviousEditPrediction,
7394 window: &mut Window,
7395 cx: &mut Context<Self>,
7396 ) {
7397 if self.has_active_edit_prediction() {
7398 self.cycle_edit_prediction(Direction::Prev, window, cx);
7399 } else {
7400 let is_copilot_disabled = self
7401 .refresh_edit_prediction(false, true, window, cx)
7402 .is_none();
7403 if is_copilot_disabled {
7404 cx.propagate();
7405 }
7406 }
7407 }
7408
7409 pub fn accept_edit_prediction(
7410 &mut self,
7411 _: &AcceptEditPrediction,
7412 window: &mut Window,
7413 cx: &mut Context<Self>,
7414 ) {
7415 if self.show_edit_predictions_in_menu() {
7416 self.hide_context_menu(window, cx);
7417 }
7418
7419 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7420 return;
7421 };
7422
7423 match &active_edit_prediction.completion {
7424 EditPrediction::MoveWithin { target, .. } => {
7425 let target = *target;
7426
7427 if let Some(position_map) = &self.last_position_map {
7428 if position_map
7429 .visible_row_range
7430 .contains(&target.to_display_point(&position_map.snapshot).row())
7431 || !self.edit_prediction_requires_modifier()
7432 {
7433 self.unfold_ranges(&[target..target], true, false, cx);
7434 // Note that this is also done in vim's handler of the Tab action.
7435 self.change_selections(
7436 SelectionEffects::scroll(Autoscroll::newest()),
7437 window,
7438 cx,
7439 |selections| {
7440 selections.select_anchor_ranges([target..target]);
7441 },
7442 );
7443 self.clear_row_highlights::<EditPredictionPreview>();
7444
7445 self.edit_prediction_preview
7446 .set_previous_scroll_position(None);
7447 } else {
7448 self.edit_prediction_preview
7449 .set_previous_scroll_position(Some(
7450 position_map.snapshot.scroll_anchor,
7451 ));
7452
7453 self.highlight_rows::<EditPredictionPreview>(
7454 target..target,
7455 cx.theme().colors().editor_highlighted_line_background,
7456 RowHighlightOptions {
7457 autoscroll: true,
7458 ..Default::default()
7459 },
7460 cx,
7461 );
7462 self.request_autoscroll(Autoscroll::fit(), cx);
7463 }
7464 }
7465 }
7466 EditPrediction::MoveOutside { snapshot, target } => {
7467 if let Some(workspace) = self.workspace() {
7468 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7469 .detach_and_log_err(cx);
7470 }
7471 }
7472 EditPrediction::Edit { edits, .. } => {
7473 self.report_edit_prediction_event(
7474 active_edit_prediction.completion_id.clone(),
7475 true,
7476 cx,
7477 );
7478
7479 if let Some(provider) = self.edit_prediction_provider() {
7480 provider.accept(cx);
7481 }
7482
7483 // Store the transaction ID and selections before applying the edit
7484 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7485
7486 let snapshot = self.buffer.read(cx).snapshot(cx);
7487 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7488
7489 self.buffer.update(cx, |buffer, cx| {
7490 buffer.edit(edits.iter().cloned(), None, cx)
7491 });
7492
7493 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7494 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7495 });
7496
7497 let selections = self.selections.disjoint_anchors_arc();
7498 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7499 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7500 if has_new_transaction {
7501 self.selection_history
7502 .insert_transaction(transaction_id_now, selections);
7503 }
7504 }
7505
7506 self.update_visible_edit_prediction(window, cx);
7507 if self.active_edit_prediction.is_none() {
7508 self.refresh_edit_prediction(true, true, window, cx);
7509 }
7510
7511 cx.notify();
7512 }
7513 }
7514
7515 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7516 }
7517
7518 pub fn accept_partial_edit_prediction(
7519 &mut self,
7520 _: &AcceptPartialEditPrediction,
7521 window: &mut Window,
7522 cx: &mut Context<Self>,
7523 ) {
7524 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7525 return;
7526 };
7527 if self.selections.count() != 1 {
7528 return;
7529 }
7530
7531 match &active_edit_prediction.completion {
7532 EditPrediction::MoveWithin { target, .. } => {
7533 let target = *target;
7534 self.change_selections(
7535 SelectionEffects::scroll(Autoscroll::newest()),
7536 window,
7537 cx,
7538 |selections| {
7539 selections.select_anchor_ranges([target..target]);
7540 },
7541 );
7542 }
7543 EditPrediction::MoveOutside { snapshot, target } => {
7544 if let Some(workspace) = self.workspace() {
7545 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7546 .detach_and_log_err(cx);
7547 }
7548 }
7549 EditPrediction::Edit { edits, .. } => {
7550 self.report_edit_prediction_event(
7551 active_edit_prediction.completion_id.clone(),
7552 true,
7553 cx,
7554 );
7555
7556 // Find an insertion that starts at the cursor position.
7557 let snapshot = self.buffer.read(cx).snapshot(cx);
7558 let cursor_offset = self.selections.newest::<usize>(cx).head();
7559 let insertion = edits.iter().find_map(|(range, text)| {
7560 let range = range.to_offset(&snapshot);
7561 if range.is_empty() && range.start == cursor_offset {
7562 Some(text)
7563 } else {
7564 None
7565 }
7566 });
7567
7568 if let Some(text) = insertion {
7569 let mut partial_completion = text
7570 .chars()
7571 .by_ref()
7572 .take_while(|c| c.is_alphabetic())
7573 .collect::<String>();
7574 if partial_completion.is_empty() {
7575 partial_completion = text
7576 .chars()
7577 .by_ref()
7578 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7579 .collect::<String>();
7580 }
7581
7582 cx.emit(EditorEvent::InputHandled {
7583 utf16_range_to_replace: None,
7584 text: partial_completion.clone().into(),
7585 });
7586
7587 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7588
7589 self.refresh_edit_prediction(true, true, window, cx);
7590 cx.notify();
7591 } else {
7592 self.accept_edit_prediction(&Default::default(), window, cx);
7593 }
7594 }
7595 }
7596 }
7597
7598 fn discard_edit_prediction(
7599 &mut self,
7600 should_report_edit_prediction_event: bool,
7601 cx: &mut Context<Self>,
7602 ) -> bool {
7603 if should_report_edit_prediction_event {
7604 let completion_id = self
7605 .active_edit_prediction
7606 .as_ref()
7607 .and_then(|active_completion| active_completion.completion_id.clone());
7608
7609 self.report_edit_prediction_event(completion_id, false, cx);
7610 }
7611
7612 if let Some(provider) = self.edit_prediction_provider() {
7613 provider.discard(cx);
7614 }
7615
7616 self.take_active_edit_prediction(cx)
7617 }
7618
7619 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7620 let Some(provider) = self.edit_prediction_provider() else {
7621 return;
7622 };
7623
7624 let Some((_, buffer, _)) = self
7625 .buffer
7626 .read(cx)
7627 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7628 else {
7629 return;
7630 };
7631
7632 let extension = buffer
7633 .read(cx)
7634 .file()
7635 .and_then(|file| Some(file.path().extension()?.to_string()));
7636
7637 let event_type = match accepted {
7638 true => "Edit Prediction Accepted",
7639 false => "Edit Prediction Discarded",
7640 };
7641 telemetry::event!(
7642 event_type,
7643 provider = provider.name(),
7644 prediction_id = id,
7645 suggestion_accepted = accepted,
7646 file_extension = extension,
7647 );
7648 }
7649
7650 fn open_editor_at_anchor(
7651 snapshot: &language::BufferSnapshot,
7652 target: language::Anchor,
7653 workspace: &Entity<Workspace>,
7654 window: &mut Window,
7655 cx: &mut App,
7656 ) -> Task<Result<()>> {
7657 workspace.update(cx, |workspace, cx| {
7658 let path = snapshot.file().map(|file| file.full_path(cx));
7659 let Some(path) =
7660 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7661 else {
7662 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7663 };
7664 let target = text::ToPoint::to_point(&target, snapshot);
7665 let item = workspace.open_path(path, None, true, window, cx);
7666 window.spawn(cx, async move |cx| {
7667 let Some(editor) = item.await?.downcast::<Editor>() else {
7668 return Ok(());
7669 };
7670 editor
7671 .update_in(cx, |editor, window, cx| {
7672 editor.go_to_singleton_buffer_point(target, window, cx);
7673 })
7674 .ok();
7675 anyhow::Ok(())
7676 })
7677 })
7678 }
7679
7680 pub fn has_active_edit_prediction(&self) -> bool {
7681 self.active_edit_prediction.is_some()
7682 }
7683
7684 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7685 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7686 return false;
7687 };
7688
7689 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7690 self.clear_highlights::<EditPredictionHighlight>(cx);
7691 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7692 true
7693 }
7694
7695 /// Returns true when we're displaying the edit prediction popover below the cursor
7696 /// like we are not previewing and the LSP autocomplete menu is visible
7697 /// or we are in `when_holding_modifier` mode.
7698 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7699 if self.edit_prediction_preview_is_active()
7700 || !self.show_edit_predictions_in_menu()
7701 || !self.edit_predictions_enabled()
7702 {
7703 return false;
7704 }
7705
7706 if self.has_visible_completions_menu() {
7707 return true;
7708 }
7709
7710 has_completion && self.edit_prediction_requires_modifier()
7711 }
7712
7713 fn handle_modifiers_changed(
7714 &mut self,
7715 modifiers: Modifiers,
7716 position_map: &PositionMap,
7717 window: &mut Window,
7718 cx: &mut Context<Self>,
7719 ) {
7720 if self.show_edit_predictions_in_menu() {
7721 self.update_edit_prediction_preview(&modifiers, window, cx);
7722 }
7723
7724 self.update_selection_mode(&modifiers, position_map, window, cx);
7725
7726 let mouse_position = window.mouse_position();
7727 if !position_map.text_hitbox.is_hovered(window) {
7728 return;
7729 }
7730
7731 self.update_hovered_link(
7732 position_map.point_for_position(mouse_position),
7733 &position_map.snapshot,
7734 modifiers,
7735 window,
7736 cx,
7737 )
7738 }
7739
7740 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7741 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7742 if invert {
7743 match multi_cursor_setting {
7744 MultiCursorModifier::Alt => modifiers.alt,
7745 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7746 }
7747 } else {
7748 match multi_cursor_setting {
7749 MultiCursorModifier::Alt => modifiers.secondary(),
7750 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7751 }
7752 }
7753 }
7754
7755 fn columnar_selection_mode(
7756 modifiers: &Modifiers,
7757 cx: &mut Context<Self>,
7758 ) -> Option<ColumnarMode> {
7759 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7760 if Self::multi_cursor_modifier(false, modifiers, cx) {
7761 Some(ColumnarMode::FromMouse)
7762 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7763 Some(ColumnarMode::FromSelection)
7764 } else {
7765 None
7766 }
7767 } else {
7768 None
7769 }
7770 }
7771
7772 fn update_selection_mode(
7773 &mut self,
7774 modifiers: &Modifiers,
7775 position_map: &PositionMap,
7776 window: &mut Window,
7777 cx: &mut Context<Self>,
7778 ) {
7779 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7780 return;
7781 };
7782 if self.selections.pending_anchor().is_none() {
7783 return;
7784 }
7785
7786 let mouse_position = window.mouse_position();
7787 let point_for_position = position_map.point_for_position(mouse_position);
7788 let position = point_for_position.previous_valid;
7789
7790 self.select(
7791 SelectPhase::BeginColumnar {
7792 position,
7793 reset: false,
7794 mode,
7795 goal_column: point_for_position.exact_unclipped.column(),
7796 },
7797 window,
7798 cx,
7799 );
7800 }
7801
7802 fn update_edit_prediction_preview(
7803 &mut self,
7804 modifiers: &Modifiers,
7805 window: &mut Window,
7806 cx: &mut Context<Self>,
7807 ) {
7808 let mut modifiers_held = false;
7809 if let Some(accept_keystroke) = self
7810 .accept_edit_prediction_keybind(false, window, cx)
7811 .keystroke()
7812 {
7813 modifiers_held = modifiers_held
7814 || (accept_keystroke.modifiers() == modifiers
7815 && accept_keystroke.modifiers().modified());
7816 };
7817 if let Some(accept_partial_keystroke) = self
7818 .accept_edit_prediction_keybind(true, window, cx)
7819 .keystroke()
7820 {
7821 modifiers_held = modifiers_held
7822 || (accept_partial_keystroke.modifiers() == modifiers
7823 && accept_partial_keystroke.modifiers().modified());
7824 }
7825
7826 if modifiers_held {
7827 if matches!(
7828 self.edit_prediction_preview,
7829 EditPredictionPreview::Inactive { .. }
7830 ) {
7831 self.edit_prediction_preview = EditPredictionPreview::Active {
7832 previous_scroll_position: None,
7833 since: Instant::now(),
7834 };
7835
7836 self.update_visible_edit_prediction(window, cx);
7837 cx.notify();
7838 }
7839 } else if let EditPredictionPreview::Active {
7840 previous_scroll_position,
7841 since,
7842 } = self.edit_prediction_preview
7843 {
7844 if let (Some(previous_scroll_position), Some(position_map)) =
7845 (previous_scroll_position, self.last_position_map.as_ref())
7846 {
7847 self.set_scroll_position(
7848 previous_scroll_position
7849 .scroll_position(&position_map.snapshot.display_snapshot),
7850 window,
7851 cx,
7852 );
7853 }
7854
7855 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7856 released_too_fast: since.elapsed() < Duration::from_millis(200),
7857 };
7858 self.clear_row_highlights::<EditPredictionPreview>();
7859 self.update_visible_edit_prediction(window, cx);
7860 cx.notify();
7861 }
7862 }
7863
7864 fn update_visible_edit_prediction(
7865 &mut self,
7866 _window: &mut Window,
7867 cx: &mut Context<Self>,
7868 ) -> Option<()> {
7869 if DisableAiSettings::get_global(cx).disable_ai {
7870 return None;
7871 }
7872
7873 if self.ime_transaction.is_some() {
7874 self.discard_edit_prediction(false, cx);
7875 return None;
7876 }
7877
7878 let selection = self.selections.newest_anchor();
7879 let cursor = selection.head();
7880 let multibuffer = self.buffer.read(cx).snapshot(cx);
7881 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7882 let excerpt_id = cursor.excerpt_id;
7883
7884 let show_in_menu = self.show_edit_predictions_in_menu();
7885 let completions_menu_has_precedence = !show_in_menu
7886 && (self.context_menu.borrow().is_some()
7887 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7888
7889 if completions_menu_has_precedence
7890 || !offset_selection.is_empty()
7891 || self
7892 .active_edit_prediction
7893 .as_ref()
7894 .is_some_and(|completion| {
7895 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7896 return false;
7897 };
7898 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7899 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7900 !invalidation_range.contains(&offset_selection.head())
7901 })
7902 {
7903 self.discard_edit_prediction(false, cx);
7904 return None;
7905 }
7906
7907 self.take_active_edit_prediction(cx);
7908 let Some(provider) = self.edit_prediction_provider() else {
7909 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7910 return None;
7911 };
7912
7913 let (buffer, cursor_buffer_position) =
7914 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7915
7916 self.edit_prediction_settings =
7917 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7918
7919 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7920
7921 if self.edit_prediction_indent_conflict {
7922 let cursor_point = cursor.to_point(&multibuffer);
7923
7924 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7925
7926 if let Some((_, indent)) = indents.iter().next()
7927 && indent.len == cursor_point.column
7928 {
7929 self.edit_prediction_indent_conflict = false;
7930 }
7931 }
7932
7933 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7934
7935 let (completion_id, edits, edit_preview) = match edit_prediction {
7936 edit_prediction::EditPrediction::Local {
7937 id,
7938 edits,
7939 edit_preview,
7940 } => (id, edits, edit_preview),
7941 edit_prediction::EditPrediction::Jump {
7942 id,
7943 snapshot,
7944 target,
7945 } => {
7946 self.stale_edit_prediction_in_menu = None;
7947 self.active_edit_prediction = Some(EditPredictionState {
7948 inlay_ids: vec![],
7949 completion: EditPrediction::MoveOutside { snapshot, target },
7950 completion_id: id,
7951 invalidation_range: None,
7952 });
7953 cx.notify();
7954 return Some(());
7955 }
7956 };
7957
7958 let edits = edits
7959 .into_iter()
7960 .flat_map(|(range, new_text)| {
7961 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7962 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7963 Some((start..end, new_text))
7964 })
7965 .collect::<Vec<_>>();
7966 if edits.is_empty() {
7967 return None;
7968 }
7969
7970 let first_edit_start = edits.first().unwrap().0.start;
7971 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7972 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7973
7974 let last_edit_end = edits.last().unwrap().0.end;
7975 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7976 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7977
7978 let cursor_row = cursor.to_point(&multibuffer).row;
7979
7980 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7981
7982 let mut inlay_ids = Vec::new();
7983 let invalidation_row_range;
7984 let move_invalidation_row_range = if cursor_row < edit_start_row {
7985 Some(cursor_row..edit_end_row)
7986 } else if cursor_row > edit_end_row {
7987 Some(edit_start_row..cursor_row)
7988 } else {
7989 None
7990 };
7991 let supports_jump = self
7992 .edit_prediction_provider
7993 .as_ref()
7994 .map(|provider| provider.provider.supports_jump_to_edit())
7995 .unwrap_or(true);
7996
7997 let is_move = supports_jump
7998 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7999 let completion = if is_move {
8000 invalidation_row_range =
8001 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8002 let target = first_edit_start;
8003 EditPrediction::MoveWithin { target, snapshot }
8004 } else {
8005 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8006 && !self.edit_predictions_hidden_for_vim_mode;
8007
8008 if show_completions_in_buffer {
8009 if edits
8010 .iter()
8011 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8012 {
8013 let mut inlays = Vec::new();
8014 for (range, new_text) in &edits {
8015 let inlay = Inlay::edit_prediction(
8016 post_inc(&mut self.next_inlay_id),
8017 range.start,
8018 new_text.as_str(),
8019 );
8020 inlay_ids.push(inlay.id);
8021 inlays.push(inlay);
8022 }
8023
8024 self.splice_inlays(&[], inlays, cx);
8025 } else {
8026 let background_color = cx.theme().status().deleted_background;
8027 self.highlight_text::<EditPredictionHighlight>(
8028 edits.iter().map(|(range, _)| range.clone()).collect(),
8029 HighlightStyle {
8030 background_color: Some(background_color),
8031 ..Default::default()
8032 },
8033 cx,
8034 );
8035 }
8036 }
8037
8038 invalidation_row_range = edit_start_row..edit_end_row;
8039
8040 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8041 if provider.show_tab_accept_marker() {
8042 EditDisplayMode::TabAccept
8043 } else {
8044 EditDisplayMode::Inline
8045 }
8046 } else {
8047 EditDisplayMode::DiffPopover
8048 };
8049
8050 EditPrediction::Edit {
8051 edits,
8052 edit_preview,
8053 display_mode,
8054 snapshot,
8055 }
8056 };
8057
8058 let invalidation_range = multibuffer
8059 .anchor_before(Point::new(invalidation_row_range.start, 0))
8060 ..multibuffer.anchor_after(Point::new(
8061 invalidation_row_range.end,
8062 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8063 ));
8064
8065 self.stale_edit_prediction_in_menu = None;
8066 self.active_edit_prediction = Some(EditPredictionState {
8067 inlay_ids,
8068 completion,
8069 completion_id,
8070 invalidation_range: Some(invalidation_range),
8071 });
8072
8073 cx.notify();
8074
8075 Some(())
8076 }
8077
8078 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8079 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8080 }
8081
8082 fn clear_tasks(&mut self) {
8083 self.tasks.clear()
8084 }
8085
8086 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8087 if self.tasks.insert(key, value).is_some() {
8088 // This case should hopefully be rare, but just in case...
8089 log::error!(
8090 "multiple different run targets found on a single line, only the last target will be rendered"
8091 )
8092 }
8093 }
8094
8095 /// Get all display points of breakpoints that will be rendered within editor
8096 ///
8097 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8098 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8099 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8100 fn active_breakpoints(
8101 &self,
8102 range: Range<DisplayRow>,
8103 window: &mut Window,
8104 cx: &mut Context<Self>,
8105 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8106 let mut breakpoint_display_points = HashMap::default();
8107
8108 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8109 return breakpoint_display_points;
8110 };
8111
8112 let snapshot = self.snapshot(window, cx);
8113
8114 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8115 let Some(project) = self.project() else {
8116 return breakpoint_display_points;
8117 };
8118
8119 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8120 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8121
8122 for (buffer_snapshot, range, excerpt_id) in
8123 multi_buffer_snapshot.range_to_buffer_ranges(range)
8124 {
8125 let Some(buffer) = project
8126 .read(cx)
8127 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8128 else {
8129 continue;
8130 };
8131 let breakpoints = breakpoint_store.read(cx).breakpoints(
8132 &buffer,
8133 Some(
8134 buffer_snapshot.anchor_before(range.start)
8135 ..buffer_snapshot.anchor_after(range.end),
8136 ),
8137 buffer_snapshot,
8138 cx,
8139 );
8140 for (breakpoint, state) in breakpoints {
8141 let multi_buffer_anchor =
8142 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8143 let position = multi_buffer_anchor
8144 .to_point(multi_buffer_snapshot)
8145 .to_display_point(&snapshot);
8146
8147 breakpoint_display_points.insert(
8148 position.row(),
8149 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8150 );
8151 }
8152 }
8153
8154 breakpoint_display_points
8155 }
8156
8157 fn breakpoint_context_menu(
8158 &self,
8159 anchor: Anchor,
8160 window: &mut Window,
8161 cx: &mut Context<Self>,
8162 ) -> Entity<ui::ContextMenu> {
8163 let weak_editor = cx.weak_entity();
8164 let focus_handle = self.focus_handle(cx);
8165
8166 let row = self
8167 .buffer
8168 .read(cx)
8169 .snapshot(cx)
8170 .summary_for_anchor::<Point>(&anchor)
8171 .row;
8172
8173 let breakpoint = self
8174 .breakpoint_at_row(row, window, cx)
8175 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8176
8177 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8178 "Edit Log Breakpoint"
8179 } else {
8180 "Set Log Breakpoint"
8181 };
8182
8183 let condition_breakpoint_msg = if breakpoint
8184 .as_ref()
8185 .is_some_and(|bp| bp.1.condition.is_some())
8186 {
8187 "Edit Condition Breakpoint"
8188 } else {
8189 "Set Condition Breakpoint"
8190 };
8191
8192 let hit_condition_breakpoint_msg = if breakpoint
8193 .as_ref()
8194 .is_some_and(|bp| bp.1.hit_condition.is_some())
8195 {
8196 "Edit Hit Condition Breakpoint"
8197 } else {
8198 "Set Hit Condition Breakpoint"
8199 };
8200
8201 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8202 "Unset Breakpoint"
8203 } else {
8204 "Set Breakpoint"
8205 };
8206
8207 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8208
8209 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8210 BreakpointState::Enabled => Some("Disable"),
8211 BreakpointState::Disabled => Some("Enable"),
8212 });
8213
8214 let (anchor, breakpoint) =
8215 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8216
8217 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8218 menu.on_blur_subscription(Subscription::new(|| {}))
8219 .context(focus_handle)
8220 .when(run_to_cursor, |this| {
8221 let weak_editor = weak_editor.clone();
8222 this.entry("Run to cursor", None, move |window, cx| {
8223 weak_editor
8224 .update(cx, |editor, cx| {
8225 editor.change_selections(
8226 SelectionEffects::no_scroll(),
8227 window,
8228 cx,
8229 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8230 );
8231 })
8232 .ok();
8233
8234 window.dispatch_action(Box::new(RunToCursor), cx);
8235 })
8236 .separator()
8237 })
8238 .when_some(toggle_state_msg, |this, msg| {
8239 this.entry(msg, None, {
8240 let weak_editor = weak_editor.clone();
8241 let breakpoint = breakpoint.clone();
8242 move |_window, cx| {
8243 weak_editor
8244 .update(cx, |this, cx| {
8245 this.edit_breakpoint_at_anchor(
8246 anchor,
8247 breakpoint.as_ref().clone(),
8248 BreakpointEditAction::InvertState,
8249 cx,
8250 );
8251 })
8252 .log_err();
8253 }
8254 })
8255 })
8256 .entry(set_breakpoint_msg, None, {
8257 let weak_editor = weak_editor.clone();
8258 let breakpoint = breakpoint.clone();
8259 move |_window, cx| {
8260 weak_editor
8261 .update(cx, |this, cx| {
8262 this.edit_breakpoint_at_anchor(
8263 anchor,
8264 breakpoint.as_ref().clone(),
8265 BreakpointEditAction::Toggle,
8266 cx,
8267 );
8268 })
8269 .log_err();
8270 }
8271 })
8272 .entry(log_breakpoint_msg, None, {
8273 let breakpoint = breakpoint.clone();
8274 let weak_editor = weak_editor.clone();
8275 move |window, cx| {
8276 weak_editor
8277 .update(cx, |this, cx| {
8278 this.add_edit_breakpoint_block(
8279 anchor,
8280 breakpoint.as_ref(),
8281 BreakpointPromptEditAction::Log,
8282 window,
8283 cx,
8284 );
8285 })
8286 .log_err();
8287 }
8288 })
8289 .entry(condition_breakpoint_msg, None, {
8290 let breakpoint = breakpoint.clone();
8291 let weak_editor = weak_editor.clone();
8292 move |window, cx| {
8293 weak_editor
8294 .update(cx, |this, cx| {
8295 this.add_edit_breakpoint_block(
8296 anchor,
8297 breakpoint.as_ref(),
8298 BreakpointPromptEditAction::Condition,
8299 window,
8300 cx,
8301 );
8302 })
8303 .log_err();
8304 }
8305 })
8306 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8307 weak_editor
8308 .update(cx, |this, cx| {
8309 this.add_edit_breakpoint_block(
8310 anchor,
8311 breakpoint.as_ref(),
8312 BreakpointPromptEditAction::HitCondition,
8313 window,
8314 cx,
8315 );
8316 })
8317 .log_err();
8318 })
8319 })
8320 }
8321
8322 fn render_breakpoint(
8323 &self,
8324 position: Anchor,
8325 row: DisplayRow,
8326 breakpoint: &Breakpoint,
8327 state: Option<BreakpointSessionState>,
8328 cx: &mut Context<Self>,
8329 ) -> IconButton {
8330 let is_rejected = state.is_some_and(|s| !s.verified);
8331 // Is it a breakpoint that shows up when hovering over gutter?
8332 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8333 (false, false),
8334 |PhantomBreakpointIndicator {
8335 is_active,
8336 display_row,
8337 collides_with_existing_breakpoint,
8338 }| {
8339 (
8340 is_active && display_row == row,
8341 collides_with_existing_breakpoint,
8342 )
8343 },
8344 );
8345
8346 let (color, icon) = {
8347 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8348 (false, false) => ui::IconName::DebugBreakpoint,
8349 (true, false) => ui::IconName::DebugLogBreakpoint,
8350 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8351 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8352 };
8353
8354 let color = if is_phantom {
8355 Color::Hint
8356 } else if is_rejected {
8357 Color::Disabled
8358 } else {
8359 Color::Debugger
8360 };
8361
8362 (color, icon)
8363 };
8364
8365 let breakpoint = Arc::from(breakpoint.clone());
8366
8367 let alt_as_text = gpui::Keystroke {
8368 modifiers: Modifiers::secondary_key(),
8369 ..Default::default()
8370 };
8371 let primary_action_text = if breakpoint.is_disabled() {
8372 "Enable breakpoint"
8373 } else if is_phantom && !collides_with_existing {
8374 "Set breakpoint"
8375 } else {
8376 "Unset breakpoint"
8377 };
8378 let focus_handle = self.focus_handle.clone();
8379
8380 let meta = if is_rejected {
8381 SharedString::from("No executable code is associated with this line.")
8382 } else if collides_with_existing && !breakpoint.is_disabled() {
8383 SharedString::from(format!(
8384 "{alt_as_text}-click to disable,\nright-click for more options."
8385 ))
8386 } else {
8387 SharedString::from("Right-click for more options.")
8388 };
8389 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8390 .icon_size(IconSize::XSmall)
8391 .size(ui::ButtonSize::None)
8392 .when(is_rejected, |this| {
8393 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8394 })
8395 .icon_color(color)
8396 .style(ButtonStyle::Transparent)
8397 .on_click(cx.listener({
8398 move |editor, event: &ClickEvent, window, cx| {
8399 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8400 BreakpointEditAction::InvertState
8401 } else {
8402 BreakpointEditAction::Toggle
8403 };
8404
8405 window.focus(&editor.focus_handle(cx));
8406 editor.edit_breakpoint_at_anchor(
8407 position,
8408 breakpoint.as_ref().clone(),
8409 edit_action,
8410 cx,
8411 );
8412 }
8413 }))
8414 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8415 editor.set_breakpoint_context_menu(
8416 row,
8417 Some(position),
8418 event.position(),
8419 window,
8420 cx,
8421 );
8422 }))
8423 .tooltip(move |window, cx| {
8424 Tooltip::with_meta_in(
8425 primary_action_text,
8426 Some(&ToggleBreakpoint),
8427 meta.clone(),
8428 &focus_handle,
8429 window,
8430 cx,
8431 )
8432 })
8433 }
8434
8435 fn build_tasks_context(
8436 project: &Entity<Project>,
8437 buffer: &Entity<Buffer>,
8438 buffer_row: u32,
8439 tasks: &Arc<RunnableTasks>,
8440 cx: &mut Context<Self>,
8441 ) -> Task<Option<task::TaskContext>> {
8442 let position = Point::new(buffer_row, tasks.column);
8443 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8444 let location = Location {
8445 buffer: buffer.clone(),
8446 range: range_start..range_start,
8447 };
8448 // Fill in the environmental variables from the tree-sitter captures
8449 let mut captured_task_variables = TaskVariables::default();
8450 for (capture_name, value) in tasks.extra_variables.clone() {
8451 captured_task_variables.insert(
8452 task::VariableName::Custom(capture_name.into()),
8453 value.clone(),
8454 );
8455 }
8456 project.update(cx, |project, cx| {
8457 project.task_store().update(cx, |task_store, cx| {
8458 task_store.task_context_for_location(captured_task_variables, location, cx)
8459 })
8460 })
8461 }
8462
8463 pub fn spawn_nearest_task(
8464 &mut self,
8465 action: &SpawnNearestTask,
8466 window: &mut Window,
8467 cx: &mut Context<Self>,
8468 ) {
8469 let Some((workspace, _)) = self.workspace.clone() else {
8470 return;
8471 };
8472 let Some(project) = self.project.clone() else {
8473 return;
8474 };
8475
8476 // Try to find a closest, enclosing node using tree-sitter that has a task
8477 let Some((buffer, buffer_row, tasks)) = self
8478 .find_enclosing_node_task(cx)
8479 // Or find the task that's closest in row-distance.
8480 .or_else(|| self.find_closest_task(cx))
8481 else {
8482 return;
8483 };
8484
8485 let reveal_strategy = action.reveal;
8486 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8487 cx.spawn_in(window, async move |_, cx| {
8488 let context = task_context.await?;
8489 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8490
8491 let resolved = &mut resolved_task.resolved;
8492 resolved.reveal = reveal_strategy;
8493
8494 workspace
8495 .update_in(cx, |workspace, window, cx| {
8496 workspace.schedule_resolved_task(
8497 task_source_kind,
8498 resolved_task,
8499 false,
8500 window,
8501 cx,
8502 );
8503 })
8504 .ok()
8505 })
8506 .detach();
8507 }
8508
8509 fn find_closest_task(
8510 &mut self,
8511 cx: &mut Context<Self>,
8512 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8513 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8514
8515 let ((buffer_id, row), tasks) = self
8516 .tasks
8517 .iter()
8518 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8519
8520 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8521 let tasks = Arc::new(tasks.to_owned());
8522 Some((buffer, *row, tasks))
8523 }
8524
8525 fn find_enclosing_node_task(
8526 &mut self,
8527 cx: &mut Context<Self>,
8528 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8529 let snapshot = self.buffer.read(cx).snapshot(cx);
8530 let offset = self.selections.newest::<usize>(cx).head();
8531 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8532 let buffer_id = excerpt.buffer().remote_id();
8533
8534 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8535 let mut cursor = layer.node().walk();
8536
8537 while cursor.goto_first_child_for_byte(offset).is_some() {
8538 if cursor.node().end_byte() == offset {
8539 cursor.goto_next_sibling();
8540 }
8541 }
8542
8543 // Ascend to the smallest ancestor that contains the range and has a task.
8544 loop {
8545 let node = cursor.node();
8546 let node_range = node.byte_range();
8547 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8548
8549 // Check if this node contains our offset
8550 if node_range.start <= offset && node_range.end >= offset {
8551 // If it contains offset, check for task
8552 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8553 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8554 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8555 }
8556 }
8557
8558 if !cursor.goto_parent() {
8559 break;
8560 }
8561 }
8562 None
8563 }
8564
8565 fn render_run_indicator(
8566 &self,
8567 _style: &EditorStyle,
8568 is_active: bool,
8569 row: DisplayRow,
8570 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8571 cx: &mut Context<Self>,
8572 ) -> IconButton {
8573 let color = Color::Muted;
8574 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8575
8576 IconButton::new(
8577 ("run_indicator", row.0 as usize),
8578 ui::IconName::PlayOutlined,
8579 )
8580 .shape(ui::IconButtonShape::Square)
8581 .icon_size(IconSize::XSmall)
8582 .icon_color(color)
8583 .toggle_state(is_active)
8584 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8585 let quick_launch = match e {
8586 ClickEvent::Keyboard(_) => true,
8587 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8588 };
8589
8590 window.focus(&editor.focus_handle(cx));
8591 editor.toggle_code_actions(
8592 &ToggleCodeActions {
8593 deployed_from: Some(CodeActionSource::RunMenu(row)),
8594 quick_launch,
8595 },
8596 window,
8597 cx,
8598 );
8599 }))
8600 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8601 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8602 }))
8603 }
8604
8605 pub fn context_menu_visible(&self) -> bool {
8606 !self.edit_prediction_preview_is_active()
8607 && self
8608 .context_menu
8609 .borrow()
8610 .as_ref()
8611 .is_some_and(|menu| menu.visible())
8612 }
8613
8614 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8615 self.context_menu
8616 .borrow()
8617 .as_ref()
8618 .map(|menu| menu.origin())
8619 }
8620
8621 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8622 self.context_menu_options = Some(options);
8623 }
8624
8625 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8626 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8627
8628 fn render_edit_prediction_popover(
8629 &mut self,
8630 text_bounds: &Bounds<Pixels>,
8631 content_origin: gpui::Point<Pixels>,
8632 right_margin: Pixels,
8633 editor_snapshot: &EditorSnapshot,
8634 visible_row_range: Range<DisplayRow>,
8635 scroll_top: ScrollOffset,
8636 scroll_bottom: ScrollOffset,
8637 line_layouts: &[LineWithInvisibles],
8638 line_height: Pixels,
8639 scroll_position: gpui::Point<ScrollOffset>,
8640 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8641 newest_selection_head: Option<DisplayPoint>,
8642 editor_width: Pixels,
8643 style: &EditorStyle,
8644 window: &mut Window,
8645 cx: &mut App,
8646 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8647 if self.mode().is_minimap() {
8648 return None;
8649 }
8650 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8651
8652 if self.edit_prediction_visible_in_cursor_popover(true) {
8653 return None;
8654 }
8655
8656 match &active_edit_prediction.completion {
8657 EditPrediction::MoveWithin { target, .. } => {
8658 let target_display_point = target.to_display_point(editor_snapshot);
8659
8660 if self.edit_prediction_requires_modifier() {
8661 if !self.edit_prediction_preview_is_active() {
8662 return None;
8663 }
8664
8665 self.render_edit_prediction_modifier_jump_popover(
8666 text_bounds,
8667 content_origin,
8668 visible_row_range,
8669 line_layouts,
8670 line_height,
8671 scroll_pixel_position,
8672 newest_selection_head,
8673 target_display_point,
8674 window,
8675 cx,
8676 )
8677 } else {
8678 self.render_edit_prediction_eager_jump_popover(
8679 text_bounds,
8680 content_origin,
8681 editor_snapshot,
8682 visible_row_range,
8683 scroll_top,
8684 scroll_bottom,
8685 line_height,
8686 scroll_pixel_position,
8687 target_display_point,
8688 editor_width,
8689 window,
8690 cx,
8691 )
8692 }
8693 }
8694 EditPrediction::Edit {
8695 display_mode: EditDisplayMode::Inline,
8696 ..
8697 } => None,
8698 EditPrediction::Edit {
8699 display_mode: EditDisplayMode::TabAccept,
8700 edits,
8701 ..
8702 } => {
8703 let range = &edits.first()?.0;
8704 let target_display_point = range.end.to_display_point(editor_snapshot);
8705
8706 self.render_edit_prediction_end_of_line_popover(
8707 "Accept",
8708 editor_snapshot,
8709 visible_row_range,
8710 target_display_point,
8711 line_height,
8712 scroll_pixel_position,
8713 content_origin,
8714 editor_width,
8715 window,
8716 cx,
8717 )
8718 }
8719 EditPrediction::Edit {
8720 edits,
8721 edit_preview,
8722 display_mode: EditDisplayMode::DiffPopover,
8723 snapshot,
8724 } => self.render_edit_prediction_diff_popover(
8725 text_bounds,
8726 content_origin,
8727 right_margin,
8728 editor_snapshot,
8729 visible_row_range,
8730 line_layouts,
8731 line_height,
8732 scroll_position,
8733 scroll_pixel_position,
8734 newest_selection_head,
8735 editor_width,
8736 style,
8737 edits,
8738 edit_preview,
8739 snapshot,
8740 window,
8741 cx,
8742 ),
8743 EditPrediction::MoveOutside { snapshot, .. } => {
8744 let file_name = snapshot
8745 .file()
8746 .map(|file| file.file_name(cx))
8747 .unwrap_or("untitled");
8748 let mut element = self
8749 .render_edit_prediction_line_popover(
8750 format!("Jump to {file_name}"),
8751 Some(IconName::ZedPredict),
8752 window,
8753 cx,
8754 )
8755 .into_any();
8756
8757 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8758 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8759 let origin_y = text_bounds.size.height - size.height - px(30.);
8760 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8761 element.prepaint_at(origin, window, cx);
8762
8763 Some((element, origin))
8764 }
8765 }
8766 }
8767
8768 fn render_edit_prediction_modifier_jump_popover(
8769 &mut self,
8770 text_bounds: &Bounds<Pixels>,
8771 content_origin: gpui::Point<Pixels>,
8772 visible_row_range: Range<DisplayRow>,
8773 line_layouts: &[LineWithInvisibles],
8774 line_height: Pixels,
8775 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8776 newest_selection_head: Option<DisplayPoint>,
8777 target_display_point: DisplayPoint,
8778 window: &mut Window,
8779 cx: &mut App,
8780 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8781 let scrolled_content_origin =
8782 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8783
8784 const SCROLL_PADDING_Y: Pixels = px(12.);
8785
8786 if target_display_point.row() < visible_row_range.start {
8787 return self.render_edit_prediction_scroll_popover(
8788 |_| SCROLL_PADDING_Y,
8789 IconName::ArrowUp,
8790 visible_row_range,
8791 line_layouts,
8792 newest_selection_head,
8793 scrolled_content_origin,
8794 window,
8795 cx,
8796 );
8797 } else if target_display_point.row() >= visible_row_range.end {
8798 return self.render_edit_prediction_scroll_popover(
8799 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8800 IconName::ArrowDown,
8801 visible_row_range,
8802 line_layouts,
8803 newest_selection_head,
8804 scrolled_content_origin,
8805 window,
8806 cx,
8807 );
8808 }
8809
8810 const POLE_WIDTH: Pixels = px(2.);
8811
8812 let line_layout =
8813 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8814 let target_column = target_display_point.column() as usize;
8815
8816 let target_x = line_layout.x_for_index(target_column);
8817 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8818 - scroll_pixel_position.y;
8819
8820 let flag_on_right = target_x < text_bounds.size.width / 2.;
8821
8822 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8823 border_color.l += 0.001;
8824
8825 let mut element = v_flex()
8826 .items_end()
8827 .when(flag_on_right, |el| el.items_start())
8828 .child(if flag_on_right {
8829 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8830 .rounded_bl(px(0.))
8831 .rounded_tl(px(0.))
8832 .border_l_2()
8833 .border_color(border_color)
8834 } else {
8835 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8836 .rounded_br(px(0.))
8837 .rounded_tr(px(0.))
8838 .border_r_2()
8839 .border_color(border_color)
8840 })
8841 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8842 .into_any();
8843
8844 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8845
8846 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8847 - point(
8848 if flag_on_right {
8849 POLE_WIDTH
8850 } else {
8851 size.width - POLE_WIDTH
8852 },
8853 size.height - line_height,
8854 );
8855
8856 origin.x = origin.x.max(content_origin.x);
8857
8858 element.prepaint_at(origin, window, cx);
8859
8860 Some((element, origin))
8861 }
8862
8863 fn render_edit_prediction_scroll_popover(
8864 &mut self,
8865 to_y: impl Fn(Size<Pixels>) -> Pixels,
8866 scroll_icon: IconName,
8867 visible_row_range: Range<DisplayRow>,
8868 line_layouts: &[LineWithInvisibles],
8869 newest_selection_head: Option<DisplayPoint>,
8870 scrolled_content_origin: gpui::Point<Pixels>,
8871 window: &mut Window,
8872 cx: &mut App,
8873 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8874 let mut element = self
8875 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8876 .into_any();
8877
8878 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8879
8880 let cursor = newest_selection_head?;
8881 let cursor_row_layout =
8882 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8883 let cursor_column = cursor.column() as usize;
8884
8885 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8886
8887 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8888
8889 element.prepaint_at(origin, window, cx);
8890 Some((element, origin))
8891 }
8892
8893 fn render_edit_prediction_eager_jump_popover(
8894 &mut self,
8895 text_bounds: &Bounds<Pixels>,
8896 content_origin: gpui::Point<Pixels>,
8897 editor_snapshot: &EditorSnapshot,
8898 visible_row_range: Range<DisplayRow>,
8899 scroll_top: ScrollOffset,
8900 scroll_bottom: ScrollOffset,
8901 line_height: Pixels,
8902 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8903 target_display_point: DisplayPoint,
8904 editor_width: Pixels,
8905 window: &mut Window,
8906 cx: &mut App,
8907 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8908 if target_display_point.row().as_f64() < scroll_top {
8909 let mut element = self
8910 .render_edit_prediction_line_popover(
8911 "Jump to Edit",
8912 Some(IconName::ArrowUp),
8913 window,
8914 cx,
8915 )
8916 .into_any();
8917
8918 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8919 let offset = point(
8920 (text_bounds.size.width - size.width) / 2.,
8921 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8922 );
8923
8924 let origin = text_bounds.origin + offset;
8925 element.prepaint_at(origin, window, cx);
8926 Some((element, origin))
8927 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8928 let mut element = self
8929 .render_edit_prediction_line_popover(
8930 "Jump to Edit",
8931 Some(IconName::ArrowDown),
8932 window,
8933 cx,
8934 )
8935 .into_any();
8936
8937 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8938 let offset = point(
8939 (text_bounds.size.width - size.width) / 2.,
8940 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8941 );
8942
8943 let origin = text_bounds.origin + offset;
8944 element.prepaint_at(origin, window, cx);
8945 Some((element, origin))
8946 } else {
8947 self.render_edit_prediction_end_of_line_popover(
8948 "Jump to Edit",
8949 editor_snapshot,
8950 visible_row_range,
8951 target_display_point,
8952 line_height,
8953 scroll_pixel_position,
8954 content_origin,
8955 editor_width,
8956 window,
8957 cx,
8958 )
8959 }
8960 }
8961
8962 fn render_edit_prediction_end_of_line_popover(
8963 self: &mut Editor,
8964 label: &'static str,
8965 editor_snapshot: &EditorSnapshot,
8966 visible_row_range: Range<DisplayRow>,
8967 target_display_point: DisplayPoint,
8968 line_height: Pixels,
8969 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8970 content_origin: gpui::Point<Pixels>,
8971 editor_width: Pixels,
8972 window: &mut Window,
8973 cx: &mut App,
8974 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8975 let target_line_end = DisplayPoint::new(
8976 target_display_point.row(),
8977 editor_snapshot.line_len(target_display_point.row()),
8978 );
8979
8980 let mut element = self
8981 .render_edit_prediction_line_popover(label, None, window, cx)
8982 .into_any();
8983
8984 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8985
8986 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8987
8988 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8989 let mut origin = start_point
8990 + line_origin
8991 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8992 origin.x = origin.x.max(content_origin.x);
8993
8994 let max_x = content_origin.x + editor_width - size.width;
8995
8996 if origin.x > max_x {
8997 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8998
8999 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9000 origin.y += offset;
9001 IconName::ArrowUp
9002 } else {
9003 origin.y -= offset;
9004 IconName::ArrowDown
9005 };
9006
9007 element = self
9008 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9009 .into_any();
9010
9011 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9012
9013 origin.x = content_origin.x + editor_width - size.width - px(2.);
9014 }
9015
9016 element.prepaint_at(origin, window, cx);
9017 Some((element, origin))
9018 }
9019
9020 fn render_edit_prediction_diff_popover(
9021 self: &Editor,
9022 text_bounds: &Bounds<Pixels>,
9023 content_origin: gpui::Point<Pixels>,
9024 right_margin: Pixels,
9025 editor_snapshot: &EditorSnapshot,
9026 visible_row_range: Range<DisplayRow>,
9027 line_layouts: &[LineWithInvisibles],
9028 line_height: Pixels,
9029 scroll_position: gpui::Point<ScrollOffset>,
9030 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9031 newest_selection_head: Option<DisplayPoint>,
9032 editor_width: Pixels,
9033 style: &EditorStyle,
9034 edits: &Vec<(Range<Anchor>, String)>,
9035 edit_preview: &Option<language::EditPreview>,
9036 snapshot: &language::BufferSnapshot,
9037 window: &mut Window,
9038 cx: &mut App,
9039 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9040 let edit_start = edits
9041 .first()
9042 .unwrap()
9043 .0
9044 .start
9045 .to_display_point(editor_snapshot);
9046 let edit_end = edits
9047 .last()
9048 .unwrap()
9049 .0
9050 .end
9051 .to_display_point(editor_snapshot);
9052
9053 let is_visible = visible_row_range.contains(&edit_start.row())
9054 || visible_row_range.contains(&edit_end.row());
9055 if !is_visible {
9056 return None;
9057 }
9058
9059 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9060 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9061 } else {
9062 // Fallback for providers without edit_preview
9063 crate::edit_prediction_fallback_text(edits, cx)
9064 };
9065
9066 let styled_text = highlighted_edits.to_styled_text(&style.text);
9067 let line_count = highlighted_edits.text.lines().count();
9068
9069 const BORDER_WIDTH: Pixels = px(1.);
9070
9071 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9072 let has_keybind = keybind.is_some();
9073
9074 let mut element = h_flex()
9075 .items_start()
9076 .child(
9077 h_flex()
9078 .bg(cx.theme().colors().editor_background)
9079 .border(BORDER_WIDTH)
9080 .shadow_xs()
9081 .border_color(cx.theme().colors().border)
9082 .rounded_l_lg()
9083 .when(line_count > 1, |el| el.rounded_br_lg())
9084 .pr_1()
9085 .child(styled_text),
9086 )
9087 .child(
9088 h_flex()
9089 .h(line_height + BORDER_WIDTH * 2.)
9090 .px_1p5()
9091 .gap_1()
9092 // Workaround: For some reason, there's a gap if we don't do this
9093 .ml(-BORDER_WIDTH)
9094 .shadow(vec![gpui::BoxShadow {
9095 color: gpui::black().opacity(0.05),
9096 offset: point(px(1.), px(1.)),
9097 blur_radius: px(2.),
9098 spread_radius: px(0.),
9099 }])
9100 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9101 .border(BORDER_WIDTH)
9102 .border_color(cx.theme().colors().border)
9103 .rounded_r_lg()
9104 .id("edit_prediction_diff_popover_keybind")
9105 .when(!has_keybind, |el| {
9106 let status_colors = cx.theme().status();
9107
9108 el.bg(status_colors.error_background)
9109 .border_color(status_colors.error.opacity(0.6))
9110 .child(Icon::new(IconName::Info).color(Color::Error))
9111 .cursor_default()
9112 .hoverable_tooltip(move |_window, cx| {
9113 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9114 })
9115 })
9116 .children(keybind),
9117 )
9118 .into_any();
9119
9120 let longest_row =
9121 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9122 let longest_line_width = if visible_row_range.contains(&longest_row) {
9123 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9124 } else {
9125 layout_line(
9126 longest_row,
9127 editor_snapshot,
9128 style,
9129 editor_width,
9130 |_| false,
9131 window,
9132 cx,
9133 )
9134 .width
9135 };
9136
9137 let viewport_bounds =
9138 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9139 right: -right_margin,
9140 ..Default::default()
9141 });
9142
9143 let x_after_longest = Pixels::from(
9144 ScrollPixelOffset::from(
9145 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9146 ) - scroll_pixel_position.x,
9147 );
9148
9149 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9150
9151 // Fully visible if it can be displayed within the window (allow overlapping other
9152 // panes). However, this is only allowed if the popover starts within text_bounds.
9153 let can_position_to_the_right = x_after_longest < text_bounds.right()
9154 && x_after_longest + element_bounds.width < viewport_bounds.right();
9155
9156 let mut origin = if can_position_to_the_right {
9157 point(
9158 x_after_longest,
9159 text_bounds.origin.y
9160 + Pixels::from(
9161 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9162 - scroll_pixel_position.y,
9163 ),
9164 )
9165 } else {
9166 let cursor_row = newest_selection_head.map(|head| head.row());
9167 let above_edit = edit_start
9168 .row()
9169 .0
9170 .checked_sub(line_count as u32)
9171 .map(DisplayRow);
9172 let below_edit = Some(edit_end.row() + 1);
9173 let above_cursor =
9174 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9175 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9176
9177 // Place the edit popover adjacent to the edit if there is a location
9178 // available that is onscreen and does not obscure the cursor. Otherwise,
9179 // place it adjacent to the cursor.
9180 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9181 .into_iter()
9182 .flatten()
9183 .find(|&start_row| {
9184 let end_row = start_row + line_count as u32;
9185 visible_row_range.contains(&start_row)
9186 && visible_row_range.contains(&end_row)
9187 && cursor_row
9188 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9189 })?;
9190
9191 content_origin
9192 + point(
9193 Pixels::from(-scroll_pixel_position.x),
9194 Pixels::from(
9195 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9196 ),
9197 )
9198 };
9199
9200 origin.x -= BORDER_WIDTH;
9201
9202 window.defer_draw(element, origin, 1);
9203
9204 // Do not return an element, since it will already be drawn due to defer_draw.
9205 None
9206 }
9207
9208 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9209 px(30.)
9210 }
9211
9212 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9213 if self.read_only(cx) {
9214 cx.theme().players().read_only()
9215 } else {
9216 self.style.as_ref().unwrap().local_player
9217 }
9218 }
9219
9220 fn render_edit_prediction_accept_keybind(
9221 &self,
9222 window: &mut Window,
9223 cx: &App,
9224 ) -> Option<AnyElement> {
9225 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9226 let accept_keystroke = accept_binding.keystroke()?;
9227
9228 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9229
9230 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9231 Color::Accent
9232 } else {
9233 Color::Muted
9234 };
9235
9236 h_flex()
9237 .px_0p5()
9238 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9239 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9240 .text_size(TextSize::XSmall.rems(cx))
9241 .child(h_flex().children(ui::render_modifiers(
9242 accept_keystroke.modifiers(),
9243 PlatformStyle::platform(),
9244 Some(modifiers_color),
9245 Some(IconSize::XSmall.rems().into()),
9246 true,
9247 )))
9248 .when(is_platform_style_mac, |parent| {
9249 parent.child(accept_keystroke.key().to_string())
9250 })
9251 .when(!is_platform_style_mac, |parent| {
9252 parent.child(
9253 Key::new(
9254 util::capitalize(accept_keystroke.key()),
9255 Some(Color::Default),
9256 )
9257 .size(Some(IconSize::XSmall.rems().into())),
9258 )
9259 })
9260 .into_any()
9261 .into()
9262 }
9263
9264 fn render_edit_prediction_line_popover(
9265 &self,
9266 label: impl Into<SharedString>,
9267 icon: Option<IconName>,
9268 window: &mut Window,
9269 cx: &App,
9270 ) -> Stateful<Div> {
9271 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9272
9273 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9274 let has_keybind = keybind.is_some();
9275
9276 h_flex()
9277 .id("ep-line-popover")
9278 .py_0p5()
9279 .pl_1()
9280 .pr(padding_right)
9281 .gap_1()
9282 .rounded_md()
9283 .border_1()
9284 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9285 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9286 .shadow_xs()
9287 .when(!has_keybind, |el| {
9288 let status_colors = cx.theme().status();
9289
9290 el.bg(status_colors.error_background)
9291 .border_color(status_colors.error.opacity(0.6))
9292 .pl_2()
9293 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9294 .cursor_default()
9295 .hoverable_tooltip(move |_window, cx| {
9296 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9297 })
9298 })
9299 .children(keybind)
9300 .child(
9301 Label::new(label)
9302 .size(LabelSize::Small)
9303 .when(!has_keybind, |el| {
9304 el.color(cx.theme().status().error.into()).strikethrough()
9305 }),
9306 )
9307 .when(!has_keybind, |el| {
9308 el.child(
9309 h_flex().ml_1().child(
9310 Icon::new(IconName::Info)
9311 .size(IconSize::Small)
9312 .color(cx.theme().status().error.into()),
9313 ),
9314 )
9315 })
9316 .when_some(icon, |element, icon| {
9317 element.child(
9318 div()
9319 .mt(px(1.5))
9320 .child(Icon::new(icon).size(IconSize::Small)),
9321 )
9322 })
9323 }
9324
9325 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9326 let accent_color = cx.theme().colors().text_accent;
9327 let editor_bg_color = cx.theme().colors().editor_background;
9328 editor_bg_color.blend(accent_color.opacity(0.1))
9329 }
9330
9331 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9332 let accent_color = cx.theme().colors().text_accent;
9333 let editor_bg_color = cx.theme().colors().editor_background;
9334 editor_bg_color.blend(accent_color.opacity(0.6))
9335 }
9336 fn get_prediction_provider_icon_name(
9337 provider: &Option<RegisteredEditPredictionProvider>,
9338 ) -> IconName {
9339 match provider {
9340 Some(provider) => match provider.provider.name() {
9341 "copilot" => IconName::Copilot,
9342 "supermaven" => IconName::Supermaven,
9343 _ => IconName::ZedPredict,
9344 },
9345 None => IconName::ZedPredict,
9346 }
9347 }
9348
9349 fn render_edit_prediction_cursor_popover(
9350 &self,
9351 min_width: Pixels,
9352 max_width: Pixels,
9353 cursor_point: Point,
9354 style: &EditorStyle,
9355 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9356 _window: &Window,
9357 cx: &mut Context<Editor>,
9358 ) -> Option<AnyElement> {
9359 let provider = self.edit_prediction_provider.as_ref()?;
9360 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9361
9362 let is_refreshing = provider.provider.is_refreshing(cx);
9363
9364 fn pending_completion_container(icon: IconName) -> Div {
9365 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9366 }
9367
9368 let completion = match &self.active_edit_prediction {
9369 Some(prediction) => {
9370 if !self.has_visible_completions_menu() {
9371 const RADIUS: Pixels = px(6.);
9372 const BORDER_WIDTH: Pixels = px(1.);
9373
9374 return Some(
9375 h_flex()
9376 .elevation_2(cx)
9377 .border(BORDER_WIDTH)
9378 .border_color(cx.theme().colors().border)
9379 .when(accept_keystroke.is_none(), |el| {
9380 el.border_color(cx.theme().status().error)
9381 })
9382 .rounded(RADIUS)
9383 .rounded_tl(px(0.))
9384 .overflow_hidden()
9385 .child(div().px_1p5().child(match &prediction.completion {
9386 EditPrediction::MoveWithin { target, snapshot } => {
9387 use text::ToPoint as _;
9388 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9389 {
9390 Icon::new(IconName::ZedPredictDown)
9391 } else {
9392 Icon::new(IconName::ZedPredictUp)
9393 }
9394 }
9395 EditPrediction::MoveOutside { .. } => {
9396 // TODO [zeta2] custom icon for external jump?
9397 Icon::new(provider_icon)
9398 }
9399 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9400 }))
9401 .child(
9402 h_flex()
9403 .gap_1()
9404 .py_1()
9405 .px_2()
9406 .rounded_r(RADIUS - BORDER_WIDTH)
9407 .border_l_1()
9408 .border_color(cx.theme().colors().border)
9409 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9410 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9411 el.child(
9412 Label::new("Hold")
9413 .size(LabelSize::Small)
9414 .when(accept_keystroke.is_none(), |el| {
9415 el.strikethrough()
9416 })
9417 .line_height_style(LineHeightStyle::UiLabel),
9418 )
9419 })
9420 .id("edit_prediction_cursor_popover_keybind")
9421 .when(accept_keystroke.is_none(), |el| {
9422 let status_colors = cx.theme().status();
9423
9424 el.bg(status_colors.error_background)
9425 .border_color(status_colors.error.opacity(0.6))
9426 .child(Icon::new(IconName::Info).color(Color::Error))
9427 .cursor_default()
9428 .hoverable_tooltip(move |_window, cx| {
9429 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9430 .into()
9431 })
9432 })
9433 .when_some(
9434 accept_keystroke.as_ref(),
9435 |el, accept_keystroke| {
9436 el.child(h_flex().children(ui::render_modifiers(
9437 accept_keystroke.modifiers(),
9438 PlatformStyle::platform(),
9439 Some(Color::Default),
9440 Some(IconSize::XSmall.rems().into()),
9441 false,
9442 )))
9443 },
9444 ),
9445 )
9446 .into_any(),
9447 );
9448 }
9449
9450 self.render_edit_prediction_cursor_popover_preview(
9451 prediction,
9452 cursor_point,
9453 style,
9454 cx,
9455 )?
9456 }
9457
9458 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9459 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9460 stale_completion,
9461 cursor_point,
9462 style,
9463 cx,
9464 )?,
9465
9466 None => pending_completion_container(provider_icon)
9467 .child(Label::new("...").size(LabelSize::Small)),
9468 },
9469
9470 None => pending_completion_container(provider_icon)
9471 .child(Label::new("...").size(LabelSize::Small)),
9472 };
9473
9474 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9475 completion
9476 .with_animation(
9477 "loading-completion",
9478 Animation::new(Duration::from_secs(2))
9479 .repeat()
9480 .with_easing(pulsating_between(0.4, 0.8)),
9481 |label, delta| label.opacity(delta),
9482 )
9483 .into_any_element()
9484 } else {
9485 completion.into_any_element()
9486 };
9487
9488 let has_completion = self.active_edit_prediction.is_some();
9489
9490 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9491 Some(
9492 h_flex()
9493 .min_w(min_width)
9494 .max_w(max_width)
9495 .flex_1()
9496 .elevation_2(cx)
9497 .border_color(cx.theme().colors().border)
9498 .child(
9499 div()
9500 .flex_1()
9501 .py_1()
9502 .px_2()
9503 .overflow_hidden()
9504 .child(completion),
9505 )
9506 .when_some(accept_keystroke, |el, accept_keystroke| {
9507 if !accept_keystroke.modifiers().modified() {
9508 return el;
9509 }
9510
9511 el.child(
9512 h_flex()
9513 .h_full()
9514 .border_l_1()
9515 .rounded_r_lg()
9516 .border_color(cx.theme().colors().border)
9517 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9518 .gap_1()
9519 .py_1()
9520 .px_2()
9521 .child(
9522 h_flex()
9523 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9524 .when(is_platform_style_mac, |parent| parent.gap_1())
9525 .child(h_flex().children(ui::render_modifiers(
9526 accept_keystroke.modifiers(),
9527 PlatformStyle::platform(),
9528 Some(if !has_completion {
9529 Color::Muted
9530 } else {
9531 Color::Default
9532 }),
9533 None,
9534 false,
9535 ))),
9536 )
9537 .child(Label::new("Preview").into_any_element())
9538 .opacity(if has_completion { 1.0 } else { 0.4 }),
9539 )
9540 })
9541 .into_any(),
9542 )
9543 }
9544
9545 fn render_edit_prediction_cursor_popover_preview(
9546 &self,
9547 completion: &EditPredictionState,
9548 cursor_point: Point,
9549 style: &EditorStyle,
9550 cx: &mut Context<Editor>,
9551 ) -> Option<Div> {
9552 use text::ToPoint as _;
9553
9554 fn render_relative_row_jump(
9555 prefix: impl Into<String>,
9556 current_row: u32,
9557 target_row: u32,
9558 ) -> Div {
9559 let (row_diff, arrow) = if target_row < current_row {
9560 (current_row - target_row, IconName::ArrowUp)
9561 } else {
9562 (target_row - current_row, IconName::ArrowDown)
9563 };
9564
9565 h_flex()
9566 .child(
9567 Label::new(format!("{}{}", prefix.into(), row_diff))
9568 .color(Color::Muted)
9569 .size(LabelSize::Small),
9570 )
9571 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9572 }
9573
9574 let supports_jump = self
9575 .edit_prediction_provider
9576 .as_ref()
9577 .map(|provider| provider.provider.supports_jump_to_edit())
9578 .unwrap_or(true);
9579
9580 match &completion.completion {
9581 EditPrediction::MoveWithin {
9582 target, snapshot, ..
9583 } => {
9584 if !supports_jump {
9585 return None;
9586 }
9587
9588 Some(
9589 h_flex()
9590 .px_2()
9591 .gap_2()
9592 .flex_1()
9593 .child(
9594 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9595 Icon::new(IconName::ZedPredictDown)
9596 } else {
9597 Icon::new(IconName::ZedPredictUp)
9598 },
9599 )
9600 .child(Label::new("Jump to Edit")),
9601 )
9602 }
9603 EditPrediction::MoveOutside { snapshot, .. } => {
9604 let file_name = snapshot
9605 .file()
9606 .map(|file| file.file_name(cx))
9607 .unwrap_or("untitled");
9608 Some(
9609 h_flex()
9610 .px_2()
9611 .gap_2()
9612 .flex_1()
9613 .child(Icon::new(IconName::ZedPredict))
9614 .child(Label::new(format!("Jump to {file_name}"))),
9615 )
9616 }
9617 EditPrediction::Edit {
9618 edits,
9619 edit_preview,
9620 snapshot,
9621 display_mode: _,
9622 } => {
9623 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9624
9625 let (highlighted_edits, has_more_lines) =
9626 if let Some(edit_preview) = edit_preview.as_ref() {
9627 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9628 .first_line_preview()
9629 } else {
9630 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9631 };
9632
9633 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9634 .with_default_highlights(&style.text, highlighted_edits.highlights);
9635
9636 let preview = h_flex()
9637 .gap_1()
9638 .min_w_16()
9639 .child(styled_text)
9640 .when(has_more_lines, |parent| parent.child("…"));
9641
9642 let left = if supports_jump && first_edit_row != cursor_point.row {
9643 render_relative_row_jump("", cursor_point.row, first_edit_row)
9644 .into_any_element()
9645 } else {
9646 let icon_name =
9647 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9648 Icon::new(icon_name).into_any_element()
9649 };
9650
9651 Some(
9652 h_flex()
9653 .h_full()
9654 .flex_1()
9655 .gap_2()
9656 .pr_1()
9657 .overflow_x_hidden()
9658 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9659 .child(left)
9660 .child(preview),
9661 )
9662 }
9663 }
9664 }
9665
9666 pub fn render_context_menu(
9667 &self,
9668 style: &EditorStyle,
9669 max_height_in_lines: u32,
9670 window: &mut Window,
9671 cx: &mut Context<Editor>,
9672 ) -> Option<AnyElement> {
9673 let menu = self.context_menu.borrow();
9674 let menu = menu.as_ref()?;
9675 if !menu.visible() {
9676 return None;
9677 };
9678 Some(menu.render(style, max_height_in_lines, window, cx))
9679 }
9680
9681 fn render_context_menu_aside(
9682 &mut self,
9683 max_size: Size<Pixels>,
9684 window: &mut Window,
9685 cx: &mut Context<Editor>,
9686 ) -> Option<AnyElement> {
9687 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9688 if menu.visible() {
9689 menu.render_aside(max_size, window, cx)
9690 } else {
9691 None
9692 }
9693 })
9694 }
9695
9696 fn hide_context_menu(
9697 &mut self,
9698 window: &mut Window,
9699 cx: &mut Context<Self>,
9700 ) -> Option<CodeContextMenu> {
9701 cx.notify();
9702 self.completion_tasks.clear();
9703 let context_menu = self.context_menu.borrow_mut().take();
9704 self.stale_edit_prediction_in_menu.take();
9705 self.update_visible_edit_prediction(window, cx);
9706 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9707 && let Some(completion_provider) = &self.completion_provider
9708 {
9709 completion_provider.selection_changed(None, window, cx);
9710 }
9711 context_menu
9712 }
9713
9714 fn show_snippet_choices(
9715 &mut self,
9716 choices: &Vec<String>,
9717 selection: Range<Anchor>,
9718 cx: &mut Context<Self>,
9719 ) {
9720 let Some((_, buffer, _)) = self
9721 .buffer()
9722 .read(cx)
9723 .excerpt_containing(selection.start, cx)
9724 else {
9725 return;
9726 };
9727 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9728 else {
9729 return;
9730 };
9731 if buffer != end_buffer {
9732 log::error!("expected anchor range to have matching buffer IDs");
9733 return;
9734 }
9735
9736 let id = post_inc(&mut self.next_completion_id);
9737 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9738 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9739 CompletionsMenu::new_snippet_choices(
9740 id,
9741 true,
9742 choices,
9743 selection,
9744 buffer,
9745 snippet_sort_order,
9746 ),
9747 ));
9748 }
9749
9750 pub fn insert_snippet(
9751 &mut self,
9752 insertion_ranges: &[Range<usize>],
9753 snippet: Snippet,
9754 window: &mut Window,
9755 cx: &mut Context<Self>,
9756 ) -> Result<()> {
9757 struct Tabstop<T> {
9758 is_end_tabstop: bool,
9759 ranges: Vec<Range<T>>,
9760 choices: Option<Vec<String>>,
9761 }
9762
9763 let tabstops = self.buffer.update(cx, |buffer, cx| {
9764 let snippet_text: Arc<str> = snippet.text.clone().into();
9765 let edits = insertion_ranges
9766 .iter()
9767 .cloned()
9768 .map(|range| (range, snippet_text.clone()));
9769 let autoindent_mode = AutoindentMode::Block {
9770 original_indent_columns: Vec::new(),
9771 };
9772 buffer.edit(edits, Some(autoindent_mode), cx);
9773
9774 let snapshot = &*buffer.read(cx);
9775 let snippet = &snippet;
9776 snippet
9777 .tabstops
9778 .iter()
9779 .map(|tabstop| {
9780 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9781 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9782 });
9783 let mut tabstop_ranges = tabstop
9784 .ranges
9785 .iter()
9786 .flat_map(|tabstop_range| {
9787 let mut delta = 0_isize;
9788 insertion_ranges.iter().map(move |insertion_range| {
9789 let insertion_start = insertion_range.start as isize + delta;
9790 delta +=
9791 snippet.text.len() as isize - insertion_range.len() as isize;
9792
9793 let start = ((insertion_start + tabstop_range.start) as usize)
9794 .min(snapshot.len());
9795 let end = ((insertion_start + tabstop_range.end) as usize)
9796 .min(snapshot.len());
9797 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9798 })
9799 })
9800 .collect::<Vec<_>>();
9801 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9802
9803 Tabstop {
9804 is_end_tabstop,
9805 ranges: tabstop_ranges,
9806 choices: tabstop.choices.clone(),
9807 }
9808 })
9809 .collect::<Vec<_>>()
9810 });
9811 if let Some(tabstop) = tabstops.first() {
9812 self.change_selections(Default::default(), window, cx, |s| {
9813 // Reverse order so that the first range is the newest created selection.
9814 // Completions will use it and autoscroll will prioritize it.
9815 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9816 });
9817
9818 if let Some(choices) = &tabstop.choices
9819 && let Some(selection) = tabstop.ranges.first()
9820 {
9821 self.show_snippet_choices(choices, selection.clone(), cx)
9822 }
9823
9824 // If we're already at the last tabstop and it's at the end of the snippet,
9825 // we're done, we don't need to keep the state around.
9826 if !tabstop.is_end_tabstop {
9827 let choices = tabstops
9828 .iter()
9829 .map(|tabstop| tabstop.choices.clone())
9830 .collect();
9831
9832 let ranges = tabstops
9833 .into_iter()
9834 .map(|tabstop| tabstop.ranges)
9835 .collect::<Vec<_>>();
9836
9837 self.snippet_stack.push(SnippetState {
9838 active_index: 0,
9839 ranges,
9840 choices,
9841 });
9842 }
9843
9844 // Check whether the just-entered snippet ends with an auto-closable bracket.
9845 if self.autoclose_regions.is_empty() {
9846 let snapshot = self.buffer.read(cx).snapshot(cx);
9847 let mut all_selections = self.selections.all::<Point>(cx);
9848 for selection in &mut all_selections {
9849 let selection_head = selection.head();
9850 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9851 continue;
9852 };
9853
9854 let mut bracket_pair = None;
9855 let max_lookup_length = scope
9856 .brackets()
9857 .map(|(pair, _)| {
9858 pair.start
9859 .as_str()
9860 .chars()
9861 .count()
9862 .max(pair.end.as_str().chars().count())
9863 })
9864 .max();
9865 if let Some(max_lookup_length) = max_lookup_length {
9866 let next_text = snapshot
9867 .chars_at(selection_head)
9868 .take(max_lookup_length)
9869 .collect::<String>();
9870 let prev_text = snapshot
9871 .reversed_chars_at(selection_head)
9872 .take(max_lookup_length)
9873 .collect::<String>();
9874
9875 for (pair, enabled) in scope.brackets() {
9876 if enabled
9877 && pair.close
9878 && prev_text.starts_with(pair.start.as_str())
9879 && next_text.starts_with(pair.end.as_str())
9880 {
9881 bracket_pair = Some(pair.clone());
9882 break;
9883 }
9884 }
9885 }
9886
9887 if let Some(pair) = bracket_pair {
9888 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9889 let autoclose_enabled =
9890 self.use_autoclose && snapshot_settings.use_autoclose;
9891 if autoclose_enabled {
9892 let start = snapshot.anchor_after(selection_head);
9893 let end = snapshot.anchor_after(selection_head);
9894 self.autoclose_regions.push(AutocloseRegion {
9895 selection_id: selection.id,
9896 range: start..end,
9897 pair,
9898 });
9899 }
9900 }
9901 }
9902 }
9903 }
9904 Ok(())
9905 }
9906
9907 pub fn move_to_next_snippet_tabstop(
9908 &mut self,
9909 window: &mut Window,
9910 cx: &mut Context<Self>,
9911 ) -> bool {
9912 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9913 }
9914
9915 pub fn move_to_prev_snippet_tabstop(
9916 &mut self,
9917 window: &mut Window,
9918 cx: &mut Context<Self>,
9919 ) -> bool {
9920 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9921 }
9922
9923 pub fn move_to_snippet_tabstop(
9924 &mut self,
9925 bias: Bias,
9926 window: &mut Window,
9927 cx: &mut Context<Self>,
9928 ) -> bool {
9929 if let Some(mut snippet) = self.snippet_stack.pop() {
9930 match bias {
9931 Bias::Left => {
9932 if snippet.active_index > 0 {
9933 snippet.active_index -= 1;
9934 } else {
9935 self.snippet_stack.push(snippet);
9936 return false;
9937 }
9938 }
9939 Bias::Right => {
9940 if snippet.active_index + 1 < snippet.ranges.len() {
9941 snippet.active_index += 1;
9942 } else {
9943 self.snippet_stack.push(snippet);
9944 return false;
9945 }
9946 }
9947 }
9948 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9949 self.change_selections(Default::default(), window, cx, |s| {
9950 // Reverse order so that the first range is the newest created selection.
9951 // Completions will use it and autoscroll will prioritize it.
9952 s.select_ranges(current_ranges.iter().rev().cloned())
9953 });
9954
9955 if let Some(choices) = &snippet.choices[snippet.active_index]
9956 && let Some(selection) = current_ranges.first()
9957 {
9958 self.show_snippet_choices(choices, selection.clone(), cx);
9959 }
9960
9961 // If snippet state is not at the last tabstop, push it back on the stack
9962 if snippet.active_index + 1 < snippet.ranges.len() {
9963 self.snippet_stack.push(snippet);
9964 }
9965 return true;
9966 }
9967 }
9968
9969 false
9970 }
9971
9972 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9973 self.transact(window, cx, |this, window, cx| {
9974 this.select_all(&SelectAll, window, cx);
9975 this.insert("", window, cx);
9976 });
9977 }
9978
9979 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9980 if self.read_only(cx) {
9981 return;
9982 }
9983 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9984 self.transact(window, cx, |this, window, cx| {
9985 this.select_autoclose_pair(window, cx);
9986 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9987 if !this.linked_edit_ranges.is_empty() {
9988 let selections = this.selections.all::<MultiBufferPoint>(cx);
9989 let snapshot = this.buffer.read(cx).snapshot(cx);
9990
9991 for selection in selections.iter() {
9992 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9993 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9994 if selection_start.buffer_id != selection_end.buffer_id {
9995 continue;
9996 }
9997 if let Some(ranges) =
9998 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9999 {
10000 for (buffer, entries) in ranges {
10001 linked_ranges.entry(buffer).or_default().extend(entries);
10002 }
10003 }
10004 }
10005 }
10006
10007 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
10008 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10009 for selection in &mut selections {
10010 if selection.is_empty() {
10011 let old_head = selection.head();
10012 let mut new_head =
10013 movement::left(&display_map, old_head.to_display_point(&display_map))
10014 .to_point(&display_map);
10015 if let Some((buffer, line_buffer_range)) = display_map
10016 .buffer_snapshot
10017 .buffer_line_for_row(MultiBufferRow(old_head.row))
10018 {
10019 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10020 let indent_len = match indent_size.kind {
10021 IndentKind::Space => {
10022 buffer.settings_at(line_buffer_range.start, cx).tab_size
10023 }
10024 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10025 };
10026 if old_head.column <= indent_size.len && old_head.column > 0 {
10027 let indent_len = indent_len.get();
10028 new_head = cmp::min(
10029 new_head,
10030 MultiBufferPoint::new(
10031 old_head.row,
10032 ((old_head.column - 1) / indent_len) * indent_len,
10033 ),
10034 );
10035 }
10036 }
10037
10038 selection.set_head(new_head, SelectionGoal::None);
10039 }
10040 }
10041
10042 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10043 this.insert("", window, cx);
10044 let empty_str: Arc<str> = Arc::from("");
10045 for (buffer, edits) in linked_ranges {
10046 let snapshot = buffer.read(cx).snapshot();
10047 use text::ToPoint as TP;
10048
10049 let edits = edits
10050 .into_iter()
10051 .map(|range| {
10052 let end_point = TP::to_point(&range.end, &snapshot);
10053 let mut start_point = TP::to_point(&range.start, &snapshot);
10054
10055 if end_point == start_point {
10056 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10057 .saturating_sub(1);
10058 start_point =
10059 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10060 };
10061
10062 (start_point..end_point, empty_str.clone())
10063 })
10064 .sorted_by_key(|(range, _)| range.start)
10065 .collect::<Vec<_>>();
10066 buffer.update(cx, |this, cx| {
10067 this.edit(edits, None, cx);
10068 })
10069 }
10070 this.refresh_edit_prediction(true, false, window, cx);
10071 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
10072 });
10073 }
10074
10075 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10076 if self.read_only(cx) {
10077 return;
10078 }
10079 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10080 self.transact(window, cx, |this, window, cx| {
10081 this.change_selections(Default::default(), window, cx, |s| {
10082 s.move_with(|map, selection| {
10083 if selection.is_empty() {
10084 let cursor = movement::right(map, selection.head());
10085 selection.end = cursor;
10086 selection.reversed = true;
10087 selection.goal = SelectionGoal::None;
10088 }
10089 })
10090 });
10091 this.insert("", window, cx);
10092 this.refresh_edit_prediction(true, false, window, cx);
10093 });
10094 }
10095
10096 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10097 if self.mode.is_single_line() {
10098 cx.propagate();
10099 return;
10100 }
10101
10102 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10103 if self.move_to_prev_snippet_tabstop(window, cx) {
10104 return;
10105 }
10106 self.outdent(&Outdent, window, cx);
10107 }
10108
10109 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10110 if self.mode.is_single_line() {
10111 cx.propagate();
10112 return;
10113 }
10114
10115 if self.move_to_next_snippet_tabstop(window, cx) {
10116 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10117 return;
10118 }
10119 if self.read_only(cx) {
10120 return;
10121 }
10122 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10123 let mut selections = self.selections.all_adjusted(cx);
10124 let buffer = self.buffer.read(cx);
10125 let snapshot = buffer.snapshot(cx);
10126 let rows_iter = selections.iter().map(|s| s.head().row);
10127 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10128
10129 let has_some_cursor_in_whitespace = selections
10130 .iter()
10131 .filter(|selection| selection.is_empty())
10132 .any(|selection| {
10133 let cursor = selection.head();
10134 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10135 cursor.column < current_indent.len
10136 });
10137
10138 let mut edits = Vec::new();
10139 let mut prev_edited_row = 0;
10140 let mut row_delta = 0;
10141 for selection in &mut selections {
10142 if selection.start.row != prev_edited_row {
10143 row_delta = 0;
10144 }
10145 prev_edited_row = selection.end.row;
10146
10147 // If the selection is non-empty, then increase the indentation of the selected lines.
10148 if !selection.is_empty() {
10149 row_delta =
10150 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10151 continue;
10152 }
10153
10154 let cursor = selection.head();
10155 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10156 if let Some(suggested_indent) =
10157 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10158 {
10159 // Don't do anything if already at suggested indent
10160 // and there is any other cursor which is not
10161 if has_some_cursor_in_whitespace
10162 && cursor.column == current_indent.len
10163 && current_indent.len == suggested_indent.len
10164 {
10165 continue;
10166 }
10167
10168 // Adjust line and move cursor to suggested indent
10169 // if cursor is not at suggested indent
10170 if cursor.column < suggested_indent.len
10171 && cursor.column <= current_indent.len
10172 && current_indent.len <= suggested_indent.len
10173 {
10174 selection.start = Point::new(cursor.row, suggested_indent.len);
10175 selection.end = selection.start;
10176 if row_delta == 0 {
10177 edits.extend(Buffer::edit_for_indent_size_adjustment(
10178 cursor.row,
10179 current_indent,
10180 suggested_indent,
10181 ));
10182 row_delta = suggested_indent.len - current_indent.len;
10183 }
10184 continue;
10185 }
10186
10187 // If current indent is more than suggested indent
10188 // only move cursor to current indent and skip indent
10189 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10190 selection.start = Point::new(cursor.row, current_indent.len);
10191 selection.end = selection.start;
10192 continue;
10193 }
10194 }
10195
10196 // Otherwise, insert a hard or soft tab.
10197 let settings = buffer.language_settings_at(cursor, cx);
10198 let tab_size = if settings.hard_tabs {
10199 IndentSize::tab()
10200 } else {
10201 let tab_size = settings.tab_size.get();
10202 let indent_remainder = snapshot
10203 .text_for_range(Point::new(cursor.row, 0)..cursor)
10204 .flat_map(str::chars)
10205 .fold(row_delta % tab_size, |counter: u32, c| {
10206 if c == '\t' {
10207 0
10208 } else {
10209 (counter + 1) % tab_size
10210 }
10211 });
10212
10213 let chars_to_next_tab_stop = tab_size - indent_remainder;
10214 IndentSize::spaces(chars_to_next_tab_stop)
10215 };
10216 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10217 selection.end = selection.start;
10218 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10219 row_delta += tab_size.len;
10220 }
10221
10222 self.transact(window, cx, |this, window, cx| {
10223 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10224 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10225 this.refresh_edit_prediction(true, false, window, cx);
10226 });
10227 }
10228
10229 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10230 if self.read_only(cx) {
10231 return;
10232 }
10233 if self.mode.is_single_line() {
10234 cx.propagate();
10235 return;
10236 }
10237
10238 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10239 let mut selections = self.selections.all::<Point>(cx);
10240 let mut prev_edited_row = 0;
10241 let mut row_delta = 0;
10242 let mut edits = Vec::new();
10243 let buffer = self.buffer.read(cx);
10244 let snapshot = buffer.snapshot(cx);
10245 for selection in &mut selections {
10246 if selection.start.row != prev_edited_row {
10247 row_delta = 0;
10248 }
10249 prev_edited_row = selection.end.row;
10250
10251 row_delta =
10252 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10253 }
10254
10255 self.transact(window, cx, |this, window, cx| {
10256 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10257 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10258 });
10259 }
10260
10261 fn indent_selection(
10262 buffer: &MultiBuffer,
10263 snapshot: &MultiBufferSnapshot,
10264 selection: &mut Selection<Point>,
10265 edits: &mut Vec<(Range<Point>, String)>,
10266 delta_for_start_row: u32,
10267 cx: &App,
10268 ) -> u32 {
10269 let settings = buffer.language_settings_at(selection.start, cx);
10270 let tab_size = settings.tab_size.get();
10271 let indent_kind = if settings.hard_tabs {
10272 IndentKind::Tab
10273 } else {
10274 IndentKind::Space
10275 };
10276 let mut start_row = selection.start.row;
10277 let mut end_row = selection.end.row + 1;
10278
10279 // If a selection ends at the beginning of a line, don't indent
10280 // that last line.
10281 if selection.end.column == 0 && selection.end.row > selection.start.row {
10282 end_row -= 1;
10283 }
10284
10285 // Avoid re-indenting a row that has already been indented by a
10286 // previous selection, but still update this selection's column
10287 // to reflect that indentation.
10288 if delta_for_start_row > 0 {
10289 start_row += 1;
10290 selection.start.column += delta_for_start_row;
10291 if selection.end.row == selection.start.row {
10292 selection.end.column += delta_for_start_row;
10293 }
10294 }
10295
10296 let mut delta_for_end_row = 0;
10297 let has_multiple_rows = start_row + 1 != end_row;
10298 for row in start_row..end_row {
10299 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10300 let indent_delta = match (current_indent.kind, indent_kind) {
10301 (IndentKind::Space, IndentKind::Space) => {
10302 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10303 IndentSize::spaces(columns_to_next_tab_stop)
10304 }
10305 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10306 (_, IndentKind::Tab) => IndentSize::tab(),
10307 };
10308
10309 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10310 0
10311 } else {
10312 selection.start.column
10313 };
10314 let row_start = Point::new(row, start);
10315 edits.push((
10316 row_start..row_start,
10317 indent_delta.chars().collect::<String>(),
10318 ));
10319
10320 // Update this selection's endpoints to reflect the indentation.
10321 if row == selection.start.row {
10322 selection.start.column += indent_delta.len;
10323 }
10324 if row == selection.end.row {
10325 selection.end.column += indent_delta.len;
10326 delta_for_end_row = indent_delta.len;
10327 }
10328 }
10329
10330 if selection.start.row == selection.end.row {
10331 delta_for_start_row + delta_for_end_row
10332 } else {
10333 delta_for_end_row
10334 }
10335 }
10336
10337 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10338 if self.read_only(cx) {
10339 return;
10340 }
10341 if self.mode.is_single_line() {
10342 cx.propagate();
10343 return;
10344 }
10345
10346 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10347 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10348 let selections = self.selections.all::<Point>(cx);
10349 let mut deletion_ranges = Vec::new();
10350 let mut last_outdent = None;
10351 {
10352 let buffer = self.buffer.read(cx);
10353 let snapshot = buffer.snapshot(cx);
10354 for selection in &selections {
10355 let settings = buffer.language_settings_at(selection.start, cx);
10356 let tab_size = settings.tab_size.get();
10357 let mut rows = selection.spanned_rows(false, &display_map);
10358
10359 // Avoid re-outdenting a row that has already been outdented by a
10360 // previous selection.
10361 if let Some(last_row) = last_outdent
10362 && last_row == rows.start
10363 {
10364 rows.start = rows.start.next_row();
10365 }
10366 let has_multiple_rows = rows.len() > 1;
10367 for row in rows.iter_rows() {
10368 let indent_size = snapshot.indent_size_for_line(row);
10369 if indent_size.len > 0 {
10370 let deletion_len = match indent_size.kind {
10371 IndentKind::Space => {
10372 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10373 if columns_to_prev_tab_stop == 0 {
10374 tab_size
10375 } else {
10376 columns_to_prev_tab_stop
10377 }
10378 }
10379 IndentKind::Tab => 1,
10380 };
10381 let start = if has_multiple_rows
10382 || deletion_len > selection.start.column
10383 || indent_size.len < selection.start.column
10384 {
10385 0
10386 } else {
10387 selection.start.column - deletion_len
10388 };
10389 deletion_ranges.push(
10390 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10391 );
10392 last_outdent = Some(row);
10393 }
10394 }
10395 }
10396 }
10397
10398 self.transact(window, cx, |this, window, cx| {
10399 this.buffer.update(cx, |buffer, cx| {
10400 let empty_str: Arc<str> = Arc::default();
10401 buffer.edit(
10402 deletion_ranges
10403 .into_iter()
10404 .map(|range| (range, empty_str.clone())),
10405 None,
10406 cx,
10407 );
10408 });
10409 let selections = this.selections.all::<usize>(cx);
10410 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10411 });
10412 }
10413
10414 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10415 if self.read_only(cx) {
10416 return;
10417 }
10418 if self.mode.is_single_line() {
10419 cx.propagate();
10420 return;
10421 }
10422
10423 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10424 let selections = self
10425 .selections
10426 .all::<usize>(cx)
10427 .into_iter()
10428 .map(|s| s.range());
10429
10430 self.transact(window, cx, |this, window, cx| {
10431 this.buffer.update(cx, |buffer, cx| {
10432 buffer.autoindent_ranges(selections, cx);
10433 });
10434 let selections = this.selections.all::<usize>(cx);
10435 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10436 });
10437 }
10438
10439 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10440 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10441 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10442 let selections = self.selections.all::<Point>(cx);
10443
10444 let mut new_cursors = Vec::new();
10445 let mut edit_ranges = Vec::new();
10446 let mut selections = selections.iter().peekable();
10447 while let Some(selection) = selections.next() {
10448 let mut rows = selection.spanned_rows(false, &display_map);
10449 let goal_display_column = selection.head().to_display_point(&display_map).column();
10450
10451 // Accumulate contiguous regions of rows that we want to delete.
10452 while let Some(next_selection) = selections.peek() {
10453 let next_rows = next_selection.spanned_rows(false, &display_map);
10454 if next_rows.start <= rows.end {
10455 rows.end = next_rows.end;
10456 selections.next().unwrap();
10457 } else {
10458 break;
10459 }
10460 }
10461
10462 let buffer = &display_map.buffer_snapshot;
10463 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10464 let edit_end;
10465 let cursor_buffer_row;
10466 if buffer.max_point().row >= rows.end.0 {
10467 // If there's a line after the range, delete the \n from the end of the row range
10468 // and position the cursor on the next line.
10469 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10470 cursor_buffer_row = rows.end;
10471 } else {
10472 // If there isn't a line after the range, delete the \n from the line before the
10473 // start of the row range and position the cursor there.
10474 edit_start = edit_start.saturating_sub(1);
10475 edit_end = buffer.len();
10476 cursor_buffer_row = rows.start.previous_row();
10477 }
10478
10479 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10480 *cursor.column_mut() =
10481 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10482
10483 new_cursors.push((
10484 selection.id,
10485 buffer.anchor_after(cursor.to_point(&display_map)),
10486 ));
10487 edit_ranges.push(edit_start..edit_end);
10488 }
10489
10490 self.transact(window, cx, |this, window, cx| {
10491 let buffer = this.buffer.update(cx, |buffer, cx| {
10492 let empty_str: Arc<str> = Arc::default();
10493 buffer.edit(
10494 edit_ranges
10495 .into_iter()
10496 .map(|range| (range, empty_str.clone())),
10497 None,
10498 cx,
10499 );
10500 buffer.snapshot(cx)
10501 });
10502 let new_selections = new_cursors
10503 .into_iter()
10504 .map(|(id, cursor)| {
10505 let cursor = cursor.to_point(&buffer);
10506 Selection {
10507 id,
10508 start: cursor,
10509 end: cursor,
10510 reversed: false,
10511 goal: SelectionGoal::None,
10512 }
10513 })
10514 .collect();
10515
10516 this.change_selections(Default::default(), window, cx, |s| {
10517 s.select(new_selections);
10518 });
10519 });
10520 }
10521
10522 pub fn join_lines_impl(
10523 &mut self,
10524 insert_whitespace: bool,
10525 window: &mut Window,
10526 cx: &mut Context<Self>,
10527 ) {
10528 if self.read_only(cx) {
10529 return;
10530 }
10531 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10532 for selection in self.selections.all::<Point>(cx) {
10533 let start = MultiBufferRow(selection.start.row);
10534 // Treat single line selections as if they include the next line. Otherwise this action
10535 // would do nothing for single line selections individual cursors.
10536 let end = if selection.start.row == selection.end.row {
10537 MultiBufferRow(selection.start.row + 1)
10538 } else {
10539 MultiBufferRow(selection.end.row)
10540 };
10541
10542 if let Some(last_row_range) = row_ranges.last_mut()
10543 && start <= last_row_range.end
10544 {
10545 last_row_range.end = end;
10546 continue;
10547 }
10548 row_ranges.push(start..end);
10549 }
10550
10551 let snapshot = self.buffer.read(cx).snapshot(cx);
10552 let mut cursor_positions = Vec::new();
10553 for row_range in &row_ranges {
10554 let anchor = snapshot.anchor_before(Point::new(
10555 row_range.end.previous_row().0,
10556 snapshot.line_len(row_range.end.previous_row()),
10557 ));
10558 cursor_positions.push(anchor..anchor);
10559 }
10560
10561 self.transact(window, cx, |this, window, cx| {
10562 for row_range in row_ranges.into_iter().rev() {
10563 for row in row_range.iter_rows().rev() {
10564 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10565 let next_line_row = row.next_row();
10566 let indent = snapshot.indent_size_for_line(next_line_row);
10567 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10568
10569 let replace =
10570 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10571 " "
10572 } else {
10573 ""
10574 };
10575
10576 this.buffer.update(cx, |buffer, cx| {
10577 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10578 });
10579 }
10580 }
10581
10582 this.change_selections(Default::default(), window, cx, |s| {
10583 s.select_anchor_ranges(cursor_positions)
10584 });
10585 });
10586 }
10587
10588 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10589 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10590 self.join_lines_impl(true, window, cx);
10591 }
10592
10593 pub fn sort_lines_case_sensitive(
10594 &mut self,
10595 _: &SortLinesCaseSensitive,
10596 window: &mut Window,
10597 cx: &mut Context<Self>,
10598 ) {
10599 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10600 }
10601
10602 pub fn sort_lines_by_length(
10603 &mut self,
10604 _: &SortLinesByLength,
10605 window: &mut Window,
10606 cx: &mut Context<Self>,
10607 ) {
10608 self.manipulate_immutable_lines(window, cx, |lines| {
10609 lines.sort_by_key(|&line| line.chars().count())
10610 })
10611 }
10612
10613 pub fn sort_lines_case_insensitive(
10614 &mut self,
10615 _: &SortLinesCaseInsensitive,
10616 window: &mut Window,
10617 cx: &mut Context<Self>,
10618 ) {
10619 self.manipulate_immutable_lines(window, cx, |lines| {
10620 lines.sort_by_key(|line| line.to_lowercase())
10621 })
10622 }
10623
10624 pub fn unique_lines_case_insensitive(
10625 &mut self,
10626 _: &UniqueLinesCaseInsensitive,
10627 window: &mut Window,
10628 cx: &mut Context<Self>,
10629 ) {
10630 self.manipulate_immutable_lines(window, cx, |lines| {
10631 let mut seen = HashSet::default();
10632 lines.retain(|line| seen.insert(line.to_lowercase()));
10633 })
10634 }
10635
10636 pub fn unique_lines_case_sensitive(
10637 &mut self,
10638 _: &UniqueLinesCaseSensitive,
10639 window: &mut Window,
10640 cx: &mut Context<Self>,
10641 ) {
10642 self.manipulate_immutable_lines(window, cx, |lines| {
10643 let mut seen = HashSet::default();
10644 lines.retain(|line| seen.insert(*line));
10645 })
10646 }
10647
10648 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10649 let snapshot = self.buffer.read(cx).snapshot(cx);
10650 for selection in self.selections.disjoint_anchors_arc().iter() {
10651 if snapshot
10652 .language_at(selection.start)
10653 .and_then(|lang| lang.config().wrap_characters.as_ref())
10654 .is_some()
10655 {
10656 return true;
10657 }
10658 }
10659 false
10660 }
10661
10662 fn wrap_selections_in_tag(
10663 &mut self,
10664 _: &WrapSelectionsInTag,
10665 window: &mut Window,
10666 cx: &mut Context<Self>,
10667 ) {
10668 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10669
10670 let snapshot = self.buffer.read(cx).snapshot(cx);
10671
10672 let mut edits = Vec::new();
10673 let mut boundaries = Vec::new();
10674
10675 for selection in self.selections.all::<Point>(cx).iter() {
10676 let Some(wrap_config) = snapshot
10677 .language_at(selection.start)
10678 .and_then(|lang| lang.config().wrap_characters.clone())
10679 else {
10680 continue;
10681 };
10682
10683 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10684 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10685
10686 let start_before = snapshot.anchor_before(selection.start);
10687 let end_after = snapshot.anchor_after(selection.end);
10688
10689 edits.push((start_before..start_before, open_tag));
10690 edits.push((end_after..end_after, close_tag));
10691
10692 boundaries.push((
10693 start_before,
10694 end_after,
10695 wrap_config.start_prefix.len(),
10696 wrap_config.end_suffix.len(),
10697 ));
10698 }
10699
10700 if edits.is_empty() {
10701 return;
10702 }
10703
10704 self.transact(window, cx, |this, window, cx| {
10705 let buffer = this.buffer.update(cx, |buffer, cx| {
10706 buffer.edit(edits, None, cx);
10707 buffer.snapshot(cx)
10708 });
10709
10710 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10711 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10712 boundaries.into_iter()
10713 {
10714 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10715 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10716 new_selections.push(open_offset..open_offset);
10717 new_selections.push(close_offset..close_offset);
10718 }
10719
10720 this.change_selections(Default::default(), window, cx, |s| {
10721 s.select_ranges(new_selections);
10722 });
10723
10724 this.request_autoscroll(Autoscroll::fit(), cx);
10725 });
10726 }
10727
10728 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10729 let Some(project) = self.project.clone() else {
10730 return;
10731 };
10732 self.reload(project, window, cx)
10733 .detach_and_notify_err(window, cx);
10734 }
10735
10736 pub fn restore_file(
10737 &mut self,
10738 _: &::git::RestoreFile,
10739 window: &mut Window,
10740 cx: &mut Context<Self>,
10741 ) {
10742 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10743 let mut buffer_ids = HashSet::default();
10744 let snapshot = self.buffer().read(cx).snapshot(cx);
10745 for selection in self.selections.all::<usize>(cx) {
10746 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10747 }
10748
10749 let buffer = self.buffer().read(cx);
10750 let ranges = buffer_ids
10751 .into_iter()
10752 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10753 .collect::<Vec<_>>();
10754
10755 self.restore_hunks_in_ranges(ranges, window, cx);
10756 }
10757
10758 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10759 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10760 let selections = self
10761 .selections
10762 .all(cx)
10763 .into_iter()
10764 .map(|s| s.range())
10765 .collect();
10766 self.restore_hunks_in_ranges(selections, window, cx);
10767 }
10768
10769 pub fn restore_hunks_in_ranges(
10770 &mut self,
10771 ranges: Vec<Range<Point>>,
10772 window: &mut Window,
10773 cx: &mut Context<Editor>,
10774 ) {
10775 let mut revert_changes = HashMap::default();
10776 let chunk_by = self
10777 .snapshot(window, cx)
10778 .hunks_for_ranges(ranges)
10779 .into_iter()
10780 .chunk_by(|hunk| hunk.buffer_id);
10781 for (buffer_id, hunks) in &chunk_by {
10782 let hunks = hunks.collect::<Vec<_>>();
10783 for hunk in &hunks {
10784 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10785 }
10786 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10787 }
10788 drop(chunk_by);
10789 if !revert_changes.is_empty() {
10790 self.transact(window, cx, |editor, window, cx| {
10791 editor.restore(revert_changes, window, cx);
10792 });
10793 }
10794 }
10795
10796 pub fn open_active_item_in_terminal(
10797 &mut self,
10798 _: &OpenInTerminal,
10799 window: &mut Window,
10800 cx: &mut Context<Self>,
10801 ) {
10802 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10803 let project_path = buffer.read(cx).project_path(cx)?;
10804 let project = self.project()?.read(cx);
10805 let entry = project.entry_for_path(&project_path, cx)?;
10806 let parent = match &entry.canonical_path {
10807 Some(canonical_path) => canonical_path.to_path_buf(),
10808 None => project.absolute_path(&project_path, cx)?,
10809 }
10810 .parent()?
10811 .to_path_buf();
10812 Some(parent)
10813 }) {
10814 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10815 }
10816 }
10817
10818 fn set_breakpoint_context_menu(
10819 &mut self,
10820 display_row: DisplayRow,
10821 position: Option<Anchor>,
10822 clicked_point: gpui::Point<Pixels>,
10823 window: &mut Window,
10824 cx: &mut Context<Self>,
10825 ) {
10826 let source = self
10827 .buffer
10828 .read(cx)
10829 .snapshot(cx)
10830 .anchor_before(Point::new(display_row.0, 0u32));
10831
10832 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10833
10834 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10835 self,
10836 source,
10837 clicked_point,
10838 context_menu,
10839 window,
10840 cx,
10841 );
10842 }
10843
10844 fn add_edit_breakpoint_block(
10845 &mut self,
10846 anchor: Anchor,
10847 breakpoint: &Breakpoint,
10848 edit_action: BreakpointPromptEditAction,
10849 window: &mut Window,
10850 cx: &mut Context<Self>,
10851 ) {
10852 let weak_editor = cx.weak_entity();
10853 let bp_prompt = cx.new(|cx| {
10854 BreakpointPromptEditor::new(
10855 weak_editor,
10856 anchor,
10857 breakpoint.clone(),
10858 edit_action,
10859 window,
10860 cx,
10861 )
10862 });
10863
10864 let height = bp_prompt.update(cx, |this, cx| {
10865 this.prompt
10866 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10867 });
10868 let cloned_prompt = bp_prompt.clone();
10869 let blocks = vec![BlockProperties {
10870 style: BlockStyle::Sticky,
10871 placement: BlockPlacement::Above(anchor),
10872 height: Some(height),
10873 render: Arc::new(move |cx| {
10874 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10875 cloned_prompt.clone().into_any_element()
10876 }),
10877 priority: 0,
10878 }];
10879
10880 let focus_handle = bp_prompt.focus_handle(cx);
10881 window.focus(&focus_handle);
10882
10883 let block_ids = self.insert_blocks(blocks, None, cx);
10884 bp_prompt.update(cx, |prompt, _| {
10885 prompt.add_block_ids(block_ids);
10886 });
10887 }
10888
10889 pub(crate) fn breakpoint_at_row(
10890 &self,
10891 row: u32,
10892 window: &mut Window,
10893 cx: &mut Context<Self>,
10894 ) -> Option<(Anchor, Breakpoint)> {
10895 let snapshot = self.snapshot(window, cx);
10896 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10897
10898 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10899 }
10900
10901 pub(crate) fn breakpoint_at_anchor(
10902 &self,
10903 breakpoint_position: Anchor,
10904 snapshot: &EditorSnapshot,
10905 cx: &mut Context<Self>,
10906 ) -> Option<(Anchor, Breakpoint)> {
10907 let buffer = self
10908 .buffer
10909 .read(cx)
10910 .buffer_for_anchor(breakpoint_position, cx)?;
10911
10912 let enclosing_excerpt = breakpoint_position.excerpt_id;
10913 let buffer_snapshot = buffer.read(cx).snapshot();
10914
10915 let row = buffer_snapshot
10916 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10917 .row;
10918
10919 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10920 let anchor_end = snapshot
10921 .buffer_snapshot
10922 .anchor_after(Point::new(row, line_len));
10923
10924 self.breakpoint_store
10925 .as_ref()?
10926 .read_with(cx, |breakpoint_store, cx| {
10927 breakpoint_store
10928 .breakpoints(
10929 &buffer,
10930 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10931 &buffer_snapshot,
10932 cx,
10933 )
10934 .next()
10935 .and_then(|(bp, _)| {
10936 let breakpoint_row = buffer_snapshot
10937 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10938 .row;
10939
10940 if breakpoint_row == row {
10941 snapshot
10942 .buffer_snapshot
10943 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10944 .map(|position| (position, bp.bp.clone()))
10945 } else {
10946 None
10947 }
10948 })
10949 })
10950 }
10951
10952 pub fn edit_log_breakpoint(
10953 &mut self,
10954 _: &EditLogBreakpoint,
10955 window: &mut Window,
10956 cx: &mut Context<Self>,
10957 ) {
10958 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10959 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10960 message: None,
10961 state: BreakpointState::Enabled,
10962 condition: None,
10963 hit_condition: None,
10964 });
10965
10966 self.add_edit_breakpoint_block(
10967 anchor,
10968 &breakpoint,
10969 BreakpointPromptEditAction::Log,
10970 window,
10971 cx,
10972 );
10973 }
10974 }
10975
10976 fn breakpoints_at_cursors(
10977 &self,
10978 window: &mut Window,
10979 cx: &mut Context<Self>,
10980 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10981 let snapshot = self.snapshot(window, cx);
10982 let cursors = self
10983 .selections
10984 .disjoint_anchors_arc()
10985 .iter()
10986 .map(|selection| {
10987 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10988
10989 let breakpoint_position = self
10990 .breakpoint_at_row(cursor_position.row, window, cx)
10991 .map(|bp| bp.0)
10992 .unwrap_or_else(|| {
10993 snapshot
10994 .display_snapshot
10995 .buffer_snapshot
10996 .anchor_after(Point::new(cursor_position.row, 0))
10997 });
10998
10999 let breakpoint = self
11000 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11001 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11002
11003 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11004 })
11005 // 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.
11006 .collect::<HashMap<Anchor, _>>();
11007
11008 cursors.into_iter().collect()
11009 }
11010
11011 pub fn enable_breakpoint(
11012 &mut self,
11013 _: &crate::actions::EnableBreakpoint,
11014 window: &mut Window,
11015 cx: &mut Context<Self>,
11016 ) {
11017 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11018 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11019 continue;
11020 };
11021 self.edit_breakpoint_at_anchor(
11022 anchor,
11023 breakpoint,
11024 BreakpointEditAction::InvertState,
11025 cx,
11026 );
11027 }
11028 }
11029
11030 pub fn disable_breakpoint(
11031 &mut self,
11032 _: &crate::actions::DisableBreakpoint,
11033 window: &mut Window,
11034 cx: &mut Context<Self>,
11035 ) {
11036 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11037 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11038 continue;
11039 };
11040 self.edit_breakpoint_at_anchor(
11041 anchor,
11042 breakpoint,
11043 BreakpointEditAction::InvertState,
11044 cx,
11045 );
11046 }
11047 }
11048
11049 pub fn toggle_breakpoint(
11050 &mut self,
11051 _: &crate::actions::ToggleBreakpoint,
11052 window: &mut Window,
11053 cx: &mut Context<Self>,
11054 ) {
11055 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11056 if let Some(breakpoint) = breakpoint {
11057 self.edit_breakpoint_at_anchor(
11058 anchor,
11059 breakpoint,
11060 BreakpointEditAction::Toggle,
11061 cx,
11062 );
11063 } else {
11064 self.edit_breakpoint_at_anchor(
11065 anchor,
11066 Breakpoint::new_standard(),
11067 BreakpointEditAction::Toggle,
11068 cx,
11069 );
11070 }
11071 }
11072 }
11073
11074 pub fn edit_breakpoint_at_anchor(
11075 &mut self,
11076 breakpoint_position: Anchor,
11077 breakpoint: Breakpoint,
11078 edit_action: BreakpointEditAction,
11079 cx: &mut Context<Self>,
11080 ) {
11081 let Some(breakpoint_store) = &self.breakpoint_store else {
11082 return;
11083 };
11084
11085 let Some(buffer) = self
11086 .buffer
11087 .read(cx)
11088 .buffer_for_anchor(breakpoint_position, cx)
11089 else {
11090 return;
11091 };
11092
11093 breakpoint_store.update(cx, |breakpoint_store, cx| {
11094 breakpoint_store.toggle_breakpoint(
11095 buffer,
11096 BreakpointWithPosition {
11097 position: breakpoint_position.text_anchor,
11098 bp: breakpoint,
11099 },
11100 edit_action,
11101 cx,
11102 );
11103 });
11104
11105 cx.notify();
11106 }
11107
11108 #[cfg(any(test, feature = "test-support"))]
11109 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11110 self.breakpoint_store.clone()
11111 }
11112
11113 pub fn prepare_restore_change(
11114 &self,
11115 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11116 hunk: &MultiBufferDiffHunk,
11117 cx: &mut App,
11118 ) -> Option<()> {
11119 if hunk.is_created_file() {
11120 return None;
11121 }
11122 let buffer = self.buffer.read(cx);
11123 let diff = buffer.diff_for(hunk.buffer_id)?;
11124 let buffer = buffer.buffer(hunk.buffer_id)?;
11125 let buffer = buffer.read(cx);
11126 let original_text = diff
11127 .read(cx)
11128 .base_text()
11129 .as_rope()
11130 .slice(hunk.diff_base_byte_range.clone());
11131 let buffer_snapshot = buffer.snapshot();
11132 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11133 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11134 probe
11135 .0
11136 .start
11137 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11138 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11139 }) {
11140 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11141 Some(())
11142 } else {
11143 None
11144 }
11145 }
11146
11147 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11148 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11149 }
11150
11151 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11152 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11153 }
11154
11155 fn manipulate_lines<M>(
11156 &mut self,
11157 window: &mut Window,
11158 cx: &mut Context<Self>,
11159 mut manipulate: M,
11160 ) where
11161 M: FnMut(&str) -> LineManipulationResult,
11162 {
11163 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11164
11165 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11166 let buffer = self.buffer.read(cx).snapshot(cx);
11167
11168 let mut edits = Vec::new();
11169
11170 let selections = self.selections.all::<Point>(cx);
11171 let mut selections = selections.iter().peekable();
11172 let mut contiguous_row_selections = Vec::new();
11173 let mut new_selections = Vec::new();
11174 let mut added_lines = 0;
11175 let mut removed_lines = 0;
11176
11177 while let Some(selection) = selections.next() {
11178 let (start_row, end_row) = consume_contiguous_rows(
11179 &mut contiguous_row_selections,
11180 selection,
11181 &display_map,
11182 &mut selections,
11183 );
11184
11185 let start_point = Point::new(start_row.0, 0);
11186 let end_point = Point::new(
11187 end_row.previous_row().0,
11188 buffer.line_len(end_row.previous_row()),
11189 );
11190 let text = buffer
11191 .text_for_range(start_point..end_point)
11192 .collect::<String>();
11193
11194 let LineManipulationResult {
11195 new_text,
11196 line_count_before,
11197 line_count_after,
11198 } = manipulate(&text);
11199
11200 edits.push((start_point..end_point, new_text));
11201
11202 // Selections must change based on added and removed line count
11203 let start_row =
11204 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11205 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11206 new_selections.push(Selection {
11207 id: selection.id,
11208 start: start_row,
11209 end: end_row,
11210 goal: SelectionGoal::None,
11211 reversed: selection.reversed,
11212 });
11213
11214 if line_count_after > line_count_before {
11215 added_lines += line_count_after - line_count_before;
11216 } else if line_count_before > line_count_after {
11217 removed_lines += line_count_before - line_count_after;
11218 }
11219 }
11220
11221 self.transact(window, cx, |this, window, cx| {
11222 let buffer = this.buffer.update(cx, |buffer, cx| {
11223 buffer.edit(edits, None, cx);
11224 buffer.snapshot(cx)
11225 });
11226
11227 // Recalculate offsets on newly edited buffer
11228 let new_selections = new_selections
11229 .iter()
11230 .map(|s| {
11231 let start_point = Point::new(s.start.0, 0);
11232 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11233 Selection {
11234 id: s.id,
11235 start: buffer.point_to_offset(start_point),
11236 end: buffer.point_to_offset(end_point),
11237 goal: s.goal,
11238 reversed: s.reversed,
11239 }
11240 })
11241 .collect();
11242
11243 this.change_selections(Default::default(), window, cx, |s| {
11244 s.select(new_selections);
11245 });
11246
11247 this.request_autoscroll(Autoscroll::fit(), cx);
11248 });
11249 }
11250
11251 fn manipulate_immutable_lines<Fn>(
11252 &mut self,
11253 window: &mut Window,
11254 cx: &mut Context<Self>,
11255 mut callback: Fn,
11256 ) where
11257 Fn: FnMut(&mut Vec<&str>),
11258 {
11259 self.manipulate_lines(window, cx, |text| {
11260 let mut lines: Vec<&str> = text.split('\n').collect();
11261 let line_count_before = lines.len();
11262
11263 callback(&mut lines);
11264
11265 LineManipulationResult {
11266 new_text: lines.join("\n"),
11267 line_count_before,
11268 line_count_after: lines.len(),
11269 }
11270 });
11271 }
11272
11273 fn manipulate_mutable_lines<Fn>(
11274 &mut self,
11275 window: &mut Window,
11276 cx: &mut Context<Self>,
11277 mut callback: Fn,
11278 ) where
11279 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11280 {
11281 self.manipulate_lines(window, cx, |text| {
11282 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11283 let line_count_before = lines.len();
11284
11285 callback(&mut lines);
11286
11287 LineManipulationResult {
11288 new_text: lines.join("\n"),
11289 line_count_before,
11290 line_count_after: lines.len(),
11291 }
11292 });
11293 }
11294
11295 pub fn convert_indentation_to_spaces(
11296 &mut self,
11297 _: &ConvertIndentationToSpaces,
11298 window: &mut Window,
11299 cx: &mut Context<Self>,
11300 ) {
11301 let settings = self.buffer.read(cx).language_settings(cx);
11302 let tab_size = settings.tab_size.get() as usize;
11303
11304 self.manipulate_mutable_lines(window, cx, |lines| {
11305 // Allocates a reasonably sized scratch buffer once for the whole loop
11306 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11307 // Avoids recomputing spaces that could be inserted many times
11308 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11309 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11310 .collect();
11311
11312 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11313 let mut chars = line.as_ref().chars();
11314 let mut col = 0;
11315 let mut changed = false;
11316
11317 for ch in chars.by_ref() {
11318 match ch {
11319 ' ' => {
11320 reindented_line.push(' ');
11321 col += 1;
11322 }
11323 '\t' => {
11324 // \t are converted to spaces depending on the current column
11325 let spaces_len = tab_size - (col % tab_size);
11326 reindented_line.extend(&space_cache[spaces_len - 1]);
11327 col += spaces_len;
11328 changed = true;
11329 }
11330 _ => {
11331 // If we dont append before break, the character is consumed
11332 reindented_line.push(ch);
11333 break;
11334 }
11335 }
11336 }
11337
11338 if !changed {
11339 reindented_line.clear();
11340 continue;
11341 }
11342 // Append the rest of the line and replace old reference with new one
11343 reindented_line.extend(chars);
11344 *line = Cow::Owned(reindented_line.clone());
11345 reindented_line.clear();
11346 }
11347 });
11348 }
11349
11350 pub fn convert_indentation_to_tabs(
11351 &mut self,
11352 _: &ConvertIndentationToTabs,
11353 window: &mut Window,
11354 cx: &mut Context<Self>,
11355 ) {
11356 let settings = self.buffer.read(cx).language_settings(cx);
11357 let tab_size = settings.tab_size.get() as usize;
11358
11359 self.manipulate_mutable_lines(window, cx, |lines| {
11360 // Allocates a reasonably sized buffer once for the whole loop
11361 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11362 // Avoids recomputing spaces that could be inserted many times
11363 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11364 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11365 .collect();
11366
11367 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11368 let mut chars = line.chars();
11369 let mut spaces_count = 0;
11370 let mut first_non_indent_char = None;
11371 let mut changed = false;
11372
11373 for ch in chars.by_ref() {
11374 match ch {
11375 ' ' => {
11376 // Keep track of spaces. Append \t when we reach tab_size
11377 spaces_count += 1;
11378 changed = true;
11379 if spaces_count == tab_size {
11380 reindented_line.push('\t');
11381 spaces_count = 0;
11382 }
11383 }
11384 '\t' => {
11385 reindented_line.push('\t');
11386 spaces_count = 0;
11387 }
11388 _ => {
11389 // Dont append it yet, we might have remaining spaces
11390 first_non_indent_char = Some(ch);
11391 break;
11392 }
11393 }
11394 }
11395
11396 if !changed {
11397 reindented_line.clear();
11398 continue;
11399 }
11400 // Remaining spaces that didn't make a full tab stop
11401 if spaces_count > 0 {
11402 reindented_line.extend(&space_cache[spaces_count - 1]);
11403 }
11404 // If we consume an extra character that was not indentation, add it back
11405 if let Some(extra_char) = first_non_indent_char {
11406 reindented_line.push(extra_char);
11407 }
11408 // Append the rest of the line and replace old reference with new one
11409 reindented_line.extend(chars);
11410 *line = Cow::Owned(reindented_line.clone());
11411 reindented_line.clear();
11412 }
11413 });
11414 }
11415
11416 pub fn convert_to_upper_case(
11417 &mut self,
11418 _: &ConvertToUpperCase,
11419 window: &mut Window,
11420 cx: &mut Context<Self>,
11421 ) {
11422 self.manipulate_text(window, cx, |text| text.to_uppercase())
11423 }
11424
11425 pub fn convert_to_lower_case(
11426 &mut self,
11427 _: &ConvertToLowerCase,
11428 window: &mut Window,
11429 cx: &mut Context<Self>,
11430 ) {
11431 self.manipulate_text(window, cx, |text| text.to_lowercase())
11432 }
11433
11434 pub fn convert_to_title_case(
11435 &mut self,
11436 _: &ConvertToTitleCase,
11437 window: &mut Window,
11438 cx: &mut Context<Self>,
11439 ) {
11440 self.manipulate_text(window, cx, |text| {
11441 text.split('\n')
11442 .map(|line| line.to_case(Case::Title))
11443 .join("\n")
11444 })
11445 }
11446
11447 pub fn convert_to_snake_case(
11448 &mut self,
11449 _: &ConvertToSnakeCase,
11450 window: &mut Window,
11451 cx: &mut Context<Self>,
11452 ) {
11453 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11454 }
11455
11456 pub fn convert_to_kebab_case(
11457 &mut self,
11458 _: &ConvertToKebabCase,
11459 window: &mut Window,
11460 cx: &mut Context<Self>,
11461 ) {
11462 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11463 }
11464
11465 pub fn convert_to_upper_camel_case(
11466 &mut self,
11467 _: &ConvertToUpperCamelCase,
11468 window: &mut Window,
11469 cx: &mut Context<Self>,
11470 ) {
11471 self.manipulate_text(window, cx, |text| {
11472 text.split('\n')
11473 .map(|line| line.to_case(Case::UpperCamel))
11474 .join("\n")
11475 })
11476 }
11477
11478 pub fn convert_to_lower_camel_case(
11479 &mut self,
11480 _: &ConvertToLowerCamelCase,
11481 window: &mut Window,
11482 cx: &mut Context<Self>,
11483 ) {
11484 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11485 }
11486
11487 pub fn convert_to_opposite_case(
11488 &mut self,
11489 _: &ConvertToOppositeCase,
11490 window: &mut Window,
11491 cx: &mut Context<Self>,
11492 ) {
11493 self.manipulate_text(window, cx, |text| {
11494 text.chars()
11495 .fold(String::with_capacity(text.len()), |mut t, c| {
11496 if c.is_uppercase() {
11497 t.extend(c.to_lowercase());
11498 } else {
11499 t.extend(c.to_uppercase());
11500 }
11501 t
11502 })
11503 })
11504 }
11505
11506 pub fn convert_to_sentence_case(
11507 &mut self,
11508 _: &ConvertToSentenceCase,
11509 window: &mut Window,
11510 cx: &mut Context<Self>,
11511 ) {
11512 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11513 }
11514
11515 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11516 self.manipulate_text(window, cx, |text| {
11517 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11518 if has_upper_case_characters {
11519 text.to_lowercase()
11520 } else {
11521 text.to_uppercase()
11522 }
11523 })
11524 }
11525
11526 pub fn convert_to_rot13(
11527 &mut self,
11528 _: &ConvertToRot13,
11529 window: &mut Window,
11530 cx: &mut Context<Self>,
11531 ) {
11532 self.manipulate_text(window, cx, |text| {
11533 text.chars()
11534 .map(|c| match c {
11535 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11536 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11537 _ => c,
11538 })
11539 .collect()
11540 })
11541 }
11542
11543 pub fn convert_to_rot47(
11544 &mut self,
11545 _: &ConvertToRot47,
11546 window: &mut Window,
11547 cx: &mut Context<Self>,
11548 ) {
11549 self.manipulate_text(window, cx, |text| {
11550 text.chars()
11551 .map(|c| {
11552 let code_point = c as u32;
11553 if code_point >= 33 && code_point <= 126 {
11554 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11555 }
11556 c
11557 })
11558 .collect()
11559 })
11560 }
11561
11562 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11563 where
11564 Fn: FnMut(&str) -> String,
11565 {
11566 let buffer = self.buffer.read(cx).snapshot(cx);
11567
11568 let mut new_selections = Vec::new();
11569 let mut edits = Vec::new();
11570 let mut selection_adjustment = 0i32;
11571
11572 for selection in self.selections.all_adjusted(cx) {
11573 let selection_is_empty = selection.is_empty();
11574
11575 let (start, end) = if selection_is_empty {
11576 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11577 (word_range.start, word_range.end)
11578 } else {
11579 (
11580 buffer.point_to_offset(selection.start),
11581 buffer.point_to_offset(selection.end),
11582 )
11583 };
11584
11585 let text = buffer.text_for_range(start..end).collect::<String>();
11586 let old_length = text.len() as i32;
11587 let text = callback(&text);
11588
11589 new_selections.push(Selection {
11590 start: (start as i32 - selection_adjustment) as usize,
11591 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11592 goal: SelectionGoal::None,
11593 id: selection.id,
11594 reversed: selection.reversed,
11595 });
11596
11597 selection_adjustment += old_length - text.len() as i32;
11598
11599 edits.push((start..end, text));
11600 }
11601
11602 self.transact(window, cx, |this, window, cx| {
11603 this.buffer.update(cx, |buffer, cx| {
11604 buffer.edit(edits, None, cx);
11605 });
11606
11607 this.change_selections(Default::default(), window, cx, |s| {
11608 s.select(new_selections);
11609 });
11610
11611 this.request_autoscroll(Autoscroll::fit(), cx);
11612 });
11613 }
11614
11615 pub fn move_selection_on_drop(
11616 &mut self,
11617 selection: &Selection<Anchor>,
11618 target: DisplayPoint,
11619 is_cut: bool,
11620 window: &mut Window,
11621 cx: &mut Context<Self>,
11622 ) {
11623 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11624 let buffer = &display_map.buffer_snapshot;
11625 let mut edits = Vec::new();
11626 let insert_point = display_map
11627 .clip_point(target, Bias::Left)
11628 .to_point(&display_map);
11629 let text = buffer
11630 .text_for_range(selection.start..selection.end)
11631 .collect::<String>();
11632 if is_cut {
11633 edits.push(((selection.start..selection.end), String::new()));
11634 }
11635 let insert_anchor = buffer.anchor_before(insert_point);
11636 edits.push(((insert_anchor..insert_anchor), text));
11637 let last_edit_start = insert_anchor.bias_left(buffer);
11638 let last_edit_end = insert_anchor.bias_right(buffer);
11639 self.transact(window, cx, |this, window, cx| {
11640 this.buffer.update(cx, |buffer, cx| {
11641 buffer.edit(edits, None, cx);
11642 });
11643 this.change_selections(Default::default(), window, cx, |s| {
11644 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11645 });
11646 });
11647 }
11648
11649 pub fn clear_selection_drag_state(&mut self) {
11650 self.selection_drag_state = SelectionDragState::None;
11651 }
11652
11653 pub fn duplicate(
11654 &mut self,
11655 upwards: bool,
11656 whole_lines: bool,
11657 window: &mut Window,
11658 cx: &mut Context<Self>,
11659 ) {
11660 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11661
11662 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11663 let buffer = &display_map.buffer_snapshot;
11664 let selections = self.selections.all::<Point>(cx);
11665
11666 let mut edits = Vec::new();
11667 let mut selections_iter = selections.iter().peekable();
11668 while let Some(selection) = selections_iter.next() {
11669 let mut rows = selection.spanned_rows(false, &display_map);
11670 // duplicate line-wise
11671 if whole_lines || selection.start == selection.end {
11672 // Avoid duplicating the same lines twice.
11673 while let Some(next_selection) = selections_iter.peek() {
11674 let next_rows = next_selection.spanned_rows(false, &display_map);
11675 if next_rows.start < rows.end {
11676 rows.end = next_rows.end;
11677 selections_iter.next().unwrap();
11678 } else {
11679 break;
11680 }
11681 }
11682
11683 // Copy the text from the selected row region and splice it either at the start
11684 // or end of the region.
11685 let start = Point::new(rows.start.0, 0);
11686 let end = Point::new(
11687 rows.end.previous_row().0,
11688 buffer.line_len(rows.end.previous_row()),
11689 );
11690 let text = buffer
11691 .text_for_range(start..end)
11692 .chain(Some("\n"))
11693 .collect::<String>();
11694 let insert_location = if upwards {
11695 Point::new(rows.end.0, 0)
11696 } else {
11697 start
11698 };
11699 edits.push((insert_location..insert_location, text));
11700 } else {
11701 // duplicate character-wise
11702 let start = selection.start;
11703 let end = selection.end;
11704 let text = buffer.text_for_range(start..end).collect::<String>();
11705 edits.push((selection.end..selection.end, text));
11706 }
11707 }
11708
11709 self.transact(window, cx, |this, _, cx| {
11710 this.buffer.update(cx, |buffer, cx| {
11711 buffer.edit(edits, None, cx);
11712 });
11713
11714 this.request_autoscroll(Autoscroll::fit(), cx);
11715 });
11716 }
11717
11718 pub fn duplicate_line_up(
11719 &mut self,
11720 _: &DuplicateLineUp,
11721 window: &mut Window,
11722 cx: &mut Context<Self>,
11723 ) {
11724 self.duplicate(true, true, window, cx);
11725 }
11726
11727 pub fn duplicate_line_down(
11728 &mut self,
11729 _: &DuplicateLineDown,
11730 window: &mut Window,
11731 cx: &mut Context<Self>,
11732 ) {
11733 self.duplicate(false, true, window, cx);
11734 }
11735
11736 pub fn duplicate_selection(
11737 &mut self,
11738 _: &DuplicateSelection,
11739 window: &mut Window,
11740 cx: &mut Context<Self>,
11741 ) {
11742 self.duplicate(false, false, window, cx);
11743 }
11744
11745 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11746 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11747 if self.mode.is_single_line() {
11748 cx.propagate();
11749 return;
11750 }
11751
11752 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11753 let buffer = self.buffer.read(cx).snapshot(cx);
11754
11755 let mut edits = Vec::new();
11756 let mut unfold_ranges = Vec::new();
11757 let mut refold_creases = Vec::new();
11758
11759 let selections = self.selections.all::<Point>(cx);
11760 let mut selections = selections.iter().peekable();
11761 let mut contiguous_row_selections = Vec::new();
11762 let mut new_selections = Vec::new();
11763
11764 while let Some(selection) = selections.next() {
11765 // Find all the selections that span a contiguous row range
11766 let (start_row, end_row) = consume_contiguous_rows(
11767 &mut contiguous_row_selections,
11768 selection,
11769 &display_map,
11770 &mut selections,
11771 );
11772
11773 // Move the text spanned by the row range to be before the line preceding the row range
11774 if start_row.0 > 0 {
11775 let range_to_move = Point::new(
11776 start_row.previous_row().0,
11777 buffer.line_len(start_row.previous_row()),
11778 )
11779 ..Point::new(
11780 end_row.previous_row().0,
11781 buffer.line_len(end_row.previous_row()),
11782 );
11783 let insertion_point = display_map
11784 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11785 .0;
11786
11787 // Don't move lines across excerpts
11788 if buffer
11789 .excerpt_containing(insertion_point..range_to_move.end)
11790 .is_some()
11791 {
11792 let text = buffer
11793 .text_for_range(range_to_move.clone())
11794 .flat_map(|s| s.chars())
11795 .skip(1)
11796 .chain(['\n'])
11797 .collect::<String>();
11798
11799 edits.push((
11800 buffer.anchor_after(range_to_move.start)
11801 ..buffer.anchor_before(range_to_move.end),
11802 String::new(),
11803 ));
11804 let insertion_anchor = buffer.anchor_after(insertion_point);
11805 edits.push((insertion_anchor..insertion_anchor, text));
11806
11807 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11808
11809 // Move selections up
11810 new_selections.extend(contiguous_row_selections.drain(..).map(
11811 |mut selection| {
11812 selection.start.row -= row_delta;
11813 selection.end.row -= row_delta;
11814 selection
11815 },
11816 ));
11817
11818 // Move folds up
11819 unfold_ranges.push(range_to_move.clone());
11820 for fold in display_map.folds_in_range(
11821 buffer.anchor_before(range_to_move.start)
11822 ..buffer.anchor_after(range_to_move.end),
11823 ) {
11824 let mut start = fold.range.start.to_point(&buffer);
11825 let mut end = fold.range.end.to_point(&buffer);
11826 start.row -= row_delta;
11827 end.row -= row_delta;
11828 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11829 }
11830 }
11831 }
11832
11833 // If we didn't move line(s), preserve the existing selections
11834 new_selections.append(&mut contiguous_row_selections);
11835 }
11836
11837 self.transact(window, cx, |this, window, cx| {
11838 this.unfold_ranges(&unfold_ranges, true, true, cx);
11839 this.buffer.update(cx, |buffer, cx| {
11840 for (range, text) in edits {
11841 buffer.edit([(range, text)], None, cx);
11842 }
11843 });
11844 this.fold_creases(refold_creases, true, window, cx);
11845 this.change_selections(Default::default(), window, cx, |s| {
11846 s.select(new_selections);
11847 })
11848 });
11849 }
11850
11851 pub fn move_line_down(
11852 &mut self,
11853 _: &MoveLineDown,
11854 window: &mut Window,
11855 cx: &mut Context<Self>,
11856 ) {
11857 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11858 if self.mode.is_single_line() {
11859 cx.propagate();
11860 return;
11861 }
11862
11863 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11864 let buffer = self.buffer.read(cx).snapshot(cx);
11865
11866 let mut edits = Vec::new();
11867 let mut unfold_ranges = Vec::new();
11868 let mut refold_creases = Vec::new();
11869
11870 let selections = self.selections.all::<Point>(cx);
11871 let mut selections = selections.iter().peekable();
11872 let mut contiguous_row_selections = Vec::new();
11873 let mut new_selections = Vec::new();
11874
11875 while let Some(selection) = selections.next() {
11876 // Find all the selections that span a contiguous row range
11877 let (start_row, end_row) = consume_contiguous_rows(
11878 &mut contiguous_row_selections,
11879 selection,
11880 &display_map,
11881 &mut selections,
11882 );
11883
11884 // Move the text spanned by the row range to be after the last line of the row range
11885 if end_row.0 <= buffer.max_point().row {
11886 let range_to_move =
11887 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11888 let insertion_point = display_map
11889 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11890 .0;
11891
11892 // Don't move lines across excerpt boundaries
11893 if buffer
11894 .excerpt_containing(range_to_move.start..insertion_point)
11895 .is_some()
11896 {
11897 let mut text = String::from("\n");
11898 text.extend(buffer.text_for_range(range_to_move.clone()));
11899 text.pop(); // Drop trailing newline
11900 edits.push((
11901 buffer.anchor_after(range_to_move.start)
11902 ..buffer.anchor_before(range_to_move.end),
11903 String::new(),
11904 ));
11905 let insertion_anchor = buffer.anchor_after(insertion_point);
11906 edits.push((insertion_anchor..insertion_anchor, text));
11907
11908 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11909
11910 // Move selections down
11911 new_selections.extend(contiguous_row_selections.drain(..).map(
11912 |mut selection| {
11913 selection.start.row += row_delta;
11914 selection.end.row += row_delta;
11915 selection
11916 },
11917 ));
11918
11919 // Move folds down
11920 unfold_ranges.push(range_to_move.clone());
11921 for fold in display_map.folds_in_range(
11922 buffer.anchor_before(range_to_move.start)
11923 ..buffer.anchor_after(range_to_move.end),
11924 ) {
11925 let mut start = fold.range.start.to_point(&buffer);
11926 let mut end = fold.range.end.to_point(&buffer);
11927 start.row += row_delta;
11928 end.row += row_delta;
11929 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11930 }
11931 }
11932 }
11933
11934 // If we didn't move line(s), preserve the existing selections
11935 new_selections.append(&mut contiguous_row_selections);
11936 }
11937
11938 self.transact(window, cx, |this, window, cx| {
11939 this.unfold_ranges(&unfold_ranges, true, true, cx);
11940 this.buffer.update(cx, |buffer, cx| {
11941 for (range, text) in edits {
11942 buffer.edit([(range, text)], None, cx);
11943 }
11944 });
11945 this.fold_creases(refold_creases, true, window, cx);
11946 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11947 });
11948 }
11949
11950 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11951 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11952 let text_layout_details = &self.text_layout_details(window);
11953 self.transact(window, cx, |this, window, cx| {
11954 let edits = this.change_selections(Default::default(), window, cx, |s| {
11955 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11956 s.move_with(|display_map, selection| {
11957 if !selection.is_empty() {
11958 return;
11959 }
11960
11961 let mut head = selection.head();
11962 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11963 if head.column() == display_map.line_len(head.row()) {
11964 transpose_offset = display_map
11965 .buffer_snapshot
11966 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11967 }
11968
11969 if transpose_offset == 0 {
11970 return;
11971 }
11972
11973 *head.column_mut() += 1;
11974 head = display_map.clip_point(head, Bias::Right);
11975 let goal = SelectionGoal::HorizontalPosition(
11976 display_map
11977 .x_for_display_point(head, text_layout_details)
11978 .into(),
11979 );
11980 selection.collapse_to(head, goal);
11981
11982 let transpose_start = display_map
11983 .buffer_snapshot
11984 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11985 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11986 let transpose_end = display_map
11987 .buffer_snapshot
11988 .clip_offset(transpose_offset + 1, Bias::Right);
11989 if let Some(ch) =
11990 display_map.buffer_snapshot.chars_at(transpose_start).next()
11991 {
11992 edits.push((transpose_start..transpose_offset, String::new()));
11993 edits.push((transpose_end..transpose_end, ch.to_string()));
11994 }
11995 }
11996 });
11997 edits
11998 });
11999 this.buffer
12000 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12001 let selections = this.selections.all::<usize>(cx);
12002 this.change_selections(Default::default(), window, cx, |s| {
12003 s.select(selections);
12004 });
12005 });
12006 }
12007
12008 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12009 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12010 if self.mode.is_single_line() {
12011 cx.propagate();
12012 return;
12013 }
12014
12015 self.rewrap_impl(RewrapOptions::default(), cx)
12016 }
12017
12018 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12019 let buffer = self.buffer.read(cx).snapshot(cx);
12020 let selections = self.selections.all::<Point>(cx);
12021
12022 #[derive(Clone, Debug, PartialEq)]
12023 enum CommentFormat {
12024 /// single line comment, with prefix for line
12025 Line(String),
12026 /// single line within a block comment, with prefix for line
12027 BlockLine(String),
12028 /// a single line of a block comment that includes the initial delimiter
12029 BlockCommentWithStart(BlockCommentConfig),
12030 /// a single line of a block comment that includes the ending delimiter
12031 BlockCommentWithEnd(BlockCommentConfig),
12032 }
12033
12034 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12035 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12036 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12037 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12038 .peekable();
12039
12040 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12041 row
12042 } else {
12043 return Vec::new();
12044 };
12045
12046 let language_settings = buffer.language_settings_at(selection.head(), cx);
12047 let language_scope = buffer.language_scope_at(selection.head());
12048
12049 let indent_and_prefix_for_row =
12050 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12051 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12052 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12053 &language_scope
12054 {
12055 let indent_end = Point::new(row, indent.len);
12056 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12057 let line_text_after_indent = buffer
12058 .text_for_range(indent_end..line_end)
12059 .collect::<String>();
12060
12061 let is_within_comment_override = buffer
12062 .language_scope_at(indent_end)
12063 .is_some_and(|scope| scope.override_name() == Some("comment"));
12064 let comment_delimiters = if is_within_comment_override {
12065 // we are within a comment syntax node, but we don't
12066 // yet know what kind of comment: block, doc or line
12067 match (
12068 language_scope.documentation_comment(),
12069 language_scope.block_comment(),
12070 ) {
12071 (Some(config), _) | (_, Some(config))
12072 if buffer.contains_str_at(indent_end, &config.start) =>
12073 {
12074 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12075 }
12076 (Some(config), _) | (_, Some(config))
12077 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12078 {
12079 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12080 }
12081 (Some(config), _) | (_, Some(config))
12082 if buffer.contains_str_at(indent_end, &config.prefix) =>
12083 {
12084 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12085 }
12086 (_, _) => language_scope
12087 .line_comment_prefixes()
12088 .iter()
12089 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12090 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12091 }
12092 } else {
12093 // we not in an overridden comment node, but we may
12094 // be within a non-overridden line comment node
12095 language_scope
12096 .line_comment_prefixes()
12097 .iter()
12098 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12099 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12100 };
12101
12102 let rewrap_prefix = language_scope
12103 .rewrap_prefixes()
12104 .iter()
12105 .find_map(|prefix_regex| {
12106 prefix_regex.find(&line_text_after_indent).map(|mat| {
12107 if mat.start() == 0 {
12108 Some(mat.as_str().to_string())
12109 } else {
12110 None
12111 }
12112 })
12113 })
12114 .flatten();
12115 (comment_delimiters, rewrap_prefix)
12116 } else {
12117 (None, None)
12118 };
12119 (indent, comment_prefix, rewrap_prefix)
12120 };
12121
12122 let mut ranges = Vec::new();
12123 let from_empty_selection = selection.is_empty();
12124
12125 let mut current_range_start = first_row;
12126 let mut prev_row = first_row;
12127 let (
12128 mut current_range_indent,
12129 mut current_range_comment_delimiters,
12130 mut current_range_rewrap_prefix,
12131 ) = indent_and_prefix_for_row(first_row);
12132
12133 for row in non_blank_rows_iter.skip(1) {
12134 let has_paragraph_break = row > prev_row + 1;
12135
12136 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12137 indent_and_prefix_for_row(row);
12138
12139 let has_indent_change = row_indent != current_range_indent;
12140 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12141
12142 let has_boundary_change = has_comment_change
12143 || row_rewrap_prefix.is_some()
12144 || (has_indent_change && current_range_comment_delimiters.is_some());
12145
12146 if has_paragraph_break || has_boundary_change {
12147 ranges.push((
12148 language_settings.clone(),
12149 Point::new(current_range_start, 0)
12150 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12151 current_range_indent,
12152 current_range_comment_delimiters.clone(),
12153 current_range_rewrap_prefix.clone(),
12154 from_empty_selection,
12155 ));
12156 current_range_start = row;
12157 current_range_indent = row_indent;
12158 current_range_comment_delimiters = row_comment_delimiters;
12159 current_range_rewrap_prefix = row_rewrap_prefix;
12160 }
12161 prev_row = row;
12162 }
12163
12164 ranges.push((
12165 language_settings.clone(),
12166 Point::new(current_range_start, 0)
12167 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12168 current_range_indent,
12169 current_range_comment_delimiters,
12170 current_range_rewrap_prefix,
12171 from_empty_selection,
12172 ));
12173
12174 ranges
12175 });
12176
12177 let mut edits = Vec::new();
12178 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12179
12180 for (
12181 language_settings,
12182 wrap_range,
12183 mut indent_size,
12184 comment_prefix,
12185 rewrap_prefix,
12186 from_empty_selection,
12187 ) in wrap_ranges
12188 {
12189 let mut start_row = wrap_range.start.row;
12190 let mut end_row = wrap_range.end.row;
12191
12192 // Skip selections that overlap with a range that has already been rewrapped.
12193 let selection_range = start_row..end_row;
12194 if rewrapped_row_ranges
12195 .iter()
12196 .any(|range| range.overlaps(&selection_range))
12197 {
12198 continue;
12199 }
12200
12201 let tab_size = language_settings.tab_size;
12202
12203 let (line_prefix, inside_comment) = match &comment_prefix {
12204 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12205 (Some(prefix.as_str()), true)
12206 }
12207 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12208 (Some(prefix.as_ref()), true)
12209 }
12210 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12211 start: _,
12212 end: _,
12213 prefix,
12214 tab_size,
12215 })) => {
12216 indent_size.len += tab_size;
12217 (Some(prefix.as_ref()), true)
12218 }
12219 None => (None, false),
12220 };
12221 let indent_prefix = indent_size.chars().collect::<String>();
12222 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12223
12224 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12225 RewrapBehavior::InComments => inside_comment,
12226 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12227 RewrapBehavior::Anywhere => true,
12228 };
12229
12230 let should_rewrap = options.override_language_settings
12231 || allow_rewrap_based_on_language
12232 || self.hard_wrap.is_some();
12233 if !should_rewrap {
12234 continue;
12235 }
12236
12237 if from_empty_selection {
12238 'expand_upwards: while start_row > 0 {
12239 let prev_row = start_row - 1;
12240 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12241 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12242 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12243 {
12244 start_row = prev_row;
12245 } else {
12246 break 'expand_upwards;
12247 }
12248 }
12249
12250 'expand_downwards: while end_row < buffer.max_point().row {
12251 let next_row = end_row + 1;
12252 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12253 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12254 && !buffer.is_line_blank(MultiBufferRow(next_row))
12255 {
12256 end_row = next_row;
12257 } else {
12258 break 'expand_downwards;
12259 }
12260 }
12261 }
12262
12263 let start = Point::new(start_row, 0);
12264 let start_offset = start.to_offset(&buffer);
12265 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12266 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12267 let mut first_line_delimiter = None;
12268 let mut last_line_delimiter = None;
12269 let Some(lines_without_prefixes) = selection_text
12270 .lines()
12271 .enumerate()
12272 .map(|(ix, line)| {
12273 let line_trimmed = line.trim_start();
12274 if rewrap_prefix.is_some() && ix > 0 {
12275 Ok(line_trimmed)
12276 } else if let Some(
12277 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12278 start,
12279 prefix,
12280 end,
12281 tab_size,
12282 })
12283 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12284 start,
12285 prefix,
12286 end,
12287 tab_size,
12288 }),
12289 ) = &comment_prefix
12290 {
12291 let line_trimmed = line_trimmed
12292 .strip_prefix(start.as_ref())
12293 .map(|s| {
12294 let mut indent_size = indent_size;
12295 indent_size.len -= tab_size;
12296 let indent_prefix: String = indent_size.chars().collect();
12297 first_line_delimiter = Some((indent_prefix, start));
12298 s.trim_start()
12299 })
12300 .unwrap_or(line_trimmed);
12301 let line_trimmed = line_trimmed
12302 .strip_suffix(end.as_ref())
12303 .map(|s| {
12304 last_line_delimiter = Some(end);
12305 s.trim_end()
12306 })
12307 .unwrap_or(line_trimmed);
12308 let line_trimmed = line_trimmed
12309 .strip_prefix(prefix.as_ref())
12310 .unwrap_or(line_trimmed);
12311 Ok(line_trimmed)
12312 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12313 line_trimmed.strip_prefix(prefix).with_context(|| {
12314 format!("line did not start with prefix {prefix:?}: {line:?}")
12315 })
12316 } else {
12317 line_trimmed
12318 .strip_prefix(&line_prefix.trim_start())
12319 .with_context(|| {
12320 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12321 })
12322 }
12323 })
12324 .collect::<Result<Vec<_>, _>>()
12325 .log_err()
12326 else {
12327 continue;
12328 };
12329
12330 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12331 buffer
12332 .language_settings_at(Point::new(start_row, 0), cx)
12333 .preferred_line_length as usize
12334 });
12335
12336 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12337 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12338 } else {
12339 line_prefix.clone()
12340 };
12341
12342 let wrapped_text = {
12343 let mut wrapped_text = wrap_with_prefix(
12344 line_prefix,
12345 subsequent_lines_prefix,
12346 lines_without_prefixes.join("\n"),
12347 wrap_column,
12348 tab_size,
12349 options.preserve_existing_whitespace,
12350 );
12351
12352 if let Some((indent, delimiter)) = first_line_delimiter {
12353 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12354 }
12355 if let Some(last_line) = last_line_delimiter {
12356 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12357 }
12358
12359 wrapped_text
12360 };
12361
12362 // TODO: should always use char-based diff while still supporting cursor behavior that
12363 // matches vim.
12364 let mut diff_options = DiffOptions::default();
12365 if options.override_language_settings {
12366 diff_options.max_word_diff_len = 0;
12367 diff_options.max_word_diff_line_count = 0;
12368 } else {
12369 diff_options.max_word_diff_len = usize::MAX;
12370 diff_options.max_word_diff_line_count = usize::MAX;
12371 }
12372
12373 for (old_range, new_text) in
12374 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12375 {
12376 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12377 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12378 edits.push((edit_start..edit_end, new_text));
12379 }
12380
12381 rewrapped_row_ranges.push(start_row..=end_row);
12382 }
12383
12384 self.buffer
12385 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12386 }
12387
12388 pub fn cut_common(
12389 &mut self,
12390 cut_no_selection_line: bool,
12391 window: &mut Window,
12392 cx: &mut Context<Self>,
12393 ) -> ClipboardItem {
12394 let mut text = String::new();
12395 let buffer = self.buffer.read(cx).snapshot(cx);
12396 let mut selections = self.selections.all::<Point>(cx);
12397 let mut clipboard_selections = Vec::with_capacity(selections.len());
12398 {
12399 let max_point = buffer.max_point();
12400 let mut is_first = true;
12401 for selection in &mut selections {
12402 let is_entire_line =
12403 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12404 if is_entire_line {
12405 selection.start = Point::new(selection.start.row, 0);
12406 if !selection.is_empty() && selection.end.column == 0 {
12407 selection.end = cmp::min(max_point, selection.end);
12408 } else {
12409 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12410 }
12411 selection.goal = SelectionGoal::None;
12412 }
12413 if is_first {
12414 is_first = false;
12415 } else {
12416 text += "\n";
12417 }
12418 let mut len = 0;
12419 for chunk in buffer.text_for_range(selection.start..selection.end) {
12420 text.push_str(chunk);
12421 len += chunk.len();
12422 }
12423 clipboard_selections.push(ClipboardSelection {
12424 len,
12425 is_entire_line,
12426 first_line_indent: buffer
12427 .indent_size_for_line(MultiBufferRow(selection.start.row))
12428 .len,
12429 });
12430 }
12431 }
12432
12433 self.transact(window, cx, |this, window, cx| {
12434 this.change_selections(Default::default(), window, cx, |s| {
12435 s.select(selections);
12436 });
12437 this.insert("", window, cx);
12438 });
12439 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12440 }
12441
12442 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12443 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12444 let item = self.cut_common(true, window, cx);
12445 cx.write_to_clipboard(item);
12446 }
12447
12448 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12449 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12450 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12451 s.move_with(|snapshot, sel| {
12452 if sel.is_empty() {
12453 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12454 }
12455 if sel.is_empty() {
12456 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12457 }
12458 });
12459 });
12460 let item = self.cut_common(false, window, cx);
12461 cx.set_global(KillRing(item))
12462 }
12463
12464 pub fn kill_ring_yank(
12465 &mut self,
12466 _: &KillRingYank,
12467 window: &mut Window,
12468 cx: &mut Context<Self>,
12469 ) {
12470 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12471 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12472 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12473 (kill_ring.text().to_string(), kill_ring.metadata_json())
12474 } else {
12475 return;
12476 }
12477 } else {
12478 return;
12479 };
12480 self.do_paste(&text, metadata, false, window, cx);
12481 }
12482
12483 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12484 self.do_copy(true, cx);
12485 }
12486
12487 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12488 self.do_copy(false, cx);
12489 }
12490
12491 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12492 let selections = self.selections.all::<Point>(cx);
12493 let buffer = self.buffer.read(cx).read(cx);
12494 let mut text = String::new();
12495
12496 let mut clipboard_selections = Vec::with_capacity(selections.len());
12497 {
12498 let max_point = buffer.max_point();
12499 let mut is_first = true;
12500 for selection in &selections {
12501 let mut start = selection.start;
12502 let mut end = selection.end;
12503 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12504 if is_entire_line {
12505 start = Point::new(start.row, 0);
12506 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12507 }
12508
12509 let mut trimmed_selections = Vec::new();
12510 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12511 let row = MultiBufferRow(start.row);
12512 let first_indent = buffer.indent_size_for_line(row);
12513 if first_indent.len == 0 || start.column > first_indent.len {
12514 trimmed_selections.push(start..end);
12515 } else {
12516 trimmed_selections.push(
12517 Point::new(row.0, first_indent.len)
12518 ..Point::new(row.0, buffer.line_len(row)),
12519 );
12520 for row in start.row + 1..=end.row {
12521 let mut line_len = buffer.line_len(MultiBufferRow(row));
12522 if row == end.row {
12523 line_len = end.column;
12524 }
12525 if line_len == 0 {
12526 trimmed_selections
12527 .push(Point::new(row, 0)..Point::new(row, line_len));
12528 continue;
12529 }
12530 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12531 if row_indent_size.len >= first_indent.len {
12532 trimmed_selections.push(
12533 Point::new(row, first_indent.len)..Point::new(row, line_len),
12534 );
12535 } else {
12536 trimmed_selections.clear();
12537 trimmed_selections.push(start..end);
12538 break;
12539 }
12540 }
12541 }
12542 } else {
12543 trimmed_selections.push(start..end);
12544 }
12545
12546 for trimmed_range in trimmed_selections {
12547 if is_first {
12548 is_first = false;
12549 } else {
12550 text += "\n";
12551 }
12552 let mut len = 0;
12553 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12554 text.push_str(chunk);
12555 len += chunk.len();
12556 }
12557 clipboard_selections.push(ClipboardSelection {
12558 len,
12559 is_entire_line,
12560 first_line_indent: buffer
12561 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12562 .len,
12563 });
12564 }
12565 }
12566 }
12567
12568 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12569 text,
12570 clipboard_selections,
12571 ));
12572 }
12573
12574 pub fn do_paste(
12575 &mut self,
12576 text: &String,
12577 clipboard_selections: Option<Vec<ClipboardSelection>>,
12578 handle_entire_lines: bool,
12579 window: &mut Window,
12580 cx: &mut Context<Self>,
12581 ) {
12582 if self.read_only(cx) {
12583 return;
12584 }
12585
12586 let clipboard_text = Cow::Borrowed(text.as_str());
12587
12588 self.transact(window, cx, |this, window, cx| {
12589 let had_active_edit_prediction = this.has_active_edit_prediction();
12590 let old_selections = this.selections.all::<usize>(cx);
12591 let cursor_offset = this.selections.last::<usize>(cx).head();
12592
12593 if let Some(mut clipboard_selections) = clipboard_selections {
12594 let all_selections_were_entire_line =
12595 clipboard_selections.iter().all(|s| s.is_entire_line);
12596 let first_selection_indent_column =
12597 clipboard_selections.first().map(|s| s.first_line_indent);
12598 if clipboard_selections.len() != old_selections.len() {
12599 clipboard_selections.drain(..);
12600 }
12601 let mut auto_indent_on_paste = true;
12602
12603 this.buffer.update(cx, |buffer, cx| {
12604 let snapshot = buffer.read(cx);
12605 auto_indent_on_paste = snapshot
12606 .language_settings_at(cursor_offset, cx)
12607 .auto_indent_on_paste;
12608
12609 let mut start_offset = 0;
12610 let mut edits = Vec::new();
12611 let mut original_indent_columns = Vec::new();
12612 for (ix, selection) in old_selections.iter().enumerate() {
12613 let to_insert;
12614 let entire_line;
12615 let original_indent_column;
12616 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12617 let end_offset = start_offset + clipboard_selection.len;
12618 to_insert = &clipboard_text[start_offset..end_offset];
12619 entire_line = clipboard_selection.is_entire_line;
12620 start_offset = end_offset + 1;
12621 original_indent_column = Some(clipboard_selection.first_line_indent);
12622 } else {
12623 to_insert = &*clipboard_text;
12624 entire_line = all_selections_were_entire_line;
12625 original_indent_column = first_selection_indent_column
12626 }
12627
12628 let (range, to_insert) =
12629 if selection.is_empty() && handle_entire_lines && entire_line {
12630 // If the corresponding selection was empty when this slice of the
12631 // clipboard text was written, then the entire line containing the
12632 // selection was copied. If this selection is also currently empty,
12633 // then paste the line before the current line of the buffer.
12634 let column = selection.start.to_point(&snapshot).column as usize;
12635 let line_start = selection.start - column;
12636 (line_start..line_start, Cow::Borrowed(to_insert))
12637 } else {
12638 let language = snapshot.language_at(selection.head());
12639 let range = selection.range();
12640 if let Some(language) = language
12641 && language.name() == "Markdown".into()
12642 {
12643 edit_for_markdown_paste(
12644 &snapshot,
12645 range,
12646 to_insert,
12647 url::Url::parse(to_insert).ok(),
12648 )
12649 } else {
12650 (range, Cow::Borrowed(to_insert))
12651 }
12652 };
12653
12654 edits.push((range, to_insert));
12655 original_indent_columns.push(original_indent_column);
12656 }
12657 drop(snapshot);
12658
12659 buffer.edit(
12660 edits,
12661 if auto_indent_on_paste {
12662 Some(AutoindentMode::Block {
12663 original_indent_columns,
12664 })
12665 } else {
12666 None
12667 },
12668 cx,
12669 );
12670 });
12671
12672 let selections = this.selections.all::<usize>(cx);
12673 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12674 } else {
12675 let url = url::Url::parse(&clipboard_text).ok();
12676
12677 let auto_indent_mode = if !clipboard_text.is_empty() {
12678 Some(AutoindentMode::Block {
12679 original_indent_columns: Vec::new(),
12680 })
12681 } else {
12682 None
12683 };
12684
12685 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12686 let snapshot = buffer.snapshot(cx);
12687
12688 let anchors = old_selections
12689 .iter()
12690 .map(|s| {
12691 let anchor = snapshot.anchor_after(s.head());
12692 s.map(|_| anchor)
12693 })
12694 .collect::<Vec<_>>();
12695
12696 let mut edits = Vec::new();
12697
12698 for selection in old_selections.iter() {
12699 let language = snapshot.language_at(selection.head());
12700 let range = selection.range();
12701
12702 let (edit_range, edit_text) = if let Some(language) = language
12703 && language.name() == "Markdown".into()
12704 {
12705 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12706 } else {
12707 (range, clipboard_text.clone())
12708 };
12709
12710 edits.push((edit_range, edit_text));
12711 }
12712
12713 drop(snapshot);
12714 buffer.edit(edits, auto_indent_mode, cx);
12715
12716 anchors
12717 });
12718
12719 this.change_selections(Default::default(), window, cx, |s| {
12720 s.select_anchors(selection_anchors);
12721 });
12722 }
12723
12724 let trigger_in_words =
12725 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12726
12727 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12728 });
12729 }
12730
12731 pub fn diff_clipboard_with_selection(
12732 &mut self,
12733 _: &DiffClipboardWithSelection,
12734 window: &mut Window,
12735 cx: &mut Context<Self>,
12736 ) {
12737 let selections = self.selections.all::<usize>(cx);
12738
12739 if selections.is_empty() {
12740 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12741 return;
12742 };
12743
12744 let clipboard_text = match cx.read_from_clipboard() {
12745 Some(item) => match item.entries().first() {
12746 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12747 _ => None,
12748 },
12749 None => None,
12750 };
12751
12752 let Some(clipboard_text) = clipboard_text else {
12753 log::warn!("Clipboard doesn't contain text.");
12754 return;
12755 };
12756
12757 window.dispatch_action(
12758 Box::new(DiffClipboardWithSelectionData {
12759 clipboard_text,
12760 editor: cx.entity(),
12761 }),
12762 cx,
12763 );
12764 }
12765
12766 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12767 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12768 if let Some(item) = cx.read_from_clipboard() {
12769 let entries = item.entries();
12770
12771 match entries.first() {
12772 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12773 // of all the pasted entries.
12774 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12775 .do_paste(
12776 clipboard_string.text(),
12777 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12778 true,
12779 window,
12780 cx,
12781 ),
12782 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12783 }
12784 }
12785 }
12786
12787 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12788 if self.read_only(cx) {
12789 return;
12790 }
12791
12792 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12793
12794 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12795 if let Some((selections, _)) =
12796 self.selection_history.transaction(transaction_id).cloned()
12797 {
12798 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12799 s.select_anchors(selections.to_vec());
12800 });
12801 } else {
12802 log::error!(
12803 "No entry in selection_history found for undo. \
12804 This may correspond to a bug where undo does not update the selection. \
12805 If this is occurring, please add details to \
12806 https://github.com/zed-industries/zed/issues/22692"
12807 );
12808 }
12809 self.request_autoscroll(Autoscroll::fit(), cx);
12810 self.unmark_text(window, cx);
12811 self.refresh_edit_prediction(true, false, window, cx);
12812 cx.emit(EditorEvent::Edited { transaction_id });
12813 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12814 }
12815 }
12816
12817 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12818 if self.read_only(cx) {
12819 return;
12820 }
12821
12822 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12823
12824 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12825 if let Some((_, Some(selections))) =
12826 self.selection_history.transaction(transaction_id).cloned()
12827 {
12828 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12829 s.select_anchors(selections.to_vec());
12830 });
12831 } else {
12832 log::error!(
12833 "No entry in selection_history found for redo. \
12834 This may correspond to a bug where undo does not update the selection. \
12835 If this is occurring, please add details to \
12836 https://github.com/zed-industries/zed/issues/22692"
12837 );
12838 }
12839 self.request_autoscroll(Autoscroll::fit(), cx);
12840 self.unmark_text(window, cx);
12841 self.refresh_edit_prediction(true, false, window, cx);
12842 cx.emit(EditorEvent::Edited { transaction_id });
12843 }
12844 }
12845
12846 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12847 self.buffer
12848 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12849 }
12850
12851 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12852 self.buffer
12853 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12854 }
12855
12856 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12857 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12858 self.change_selections(Default::default(), window, cx, |s| {
12859 s.move_with(|map, selection| {
12860 let cursor = if selection.is_empty() {
12861 movement::left(map, selection.start)
12862 } else {
12863 selection.start
12864 };
12865 selection.collapse_to(cursor, SelectionGoal::None);
12866 });
12867 })
12868 }
12869
12870 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12871 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12872 self.change_selections(Default::default(), window, cx, |s| {
12873 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12874 })
12875 }
12876
12877 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12878 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12879 self.change_selections(Default::default(), window, cx, |s| {
12880 s.move_with(|map, selection| {
12881 let cursor = if selection.is_empty() {
12882 movement::right(map, selection.end)
12883 } else {
12884 selection.end
12885 };
12886 selection.collapse_to(cursor, SelectionGoal::None)
12887 });
12888 })
12889 }
12890
12891 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12892 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12893 self.change_selections(Default::default(), window, cx, |s| {
12894 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12895 });
12896 }
12897
12898 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12899 if self.take_rename(true, window, cx).is_some() {
12900 return;
12901 }
12902
12903 if self.mode.is_single_line() {
12904 cx.propagate();
12905 return;
12906 }
12907
12908 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12909
12910 let text_layout_details = &self.text_layout_details(window);
12911 let selection_count = self.selections.count();
12912 let first_selection = self.selections.first_anchor();
12913
12914 self.change_selections(Default::default(), window, cx, |s| {
12915 s.move_with(|map, selection| {
12916 if !selection.is_empty() {
12917 selection.goal = SelectionGoal::None;
12918 }
12919 let (cursor, goal) = movement::up(
12920 map,
12921 selection.start,
12922 selection.goal,
12923 false,
12924 text_layout_details,
12925 );
12926 selection.collapse_to(cursor, goal);
12927 });
12928 });
12929
12930 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12931 {
12932 cx.propagate();
12933 }
12934 }
12935
12936 pub fn move_up_by_lines(
12937 &mut self,
12938 action: &MoveUpByLines,
12939 window: &mut Window,
12940 cx: &mut Context<Self>,
12941 ) {
12942 if self.take_rename(true, window, cx).is_some() {
12943 return;
12944 }
12945
12946 if self.mode.is_single_line() {
12947 cx.propagate();
12948 return;
12949 }
12950
12951 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12952
12953 let text_layout_details = &self.text_layout_details(window);
12954
12955 self.change_selections(Default::default(), window, cx, |s| {
12956 s.move_with(|map, selection| {
12957 if !selection.is_empty() {
12958 selection.goal = SelectionGoal::None;
12959 }
12960 let (cursor, goal) = movement::up_by_rows(
12961 map,
12962 selection.start,
12963 action.lines,
12964 selection.goal,
12965 false,
12966 text_layout_details,
12967 );
12968 selection.collapse_to(cursor, goal);
12969 });
12970 })
12971 }
12972
12973 pub fn move_down_by_lines(
12974 &mut self,
12975 action: &MoveDownByLines,
12976 window: &mut Window,
12977 cx: &mut Context<Self>,
12978 ) {
12979 if self.take_rename(true, window, cx).is_some() {
12980 return;
12981 }
12982
12983 if self.mode.is_single_line() {
12984 cx.propagate();
12985 return;
12986 }
12987
12988 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12989
12990 let text_layout_details = &self.text_layout_details(window);
12991
12992 self.change_selections(Default::default(), window, cx, |s| {
12993 s.move_with(|map, selection| {
12994 if !selection.is_empty() {
12995 selection.goal = SelectionGoal::None;
12996 }
12997 let (cursor, goal) = movement::down_by_rows(
12998 map,
12999 selection.start,
13000 action.lines,
13001 selection.goal,
13002 false,
13003 text_layout_details,
13004 );
13005 selection.collapse_to(cursor, goal);
13006 });
13007 })
13008 }
13009
13010 pub fn select_down_by_lines(
13011 &mut self,
13012 action: &SelectDownByLines,
13013 window: &mut Window,
13014 cx: &mut Context<Self>,
13015 ) {
13016 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13017 let text_layout_details = &self.text_layout_details(window);
13018 self.change_selections(Default::default(), window, cx, |s| {
13019 s.move_heads_with(|map, head, goal| {
13020 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13021 })
13022 })
13023 }
13024
13025 pub fn select_up_by_lines(
13026 &mut self,
13027 action: &SelectUpByLines,
13028 window: &mut Window,
13029 cx: &mut Context<Self>,
13030 ) {
13031 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13032 let text_layout_details = &self.text_layout_details(window);
13033 self.change_selections(Default::default(), window, cx, |s| {
13034 s.move_heads_with(|map, head, goal| {
13035 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13036 })
13037 })
13038 }
13039
13040 pub fn select_page_up(
13041 &mut self,
13042 _: &SelectPageUp,
13043 window: &mut Window,
13044 cx: &mut Context<Self>,
13045 ) {
13046 let Some(row_count) = self.visible_row_count() else {
13047 return;
13048 };
13049
13050 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13051
13052 let text_layout_details = &self.text_layout_details(window);
13053
13054 self.change_selections(Default::default(), window, cx, |s| {
13055 s.move_heads_with(|map, head, goal| {
13056 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13057 })
13058 })
13059 }
13060
13061 pub fn move_page_up(
13062 &mut self,
13063 action: &MovePageUp,
13064 window: &mut Window,
13065 cx: &mut Context<Self>,
13066 ) {
13067 if self.take_rename(true, window, cx).is_some() {
13068 return;
13069 }
13070
13071 if self
13072 .context_menu
13073 .borrow_mut()
13074 .as_mut()
13075 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13076 .unwrap_or(false)
13077 {
13078 return;
13079 }
13080
13081 if matches!(self.mode, EditorMode::SingleLine) {
13082 cx.propagate();
13083 return;
13084 }
13085
13086 let Some(row_count) = self.visible_row_count() else {
13087 return;
13088 };
13089
13090 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13091
13092 let effects = if action.center_cursor {
13093 SelectionEffects::scroll(Autoscroll::center())
13094 } else {
13095 SelectionEffects::default()
13096 };
13097
13098 let text_layout_details = &self.text_layout_details(window);
13099
13100 self.change_selections(effects, window, cx, |s| {
13101 s.move_with(|map, selection| {
13102 if !selection.is_empty() {
13103 selection.goal = SelectionGoal::None;
13104 }
13105 let (cursor, goal) = movement::up_by_rows(
13106 map,
13107 selection.end,
13108 row_count,
13109 selection.goal,
13110 false,
13111 text_layout_details,
13112 );
13113 selection.collapse_to(cursor, goal);
13114 });
13115 });
13116 }
13117
13118 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13119 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13120 let text_layout_details = &self.text_layout_details(window);
13121 self.change_selections(Default::default(), window, cx, |s| {
13122 s.move_heads_with(|map, head, goal| {
13123 movement::up(map, head, goal, false, text_layout_details)
13124 })
13125 })
13126 }
13127
13128 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13129 self.take_rename(true, window, cx);
13130
13131 if self.mode.is_single_line() {
13132 cx.propagate();
13133 return;
13134 }
13135
13136 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13137
13138 let text_layout_details = &self.text_layout_details(window);
13139 let selection_count = self.selections.count();
13140 let first_selection = self.selections.first_anchor();
13141
13142 self.change_selections(Default::default(), window, cx, |s| {
13143 s.move_with(|map, selection| {
13144 if !selection.is_empty() {
13145 selection.goal = SelectionGoal::None;
13146 }
13147 let (cursor, goal) = movement::down(
13148 map,
13149 selection.end,
13150 selection.goal,
13151 false,
13152 text_layout_details,
13153 );
13154 selection.collapse_to(cursor, goal);
13155 });
13156 });
13157
13158 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13159 {
13160 cx.propagate();
13161 }
13162 }
13163
13164 pub fn select_page_down(
13165 &mut self,
13166 _: &SelectPageDown,
13167 window: &mut Window,
13168 cx: &mut Context<Self>,
13169 ) {
13170 let Some(row_count) = self.visible_row_count() else {
13171 return;
13172 };
13173
13174 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13175
13176 let text_layout_details = &self.text_layout_details(window);
13177
13178 self.change_selections(Default::default(), window, cx, |s| {
13179 s.move_heads_with(|map, head, goal| {
13180 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13181 })
13182 })
13183 }
13184
13185 pub fn move_page_down(
13186 &mut self,
13187 action: &MovePageDown,
13188 window: &mut Window,
13189 cx: &mut Context<Self>,
13190 ) {
13191 if self.take_rename(true, window, cx).is_some() {
13192 return;
13193 }
13194
13195 if self
13196 .context_menu
13197 .borrow_mut()
13198 .as_mut()
13199 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13200 .unwrap_or(false)
13201 {
13202 return;
13203 }
13204
13205 if matches!(self.mode, EditorMode::SingleLine) {
13206 cx.propagate();
13207 return;
13208 }
13209
13210 let Some(row_count) = self.visible_row_count() else {
13211 return;
13212 };
13213
13214 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13215
13216 let effects = if action.center_cursor {
13217 SelectionEffects::scroll(Autoscroll::center())
13218 } else {
13219 SelectionEffects::default()
13220 };
13221
13222 let text_layout_details = &self.text_layout_details(window);
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::down_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_down(&mut self, _: &SelectDown, 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::down(map, head, goal, false, text_layout_details)
13247 })
13248 });
13249 }
13250
13251 pub fn context_menu_first(
13252 &mut self,
13253 _: &ContextMenuFirst,
13254 window: &mut Window,
13255 cx: &mut Context<Self>,
13256 ) {
13257 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13258 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13259 }
13260 }
13261
13262 pub fn context_menu_prev(
13263 &mut self,
13264 _: &ContextMenuPrevious,
13265 window: &mut Window,
13266 cx: &mut Context<Self>,
13267 ) {
13268 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13269 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13270 }
13271 }
13272
13273 pub fn context_menu_next(
13274 &mut self,
13275 _: &ContextMenuNext,
13276 window: &mut Window,
13277 cx: &mut Context<Self>,
13278 ) {
13279 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13280 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13281 }
13282 }
13283
13284 pub fn context_menu_last(
13285 &mut self,
13286 _: &ContextMenuLast,
13287 window: &mut Window,
13288 cx: &mut Context<Self>,
13289 ) {
13290 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13291 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13292 }
13293 }
13294
13295 pub fn signature_help_prev(
13296 &mut self,
13297 _: &SignatureHelpPrevious,
13298 _: &mut Window,
13299 cx: &mut Context<Self>,
13300 ) {
13301 if let Some(popover) = self.signature_help_state.popover_mut() {
13302 if popover.current_signature == 0 {
13303 popover.current_signature = popover.signatures.len() - 1;
13304 } else {
13305 popover.current_signature -= 1;
13306 }
13307 cx.notify();
13308 }
13309 }
13310
13311 pub fn signature_help_next(
13312 &mut self,
13313 _: &SignatureHelpNext,
13314 _: &mut Window,
13315 cx: &mut Context<Self>,
13316 ) {
13317 if let Some(popover) = self.signature_help_state.popover_mut() {
13318 if popover.current_signature + 1 == popover.signatures.len() {
13319 popover.current_signature = 0;
13320 } else {
13321 popover.current_signature += 1;
13322 }
13323 cx.notify();
13324 }
13325 }
13326
13327 pub fn move_to_previous_word_start(
13328 &mut self,
13329 _: &MoveToPreviousWordStart,
13330 window: &mut Window,
13331 cx: &mut Context<Self>,
13332 ) {
13333 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13334 self.change_selections(Default::default(), window, cx, |s| {
13335 s.move_cursors_with(|map, head, _| {
13336 (
13337 movement::previous_word_start(map, head),
13338 SelectionGoal::None,
13339 )
13340 });
13341 })
13342 }
13343
13344 pub fn move_to_previous_subword_start(
13345 &mut self,
13346 _: &MoveToPreviousSubwordStart,
13347 window: &mut Window,
13348 cx: &mut Context<Self>,
13349 ) {
13350 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13351 self.change_selections(Default::default(), window, cx, |s| {
13352 s.move_cursors_with(|map, head, _| {
13353 (
13354 movement::previous_subword_start(map, head),
13355 SelectionGoal::None,
13356 )
13357 });
13358 })
13359 }
13360
13361 pub fn select_to_previous_word_start(
13362 &mut self,
13363 _: &SelectToPreviousWordStart,
13364 window: &mut Window,
13365 cx: &mut Context<Self>,
13366 ) {
13367 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13368 self.change_selections(Default::default(), window, cx, |s| {
13369 s.move_heads_with(|map, head, _| {
13370 (
13371 movement::previous_word_start(map, head),
13372 SelectionGoal::None,
13373 )
13374 });
13375 })
13376 }
13377
13378 pub fn select_to_previous_subword_start(
13379 &mut self,
13380 _: &SelectToPreviousSubwordStart,
13381 window: &mut Window,
13382 cx: &mut Context<Self>,
13383 ) {
13384 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13385 self.change_selections(Default::default(), window, cx, |s| {
13386 s.move_heads_with(|map, head, _| {
13387 (
13388 movement::previous_subword_start(map, head),
13389 SelectionGoal::None,
13390 )
13391 });
13392 })
13393 }
13394
13395 pub fn delete_to_previous_word_start(
13396 &mut self,
13397 action: &DeleteToPreviousWordStart,
13398 window: &mut Window,
13399 cx: &mut Context<Self>,
13400 ) {
13401 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13402 self.transact(window, cx, |this, window, cx| {
13403 this.select_autoclose_pair(window, cx);
13404 this.change_selections(Default::default(), window, cx, |s| {
13405 s.move_with(|map, selection| {
13406 if selection.is_empty() {
13407 let mut cursor = if action.ignore_newlines {
13408 movement::previous_word_start(map, selection.head())
13409 } else {
13410 movement::previous_word_start_or_newline(map, selection.head())
13411 };
13412 cursor = movement::adjust_greedy_deletion(
13413 map,
13414 selection.head(),
13415 cursor,
13416 action.ignore_brackets,
13417 );
13418 selection.set_head(cursor, SelectionGoal::None);
13419 }
13420 });
13421 });
13422 this.insert("", window, cx);
13423 });
13424 }
13425
13426 pub fn delete_to_previous_subword_start(
13427 &mut self,
13428 _: &DeleteToPreviousSubwordStart,
13429 window: &mut Window,
13430 cx: &mut Context<Self>,
13431 ) {
13432 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13433 self.transact(window, cx, |this, window, cx| {
13434 this.select_autoclose_pair(window, cx);
13435 this.change_selections(Default::default(), window, cx, |s| {
13436 s.move_with(|map, selection| {
13437 if selection.is_empty() {
13438 let mut cursor = movement::previous_subword_start(map, selection.head());
13439 cursor =
13440 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13441 selection.set_head(cursor, SelectionGoal::None);
13442 }
13443 });
13444 });
13445 this.insert("", window, cx);
13446 });
13447 }
13448
13449 pub fn move_to_next_word_end(
13450 &mut self,
13451 _: &MoveToNextWordEnd,
13452 window: &mut Window,
13453 cx: &mut Context<Self>,
13454 ) {
13455 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13456 self.change_selections(Default::default(), window, cx, |s| {
13457 s.move_cursors_with(|map, head, _| {
13458 (movement::next_word_end(map, head), SelectionGoal::None)
13459 });
13460 })
13461 }
13462
13463 pub fn move_to_next_subword_end(
13464 &mut self,
13465 _: &MoveToNextSubwordEnd,
13466 window: &mut Window,
13467 cx: &mut Context<Self>,
13468 ) {
13469 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13470 self.change_selections(Default::default(), window, cx, |s| {
13471 s.move_cursors_with(|map, head, _| {
13472 (movement::next_subword_end(map, head), SelectionGoal::None)
13473 });
13474 })
13475 }
13476
13477 pub fn select_to_next_word_end(
13478 &mut self,
13479 _: &SelectToNextWordEnd,
13480 window: &mut Window,
13481 cx: &mut Context<Self>,
13482 ) {
13483 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13484 self.change_selections(Default::default(), window, cx, |s| {
13485 s.move_heads_with(|map, head, _| {
13486 (movement::next_word_end(map, head), SelectionGoal::None)
13487 });
13488 })
13489 }
13490
13491 pub fn select_to_next_subword_end(
13492 &mut self,
13493 _: &SelectToNextSubwordEnd,
13494 window: &mut Window,
13495 cx: &mut Context<Self>,
13496 ) {
13497 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13498 self.change_selections(Default::default(), window, cx, |s| {
13499 s.move_heads_with(|map, head, _| {
13500 (movement::next_subword_end(map, head), SelectionGoal::None)
13501 });
13502 })
13503 }
13504
13505 pub fn delete_to_next_word_end(
13506 &mut self,
13507 action: &DeleteToNextWordEnd,
13508 window: &mut Window,
13509 cx: &mut Context<Self>,
13510 ) {
13511 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13512 self.transact(window, cx, |this, window, cx| {
13513 this.change_selections(Default::default(), window, cx, |s| {
13514 s.move_with(|map, selection| {
13515 if selection.is_empty() {
13516 let mut cursor = if action.ignore_newlines {
13517 movement::next_word_end(map, selection.head())
13518 } else {
13519 movement::next_word_end_or_newline(map, selection.head())
13520 };
13521 cursor = movement::adjust_greedy_deletion(
13522 map,
13523 selection.head(),
13524 cursor,
13525 action.ignore_brackets,
13526 );
13527 selection.set_head(cursor, SelectionGoal::None);
13528 }
13529 });
13530 });
13531 this.insert("", window, cx);
13532 });
13533 }
13534
13535 pub fn delete_to_next_subword_end(
13536 &mut self,
13537 _: &DeleteToNextSubwordEnd,
13538 window: &mut Window,
13539 cx: &mut Context<Self>,
13540 ) {
13541 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13542 self.transact(window, cx, |this, window, cx| {
13543 this.change_selections(Default::default(), window, cx, |s| {
13544 s.move_with(|map, selection| {
13545 if selection.is_empty() {
13546 let mut cursor = movement::next_subword_end(map, selection.head());
13547 cursor =
13548 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13549 selection.set_head(cursor, SelectionGoal::None);
13550 }
13551 });
13552 });
13553 this.insert("", window, cx);
13554 });
13555 }
13556
13557 pub fn move_to_beginning_of_line(
13558 &mut self,
13559 action: &MoveToBeginningOfLine,
13560 window: &mut Window,
13561 cx: &mut Context<Self>,
13562 ) {
13563 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13564 self.change_selections(Default::default(), window, cx, |s| {
13565 s.move_cursors_with(|map, head, _| {
13566 (
13567 movement::indented_line_beginning(
13568 map,
13569 head,
13570 action.stop_at_soft_wraps,
13571 action.stop_at_indent,
13572 ),
13573 SelectionGoal::None,
13574 )
13575 });
13576 })
13577 }
13578
13579 pub fn select_to_beginning_of_line(
13580 &mut self,
13581 action: &SelectToBeginningOfLine,
13582 window: &mut Window,
13583 cx: &mut Context<Self>,
13584 ) {
13585 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13586 self.change_selections(Default::default(), window, cx, |s| {
13587 s.move_heads_with(|map, head, _| {
13588 (
13589 movement::indented_line_beginning(
13590 map,
13591 head,
13592 action.stop_at_soft_wraps,
13593 action.stop_at_indent,
13594 ),
13595 SelectionGoal::None,
13596 )
13597 });
13598 });
13599 }
13600
13601 pub fn delete_to_beginning_of_line(
13602 &mut self,
13603 action: &DeleteToBeginningOfLine,
13604 window: &mut Window,
13605 cx: &mut Context<Self>,
13606 ) {
13607 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13608 self.transact(window, cx, |this, window, cx| {
13609 this.change_selections(Default::default(), window, cx, |s| {
13610 s.move_with(|_, selection| {
13611 selection.reversed = true;
13612 });
13613 });
13614
13615 this.select_to_beginning_of_line(
13616 &SelectToBeginningOfLine {
13617 stop_at_soft_wraps: false,
13618 stop_at_indent: action.stop_at_indent,
13619 },
13620 window,
13621 cx,
13622 );
13623 this.backspace(&Backspace, window, cx);
13624 });
13625 }
13626
13627 pub fn move_to_end_of_line(
13628 &mut self,
13629 action: &MoveToEndOfLine,
13630 window: &mut Window,
13631 cx: &mut Context<Self>,
13632 ) {
13633 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13634 self.change_selections(Default::default(), window, cx, |s| {
13635 s.move_cursors_with(|map, head, _| {
13636 (
13637 movement::line_end(map, head, action.stop_at_soft_wraps),
13638 SelectionGoal::None,
13639 )
13640 });
13641 })
13642 }
13643
13644 pub fn select_to_end_of_line(
13645 &mut self,
13646 action: &SelectToEndOfLine,
13647 window: &mut Window,
13648 cx: &mut Context<Self>,
13649 ) {
13650 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13651 self.change_selections(Default::default(), window, cx, |s| {
13652 s.move_heads_with(|map, head, _| {
13653 (
13654 movement::line_end(map, head, action.stop_at_soft_wraps),
13655 SelectionGoal::None,
13656 )
13657 });
13658 })
13659 }
13660
13661 pub fn delete_to_end_of_line(
13662 &mut self,
13663 _: &DeleteToEndOfLine,
13664 window: &mut Window,
13665 cx: &mut Context<Self>,
13666 ) {
13667 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13668 self.transact(window, cx, |this, window, cx| {
13669 this.select_to_end_of_line(
13670 &SelectToEndOfLine {
13671 stop_at_soft_wraps: false,
13672 },
13673 window,
13674 cx,
13675 );
13676 this.delete(&Delete, window, cx);
13677 });
13678 }
13679
13680 pub fn cut_to_end_of_line(
13681 &mut self,
13682 action: &CutToEndOfLine,
13683 window: &mut Window,
13684 cx: &mut Context<Self>,
13685 ) {
13686 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13687 self.transact(window, cx, |this, window, cx| {
13688 this.select_to_end_of_line(
13689 &SelectToEndOfLine {
13690 stop_at_soft_wraps: false,
13691 },
13692 window,
13693 cx,
13694 );
13695 if !action.stop_at_newlines {
13696 this.change_selections(Default::default(), window, cx, |s| {
13697 s.move_with(|_, sel| {
13698 if sel.is_empty() {
13699 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13700 }
13701 });
13702 });
13703 }
13704 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13705 let item = this.cut_common(false, window, cx);
13706 cx.write_to_clipboard(item);
13707 });
13708 }
13709
13710 pub fn move_to_start_of_paragraph(
13711 &mut self,
13712 _: &MoveToStartOfParagraph,
13713 window: &mut Window,
13714 cx: &mut Context<Self>,
13715 ) {
13716 if matches!(self.mode, EditorMode::SingleLine) {
13717 cx.propagate();
13718 return;
13719 }
13720 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13721 self.change_selections(Default::default(), window, cx, |s| {
13722 s.move_with(|map, selection| {
13723 selection.collapse_to(
13724 movement::start_of_paragraph(map, selection.head(), 1),
13725 SelectionGoal::None,
13726 )
13727 });
13728 })
13729 }
13730
13731 pub fn move_to_end_of_paragraph(
13732 &mut self,
13733 _: &MoveToEndOfParagraph,
13734 window: &mut Window,
13735 cx: &mut Context<Self>,
13736 ) {
13737 if matches!(self.mode, EditorMode::SingleLine) {
13738 cx.propagate();
13739 return;
13740 }
13741 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13742 self.change_selections(Default::default(), window, cx, |s| {
13743 s.move_with(|map, selection| {
13744 selection.collapse_to(
13745 movement::end_of_paragraph(map, selection.head(), 1),
13746 SelectionGoal::None,
13747 )
13748 });
13749 })
13750 }
13751
13752 pub fn select_to_start_of_paragraph(
13753 &mut self,
13754 _: &SelectToStartOfParagraph,
13755 window: &mut Window,
13756 cx: &mut Context<Self>,
13757 ) {
13758 if matches!(self.mode, EditorMode::SingleLine) {
13759 cx.propagate();
13760 return;
13761 }
13762 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13763 self.change_selections(Default::default(), window, cx, |s| {
13764 s.move_heads_with(|map, head, _| {
13765 (
13766 movement::start_of_paragraph(map, head, 1),
13767 SelectionGoal::None,
13768 )
13769 });
13770 })
13771 }
13772
13773 pub fn select_to_end_of_paragraph(
13774 &mut self,
13775 _: &SelectToEndOfParagraph,
13776 window: &mut Window,
13777 cx: &mut Context<Self>,
13778 ) {
13779 if matches!(self.mode, EditorMode::SingleLine) {
13780 cx.propagate();
13781 return;
13782 }
13783 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13784 self.change_selections(Default::default(), window, cx, |s| {
13785 s.move_heads_with(|map, head, _| {
13786 (
13787 movement::end_of_paragraph(map, head, 1),
13788 SelectionGoal::None,
13789 )
13790 });
13791 })
13792 }
13793
13794 pub fn move_to_start_of_excerpt(
13795 &mut self,
13796 _: &MoveToStartOfExcerpt,
13797 window: &mut Window,
13798 cx: &mut Context<Self>,
13799 ) {
13800 if matches!(self.mode, EditorMode::SingleLine) {
13801 cx.propagate();
13802 return;
13803 }
13804 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13805 self.change_selections(Default::default(), window, cx, |s| {
13806 s.move_with(|map, selection| {
13807 selection.collapse_to(
13808 movement::start_of_excerpt(
13809 map,
13810 selection.head(),
13811 workspace::searchable::Direction::Prev,
13812 ),
13813 SelectionGoal::None,
13814 )
13815 });
13816 })
13817 }
13818
13819 pub fn move_to_start_of_next_excerpt(
13820 &mut self,
13821 _: &MoveToStartOfNextExcerpt,
13822 window: &mut Window,
13823 cx: &mut Context<Self>,
13824 ) {
13825 if matches!(self.mode, EditorMode::SingleLine) {
13826 cx.propagate();
13827 return;
13828 }
13829
13830 self.change_selections(Default::default(), window, cx, |s| {
13831 s.move_with(|map, selection| {
13832 selection.collapse_to(
13833 movement::start_of_excerpt(
13834 map,
13835 selection.head(),
13836 workspace::searchable::Direction::Next,
13837 ),
13838 SelectionGoal::None,
13839 )
13840 });
13841 })
13842 }
13843
13844 pub fn move_to_end_of_excerpt(
13845 &mut self,
13846 _: &MoveToEndOfExcerpt,
13847 window: &mut Window,
13848 cx: &mut Context<Self>,
13849 ) {
13850 if matches!(self.mode, EditorMode::SingleLine) {
13851 cx.propagate();
13852 return;
13853 }
13854 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13855 self.change_selections(Default::default(), window, cx, |s| {
13856 s.move_with(|map, selection| {
13857 selection.collapse_to(
13858 movement::end_of_excerpt(
13859 map,
13860 selection.head(),
13861 workspace::searchable::Direction::Next,
13862 ),
13863 SelectionGoal::None,
13864 )
13865 });
13866 })
13867 }
13868
13869 pub fn move_to_end_of_previous_excerpt(
13870 &mut self,
13871 _: &MoveToEndOfPreviousExcerpt,
13872 window: &mut Window,
13873 cx: &mut Context<Self>,
13874 ) {
13875 if matches!(self.mode, EditorMode::SingleLine) {
13876 cx.propagate();
13877 return;
13878 }
13879 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13880 self.change_selections(Default::default(), window, cx, |s| {
13881 s.move_with(|map, selection| {
13882 selection.collapse_to(
13883 movement::end_of_excerpt(
13884 map,
13885 selection.head(),
13886 workspace::searchable::Direction::Prev,
13887 ),
13888 SelectionGoal::None,
13889 )
13890 });
13891 })
13892 }
13893
13894 pub fn select_to_start_of_excerpt(
13895 &mut self,
13896 _: &SelectToStartOfExcerpt,
13897 window: &mut Window,
13898 cx: &mut Context<Self>,
13899 ) {
13900 if matches!(self.mode, EditorMode::SingleLine) {
13901 cx.propagate();
13902 return;
13903 }
13904 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13905 self.change_selections(Default::default(), window, cx, |s| {
13906 s.move_heads_with(|map, head, _| {
13907 (
13908 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13909 SelectionGoal::None,
13910 )
13911 });
13912 })
13913 }
13914
13915 pub fn select_to_start_of_next_excerpt(
13916 &mut self,
13917 _: &SelectToStartOfNextExcerpt,
13918 window: &mut Window,
13919 cx: &mut Context<Self>,
13920 ) {
13921 if matches!(self.mode, EditorMode::SingleLine) {
13922 cx.propagate();
13923 return;
13924 }
13925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13926 self.change_selections(Default::default(), window, cx, |s| {
13927 s.move_heads_with(|map, head, _| {
13928 (
13929 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13930 SelectionGoal::None,
13931 )
13932 });
13933 })
13934 }
13935
13936 pub fn select_to_end_of_excerpt(
13937 &mut self,
13938 _: &SelectToEndOfExcerpt,
13939 window: &mut Window,
13940 cx: &mut Context<Self>,
13941 ) {
13942 if matches!(self.mode, EditorMode::SingleLine) {
13943 cx.propagate();
13944 return;
13945 }
13946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13947 self.change_selections(Default::default(), window, cx, |s| {
13948 s.move_heads_with(|map, head, _| {
13949 (
13950 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13951 SelectionGoal::None,
13952 )
13953 });
13954 })
13955 }
13956
13957 pub fn select_to_end_of_previous_excerpt(
13958 &mut self,
13959 _: &SelectToEndOfPreviousExcerpt,
13960 window: &mut Window,
13961 cx: &mut Context<Self>,
13962 ) {
13963 if matches!(self.mode, EditorMode::SingleLine) {
13964 cx.propagate();
13965 return;
13966 }
13967 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13968 self.change_selections(Default::default(), window, cx, |s| {
13969 s.move_heads_with(|map, head, _| {
13970 (
13971 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13972 SelectionGoal::None,
13973 )
13974 });
13975 })
13976 }
13977
13978 pub fn move_to_beginning(
13979 &mut self,
13980 _: &MoveToBeginning,
13981 window: &mut Window,
13982 cx: &mut Context<Self>,
13983 ) {
13984 if matches!(self.mode, EditorMode::SingleLine) {
13985 cx.propagate();
13986 return;
13987 }
13988 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13989 self.change_selections(Default::default(), window, cx, |s| {
13990 s.select_ranges(vec![0..0]);
13991 });
13992 }
13993
13994 pub fn select_to_beginning(
13995 &mut self,
13996 _: &SelectToBeginning,
13997 window: &mut Window,
13998 cx: &mut Context<Self>,
13999 ) {
14000 let mut selection = self.selections.last::<Point>(cx);
14001 selection.set_head(Point::zero(), SelectionGoal::None);
14002 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14003 self.change_selections(Default::default(), window, cx, |s| {
14004 s.select(vec![selection]);
14005 });
14006 }
14007
14008 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14009 if matches!(self.mode, EditorMode::SingleLine) {
14010 cx.propagate();
14011 return;
14012 }
14013 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14014 let cursor = self.buffer.read(cx).read(cx).len();
14015 self.change_selections(Default::default(), window, cx, |s| {
14016 s.select_ranges(vec![cursor..cursor])
14017 });
14018 }
14019
14020 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14021 self.nav_history = nav_history;
14022 }
14023
14024 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14025 self.nav_history.as_ref()
14026 }
14027
14028 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14029 self.push_to_nav_history(
14030 self.selections.newest_anchor().head(),
14031 None,
14032 false,
14033 true,
14034 cx,
14035 );
14036 }
14037
14038 fn push_to_nav_history(
14039 &mut self,
14040 cursor_anchor: Anchor,
14041 new_position: Option<Point>,
14042 is_deactivate: bool,
14043 always: bool,
14044 cx: &mut Context<Self>,
14045 ) {
14046 if let Some(nav_history) = self.nav_history.as_mut() {
14047 let buffer = self.buffer.read(cx).read(cx);
14048 let cursor_position = cursor_anchor.to_point(&buffer);
14049 let scroll_state = self.scroll_manager.anchor();
14050 let scroll_top_row = scroll_state.top_row(&buffer);
14051 drop(buffer);
14052
14053 if let Some(new_position) = new_position {
14054 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14055 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14056 return;
14057 }
14058 }
14059
14060 nav_history.push(
14061 Some(NavigationData {
14062 cursor_anchor,
14063 cursor_position,
14064 scroll_anchor: scroll_state,
14065 scroll_top_row,
14066 }),
14067 cx,
14068 );
14069 cx.emit(EditorEvent::PushedToNavHistory {
14070 anchor: cursor_anchor,
14071 is_deactivate,
14072 })
14073 }
14074 }
14075
14076 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14077 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14078 let buffer = self.buffer.read(cx).snapshot(cx);
14079 let mut selection = self.selections.first::<usize>(cx);
14080 selection.set_head(buffer.len(), SelectionGoal::None);
14081 self.change_selections(Default::default(), window, cx, |s| {
14082 s.select(vec![selection]);
14083 });
14084 }
14085
14086 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14087 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14088 let end = self.buffer.read(cx).read(cx).len();
14089 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14090 s.select_ranges(vec![0..end]);
14091 });
14092 }
14093
14094 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14095 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14096 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14097 let mut selections = self.selections.all::<Point>(cx);
14098 let max_point = display_map.buffer_snapshot.max_point();
14099 for selection in &mut selections {
14100 let rows = selection.spanned_rows(true, &display_map);
14101 selection.start = Point::new(rows.start.0, 0);
14102 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14103 selection.reversed = false;
14104 }
14105 self.change_selections(Default::default(), window, cx, |s| {
14106 s.select(selections);
14107 });
14108 }
14109
14110 pub fn split_selection_into_lines(
14111 &mut self,
14112 action: &SplitSelectionIntoLines,
14113 window: &mut Window,
14114 cx: &mut Context<Self>,
14115 ) {
14116 let selections = self
14117 .selections
14118 .all::<Point>(cx)
14119 .into_iter()
14120 .map(|selection| selection.start..selection.end)
14121 .collect::<Vec<_>>();
14122 self.unfold_ranges(&selections, true, true, cx);
14123
14124 let mut new_selection_ranges = Vec::new();
14125 {
14126 let buffer = self.buffer.read(cx).read(cx);
14127 for selection in selections {
14128 for row in selection.start.row..selection.end.row {
14129 let line_start = Point::new(row, 0);
14130 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14131
14132 if action.keep_selections {
14133 // Keep the selection range for each line
14134 let selection_start = if row == selection.start.row {
14135 selection.start
14136 } else {
14137 line_start
14138 };
14139 new_selection_ranges.push(selection_start..line_end);
14140 } else {
14141 // Collapse to cursor at end of line
14142 new_selection_ranges.push(line_end..line_end);
14143 }
14144 }
14145
14146 let is_multiline_selection = selection.start.row != selection.end.row;
14147 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14148 // so this action feels more ergonomic when paired with other selection operations
14149 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14150 if !should_skip_last {
14151 if action.keep_selections {
14152 if is_multiline_selection {
14153 let line_start = Point::new(selection.end.row, 0);
14154 new_selection_ranges.push(line_start..selection.end);
14155 } else {
14156 new_selection_ranges.push(selection.start..selection.end);
14157 }
14158 } else {
14159 new_selection_ranges.push(selection.end..selection.end);
14160 }
14161 }
14162 }
14163 }
14164 self.change_selections(Default::default(), window, cx, |s| {
14165 s.select_ranges(new_selection_ranges);
14166 });
14167 }
14168
14169 pub fn add_selection_above(
14170 &mut self,
14171 _: &AddSelectionAbove,
14172 window: &mut Window,
14173 cx: &mut Context<Self>,
14174 ) {
14175 self.add_selection(true, window, cx);
14176 }
14177
14178 pub fn add_selection_below(
14179 &mut self,
14180 _: &AddSelectionBelow,
14181 window: &mut Window,
14182 cx: &mut Context<Self>,
14183 ) {
14184 self.add_selection(false, window, cx);
14185 }
14186
14187 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14188 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14189
14190 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14191 let all_selections = self.selections.all::<Point>(cx);
14192 let text_layout_details = self.text_layout_details(window);
14193
14194 let (mut columnar_selections, new_selections_to_columnarize) = {
14195 if let Some(state) = self.add_selections_state.as_ref() {
14196 let columnar_selection_ids: HashSet<_> = state
14197 .groups
14198 .iter()
14199 .flat_map(|group| group.stack.iter())
14200 .copied()
14201 .collect();
14202
14203 all_selections
14204 .into_iter()
14205 .partition(|s| columnar_selection_ids.contains(&s.id))
14206 } else {
14207 (Vec::new(), all_selections)
14208 }
14209 };
14210
14211 let mut state = self
14212 .add_selections_state
14213 .take()
14214 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14215
14216 for selection in new_selections_to_columnarize {
14217 let range = selection.display_range(&display_map).sorted();
14218 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14219 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14220 let positions = start_x.min(end_x)..start_x.max(end_x);
14221 let mut stack = Vec::new();
14222 for row in range.start.row().0..=range.end.row().0 {
14223 if let Some(selection) = self.selections.build_columnar_selection(
14224 &display_map,
14225 DisplayRow(row),
14226 &positions,
14227 selection.reversed,
14228 &text_layout_details,
14229 ) {
14230 stack.push(selection.id);
14231 columnar_selections.push(selection);
14232 }
14233 }
14234 if !stack.is_empty() {
14235 if above {
14236 stack.reverse();
14237 }
14238 state.groups.push(AddSelectionsGroup { above, stack });
14239 }
14240 }
14241
14242 let mut final_selections = Vec::new();
14243 let end_row = if above {
14244 DisplayRow(0)
14245 } else {
14246 display_map.max_point().row()
14247 };
14248
14249 let mut last_added_item_per_group = HashMap::default();
14250 for group in state.groups.iter_mut() {
14251 if let Some(last_id) = group.stack.last() {
14252 last_added_item_per_group.insert(*last_id, group);
14253 }
14254 }
14255
14256 for selection in columnar_selections {
14257 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14258 if above == group.above {
14259 let range = selection.display_range(&display_map).sorted();
14260 debug_assert_eq!(range.start.row(), range.end.row());
14261 let mut row = range.start.row();
14262 let positions =
14263 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14264 Pixels::from(start)..Pixels::from(end)
14265 } else {
14266 let start_x =
14267 display_map.x_for_display_point(range.start, &text_layout_details);
14268 let end_x =
14269 display_map.x_for_display_point(range.end, &text_layout_details);
14270 start_x.min(end_x)..start_x.max(end_x)
14271 };
14272
14273 let mut maybe_new_selection = None;
14274 while row != end_row {
14275 if above {
14276 row.0 -= 1;
14277 } else {
14278 row.0 += 1;
14279 }
14280 if let Some(new_selection) = self.selections.build_columnar_selection(
14281 &display_map,
14282 row,
14283 &positions,
14284 selection.reversed,
14285 &text_layout_details,
14286 ) {
14287 maybe_new_selection = Some(new_selection);
14288 break;
14289 }
14290 }
14291
14292 if let Some(new_selection) = maybe_new_selection {
14293 group.stack.push(new_selection.id);
14294 if above {
14295 final_selections.push(new_selection);
14296 final_selections.push(selection);
14297 } else {
14298 final_selections.push(selection);
14299 final_selections.push(new_selection);
14300 }
14301 } else {
14302 final_selections.push(selection);
14303 }
14304 } else {
14305 group.stack.pop();
14306 }
14307 } else {
14308 final_selections.push(selection);
14309 }
14310 }
14311
14312 self.change_selections(Default::default(), window, cx, |s| {
14313 s.select(final_selections);
14314 });
14315
14316 let final_selection_ids: HashSet<_> = self
14317 .selections
14318 .all::<Point>(cx)
14319 .iter()
14320 .map(|s| s.id)
14321 .collect();
14322 state.groups.retain_mut(|group| {
14323 // selections might get merged above so we remove invalid items from stacks
14324 group.stack.retain(|id| final_selection_ids.contains(id));
14325
14326 // single selection in stack can be treated as initial state
14327 group.stack.len() > 1
14328 });
14329
14330 if !state.groups.is_empty() {
14331 self.add_selections_state = Some(state);
14332 }
14333 }
14334
14335 fn select_match_ranges(
14336 &mut self,
14337 range: Range<usize>,
14338 reversed: bool,
14339 replace_newest: bool,
14340 auto_scroll: Option<Autoscroll>,
14341 window: &mut Window,
14342 cx: &mut Context<Editor>,
14343 ) {
14344 self.unfold_ranges(
14345 std::slice::from_ref(&range),
14346 false,
14347 auto_scroll.is_some(),
14348 cx,
14349 );
14350 let effects = if let Some(scroll) = auto_scroll {
14351 SelectionEffects::scroll(scroll)
14352 } else {
14353 SelectionEffects::no_scroll()
14354 };
14355 self.change_selections(effects, window, cx, |s| {
14356 if replace_newest {
14357 s.delete(s.newest_anchor().id);
14358 }
14359 if reversed {
14360 s.insert_range(range.end..range.start);
14361 } else {
14362 s.insert_range(range);
14363 }
14364 });
14365 }
14366
14367 pub fn select_next_match_internal(
14368 &mut self,
14369 display_map: &DisplaySnapshot,
14370 replace_newest: bool,
14371 autoscroll: Option<Autoscroll>,
14372 window: &mut Window,
14373 cx: &mut Context<Self>,
14374 ) -> Result<()> {
14375 let buffer = &display_map.buffer_snapshot;
14376 let mut selections = self.selections.all::<usize>(cx);
14377 if let Some(mut select_next_state) = self.select_next_state.take() {
14378 let query = &select_next_state.query;
14379 if !select_next_state.done {
14380 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14381 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14382 let mut next_selected_range = None;
14383
14384 let bytes_after_last_selection =
14385 buffer.bytes_in_range(last_selection.end..buffer.len());
14386 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14387 let query_matches = query
14388 .stream_find_iter(bytes_after_last_selection)
14389 .map(|result| (last_selection.end, result))
14390 .chain(
14391 query
14392 .stream_find_iter(bytes_before_first_selection)
14393 .map(|result| (0, result)),
14394 );
14395
14396 for (start_offset, query_match) in query_matches {
14397 let query_match = query_match.unwrap(); // can only fail due to I/O
14398 let offset_range =
14399 start_offset + query_match.start()..start_offset + query_match.end();
14400
14401 if !select_next_state.wordwise
14402 || (!buffer.is_inside_word(offset_range.start, None)
14403 && !buffer.is_inside_word(offset_range.end, None))
14404 {
14405 // TODO: This is n^2, because we might check all the selections
14406 if !selections
14407 .iter()
14408 .any(|selection| selection.range().overlaps(&offset_range))
14409 {
14410 next_selected_range = Some(offset_range);
14411 break;
14412 }
14413 }
14414 }
14415
14416 if let Some(next_selected_range) = next_selected_range {
14417 self.select_match_ranges(
14418 next_selected_range,
14419 last_selection.reversed,
14420 replace_newest,
14421 autoscroll,
14422 window,
14423 cx,
14424 );
14425 } else {
14426 select_next_state.done = true;
14427 }
14428 }
14429
14430 self.select_next_state = Some(select_next_state);
14431 } else {
14432 let mut only_carets = true;
14433 let mut same_text_selected = true;
14434 let mut selected_text = None;
14435
14436 let mut selections_iter = selections.iter().peekable();
14437 while let Some(selection) = selections_iter.next() {
14438 if selection.start != selection.end {
14439 only_carets = false;
14440 }
14441
14442 if same_text_selected {
14443 if selected_text.is_none() {
14444 selected_text =
14445 Some(buffer.text_for_range(selection.range()).collect::<String>());
14446 }
14447
14448 if let Some(next_selection) = selections_iter.peek() {
14449 if next_selection.range().len() == selection.range().len() {
14450 let next_selected_text = buffer
14451 .text_for_range(next_selection.range())
14452 .collect::<String>();
14453 if Some(next_selected_text) != selected_text {
14454 same_text_selected = false;
14455 selected_text = None;
14456 }
14457 } else {
14458 same_text_selected = false;
14459 selected_text = None;
14460 }
14461 }
14462 }
14463 }
14464
14465 if only_carets {
14466 for selection in &mut selections {
14467 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14468 selection.start = word_range.start;
14469 selection.end = word_range.end;
14470 selection.goal = SelectionGoal::None;
14471 selection.reversed = false;
14472 self.select_match_ranges(
14473 selection.start..selection.end,
14474 selection.reversed,
14475 replace_newest,
14476 autoscroll,
14477 window,
14478 cx,
14479 );
14480 }
14481
14482 if selections.len() == 1 {
14483 let selection = selections
14484 .last()
14485 .expect("ensured that there's only one selection");
14486 let query = buffer
14487 .text_for_range(selection.start..selection.end)
14488 .collect::<String>();
14489 let is_empty = query.is_empty();
14490 let select_state = SelectNextState {
14491 query: AhoCorasick::new(&[query])?,
14492 wordwise: true,
14493 done: is_empty,
14494 };
14495 self.select_next_state = Some(select_state);
14496 } else {
14497 self.select_next_state = None;
14498 }
14499 } else if let Some(selected_text) = selected_text {
14500 self.select_next_state = Some(SelectNextState {
14501 query: AhoCorasick::new(&[selected_text])?,
14502 wordwise: false,
14503 done: false,
14504 });
14505 self.select_next_match_internal(
14506 display_map,
14507 replace_newest,
14508 autoscroll,
14509 window,
14510 cx,
14511 )?;
14512 }
14513 }
14514 Ok(())
14515 }
14516
14517 pub fn select_all_matches(
14518 &mut self,
14519 _action: &SelectAllMatches,
14520 window: &mut Window,
14521 cx: &mut Context<Self>,
14522 ) -> Result<()> {
14523 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14524
14525 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14526
14527 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14528 let Some(select_next_state) = self.select_next_state.as_mut() else {
14529 return Ok(());
14530 };
14531 if select_next_state.done {
14532 return Ok(());
14533 }
14534
14535 let mut new_selections = Vec::new();
14536
14537 let reversed = self.selections.oldest::<usize>(cx).reversed;
14538 let buffer = &display_map.buffer_snapshot;
14539 let query_matches = select_next_state
14540 .query
14541 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14542
14543 for query_match in query_matches.into_iter() {
14544 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14545 let offset_range = if reversed {
14546 query_match.end()..query_match.start()
14547 } else {
14548 query_match.start()..query_match.end()
14549 };
14550
14551 if !select_next_state.wordwise
14552 || (!buffer.is_inside_word(offset_range.start, None)
14553 && !buffer.is_inside_word(offset_range.end, None))
14554 {
14555 new_selections.push(offset_range.start..offset_range.end);
14556 }
14557 }
14558
14559 select_next_state.done = true;
14560
14561 if new_selections.is_empty() {
14562 log::error!("bug: new_selections is empty in select_all_matches");
14563 return Ok(());
14564 }
14565
14566 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14567 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14568 selections.select_ranges(new_selections)
14569 });
14570
14571 Ok(())
14572 }
14573
14574 pub fn select_next(
14575 &mut self,
14576 action: &SelectNext,
14577 window: &mut Window,
14578 cx: &mut Context<Self>,
14579 ) -> Result<()> {
14580 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14581 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14582 self.select_next_match_internal(
14583 &display_map,
14584 action.replace_newest,
14585 Some(Autoscroll::newest()),
14586 window,
14587 cx,
14588 )?;
14589 Ok(())
14590 }
14591
14592 pub fn select_previous(
14593 &mut self,
14594 action: &SelectPrevious,
14595 window: &mut Window,
14596 cx: &mut Context<Self>,
14597 ) -> Result<()> {
14598 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14599 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14600 let buffer = &display_map.buffer_snapshot;
14601 let mut selections = self.selections.all::<usize>(cx);
14602 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14603 let query = &select_prev_state.query;
14604 if !select_prev_state.done {
14605 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14606 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14607 let mut next_selected_range = None;
14608 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14609 let bytes_before_last_selection =
14610 buffer.reversed_bytes_in_range(0..last_selection.start);
14611 let bytes_after_first_selection =
14612 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14613 let query_matches = query
14614 .stream_find_iter(bytes_before_last_selection)
14615 .map(|result| (last_selection.start, result))
14616 .chain(
14617 query
14618 .stream_find_iter(bytes_after_first_selection)
14619 .map(|result| (buffer.len(), result)),
14620 );
14621 for (end_offset, query_match) in query_matches {
14622 let query_match = query_match.unwrap(); // can only fail due to I/O
14623 let offset_range =
14624 end_offset - query_match.end()..end_offset - query_match.start();
14625
14626 if !select_prev_state.wordwise
14627 || (!buffer.is_inside_word(offset_range.start, None)
14628 && !buffer.is_inside_word(offset_range.end, None))
14629 {
14630 next_selected_range = Some(offset_range);
14631 break;
14632 }
14633 }
14634
14635 if let Some(next_selected_range) = next_selected_range {
14636 self.select_match_ranges(
14637 next_selected_range,
14638 last_selection.reversed,
14639 action.replace_newest,
14640 Some(Autoscroll::newest()),
14641 window,
14642 cx,
14643 );
14644 } else {
14645 select_prev_state.done = true;
14646 }
14647 }
14648
14649 self.select_prev_state = Some(select_prev_state);
14650 } else {
14651 let mut only_carets = true;
14652 let mut same_text_selected = true;
14653 let mut selected_text = None;
14654
14655 let mut selections_iter = selections.iter().peekable();
14656 while let Some(selection) = selections_iter.next() {
14657 if selection.start != selection.end {
14658 only_carets = false;
14659 }
14660
14661 if same_text_selected {
14662 if selected_text.is_none() {
14663 selected_text =
14664 Some(buffer.text_for_range(selection.range()).collect::<String>());
14665 }
14666
14667 if let Some(next_selection) = selections_iter.peek() {
14668 if next_selection.range().len() == selection.range().len() {
14669 let next_selected_text = buffer
14670 .text_for_range(next_selection.range())
14671 .collect::<String>();
14672 if Some(next_selected_text) != selected_text {
14673 same_text_selected = false;
14674 selected_text = None;
14675 }
14676 } else {
14677 same_text_selected = false;
14678 selected_text = None;
14679 }
14680 }
14681 }
14682 }
14683
14684 if only_carets {
14685 for selection in &mut selections {
14686 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14687 selection.start = word_range.start;
14688 selection.end = word_range.end;
14689 selection.goal = SelectionGoal::None;
14690 selection.reversed = false;
14691 self.select_match_ranges(
14692 selection.start..selection.end,
14693 selection.reversed,
14694 action.replace_newest,
14695 Some(Autoscroll::newest()),
14696 window,
14697 cx,
14698 );
14699 }
14700 if selections.len() == 1 {
14701 let selection = selections
14702 .last()
14703 .expect("ensured that there's only one selection");
14704 let query = buffer
14705 .text_for_range(selection.start..selection.end)
14706 .collect::<String>();
14707 let is_empty = query.is_empty();
14708 let select_state = SelectNextState {
14709 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14710 wordwise: true,
14711 done: is_empty,
14712 };
14713 self.select_prev_state = Some(select_state);
14714 } else {
14715 self.select_prev_state = None;
14716 }
14717 } else if let Some(selected_text) = selected_text {
14718 self.select_prev_state = Some(SelectNextState {
14719 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14720 wordwise: false,
14721 done: false,
14722 });
14723 self.select_previous(action, window, cx)?;
14724 }
14725 }
14726 Ok(())
14727 }
14728
14729 pub fn find_next_match(
14730 &mut self,
14731 _: &FindNextMatch,
14732 window: &mut Window,
14733 cx: &mut Context<Self>,
14734 ) -> Result<()> {
14735 let selections = self.selections.disjoint_anchors_arc();
14736 match selections.first() {
14737 Some(first) if selections.len() >= 2 => {
14738 self.change_selections(Default::default(), window, cx, |s| {
14739 s.select_ranges([first.range()]);
14740 });
14741 }
14742 _ => self.select_next(
14743 &SelectNext {
14744 replace_newest: true,
14745 },
14746 window,
14747 cx,
14748 )?,
14749 }
14750 Ok(())
14751 }
14752
14753 pub fn find_previous_match(
14754 &mut self,
14755 _: &FindPreviousMatch,
14756 window: &mut Window,
14757 cx: &mut Context<Self>,
14758 ) -> Result<()> {
14759 let selections = self.selections.disjoint_anchors_arc();
14760 match selections.last() {
14761 Some(last) if selections.len() >= 2 => {
14762 self.change_selections(Default::default(), window, cx, |s| {
14763 s.select_ranges([last.range()]);
14764 });
14765 }
14766 _ => self.select_previous(
14767 &SelectPrevious {
14768 replace_newest: true,
14769 },
14770 window,
14771 cx,
14772 )?,
14773 }
14774 Ok(())
14775 }
14776
14777 pub fn toggle_comments(
14778 &mut self,
14779 action: &ToggleComments,
14780 window: &mut Window,
14781 cx: &mut Context<Self>,
14782 ) {
14783 if self.read_only(cx) {
14784 return;
14785 }
14786 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14787 let text_layout_details = &self.text_layout_details(window);
14788 self.transact(window, cx, |this, window, cx| {
14789 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14790 let mut edits = Vec::new();
14791 let mut selection_edit_ranges = Vec::new();
14792 let mut last_toggled_row = None;
14793 let snapshot = this.buffer.read(cx).read(cx);
14794 let empty_str: Arc<str> = Arc::default();
14795 let mut suffixes_inserted = Vec::new();
14796 let ignore_indent = action.ignore_indent;
14797
14798 fn comment_prefix_range(
14799 snapshot: &MultiBufferSnapshot,
14800 row: MultiBufferRow,
14801 comment_prefix: &str,
14802 comment_prefix_whitespace: &str,
14803 ignore_indent: bool,
14804 ) -> Range<Point> {
14805 let indent_size = if ignore_indent {
14806 0
14807 } else {
14808 snapshot.indent_size_for_line(row).len
14809 };
14810
14811 let start = Point::new(row.0, indent_size);
14812
14813 let mut line_bytes = snapshot
14814 .bytes_in_range(start..snapshot.max_point())
14815 .flatten()
14816 .copied();
14817
14818 // If this line currently begins with the line comment prefix, then record
14819 // the range containing the prefix.
14820 if line_bytes
14821 .by_ref()
14822 .take(comment_prefix.len())
14823 .eq(comment_prefix.bytes())
14824 {
14825 // Include any whitespace that matches the comment prefix.
14826 let matching_whitespace_len = line_bytes
14827 .zip(comment_prefix_whitespace.bytes())
14828 .take_while(|(a, b)| a == b)
14829 .count() as u32;
14830 let end = Point::new(
14831 start.row,
14832 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14833 );
14834 start..end
14835 } else {
14836 start..start
14837 }
14838 }
14839
14840 fn comment_suffix_range(
14841 snapshot: &MultiBufferSnapshot,
14842 row: MultiBufferRow,
14843 comment_suffix: &str,
14844 comment_suffix_has_leading_space: bool,
14845 ) -> Range<Point> {
14846 let end = Point::new(row.0, snapshot.line_len(row));
14847 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14848
14849 let mut line_end_bytes = snapshot
14850 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14851 .flatten()
14852 .copied();
14853
14854 let leading_space_len = if suffix_start_column > 0
14855 && line_end_bytes.next() == Some(b' ')
14856 && comment_suffix_has_leading_space
14857 {
14858 1
14859 } else {
14860 0
14861 };
14862
14863 // If this line currently begins with the line comment prefix, then record
14864 // the range containing the prefix.
14865 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14866 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14867 start..end
14868 } else {
14869 end..end
14870 }
14871 }
14872
14873 // TODO: Handle selections that cross excerpts
14874 for selection in &mut selections {
14875 let start_column = snapshot
14876 .indent_size_for_line(MultiBufferRow(selection.start.row))
14877 .len;
14878 let language = if let Some(language) =
14879 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14880 {
14881 language
14882 } else {
14883 continue;
14884 };
14885
14886 selection_edit_ranges.clear();
14887
14888 // If multiple selections contain a given row, avoid processing that
14889 // row more than once.
14890 let mut start_row = MultiBufferRow(selection.start.row);
14891 if last_toggled_row == Some(start_row) {
14892 start_row = start_row.next_row();
14893 }
14894 let end_row =
14895 if selection.end.row > selection.start.row && selection.end.column == 0 {
14896 MultiBufferRow(selection.end.row - 1)
14897 } else {
14898 MultiBufferRow(selection.end.row)
14899 };
14900 last_toggled_row = Some(end_row);
14901
14902 if start_row > end_row {
14903 continue;
14904 }
14905
14906 // If the language has line comments, toggle those.
14907 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14908
14909 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14910 if ignore_indent {
14911 full_comment_prefixes = full_comment_prefixes
14912 .into_iter()
14913 .map(|s| Arc::from(s.trim_end()))
14914 .collect();
14915 }
14916
14917 if !full_comment_prefixes.is_empty() {
14918 let first_prefix = full_comment_prefixes
14919 .first()
14920 .expect("prefixes is non-empty");
14921 let prefix_trimmed_lengths = full_comment_prefixes
14922 .iter()
14923 .map(|p| p.trim_end_matches(' ').len())
14924 .collect::<SmallVec<[usize; 4]>>();
14925
14926 let mut all_selection_lines_are_comments = true;
14927
14928 for row in start_row.0..=end_row.0 {
14929 let row = MultiBufferRow(row);
14930 if start_row < end_row && snapshot.is_line_blank(row) {
14931 continue;
14932 }
14933
14934 let prefix_range = full_comment_prefixes
14935 .iter()
14936 .zip(prefix_trimmed_lengths.iter().copied())
14937 .map(|(prefix, trimmed_prefix_len)| {
14938 comment_prefix_range(
14939 snapshot.deref(),
14940 row,
14941 &prefix[..trimmed_prefix_len],
14942 &prefix[trimmed_prefix_len..],
14943 ignore_indent,
14944 )
14945 })
14946 .max_by_key(|range| range.end.column - range.start.column)
14947 .expect("prefixes is non-empty");
14948
14949 if prefix_range.is_empty() {
14950 all_selection_lines_are_comments = false;
14951 }
14952
14953 selection_edit_ranges.push(prefix_range);
14954 }
14955
14956 if all_selection_lines_are_comments {
14957 edits.extend(
14958 selection_edit_ranges
14959 .iter()
14960 .cloned()
14961 .map(|range| (range, empty_str.clone())),
14962 );
14963 } else {
14964 let min_column = selection_edit_ranges
14965 .iter()
14966 .map(|range| range.start.column)
14967 .min()
14968 .unwrap_or(0);
14969 edits.extend(selection_edit_ranges.iter().map(|range| {
14970 let position = Point::new(range.start.row, min_column);
14971 (position..position, first_prefix.clone())
14972 }));
14973 }
14974 } else if let Some(BlockCommentConfig {
14975 start: full_comment_prefix,
14976 end: comment_suffix,
14977 ..
14978 }) = language.block_comment()
14979 {
14980 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14981 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14982 let prefix_range = comment_prefix_range(
14983 snapshot.deref(),
14984 start_row,
14985 comment_prefix,
14986 comment_prefix_whitespace,
14987 ignore_indent,
14988 );
14989 let suffix_range = comment_suffix_range(
14990 snapshot.deref(),
14991 end_row,
14992 comment_suffix.trim_start_matches(' '),
14993 comment_suffix.starts_with(' '),
14994 );
14995
14996 if prefix_range.is_empty() || suffix_range.is_empty() {
14997 edits.push((
14998 prefix_range.start..prefix_range.start,
14999 full_comment_prefix.clone(),
15000 ));
15001 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15002 suffixes_inserted.push((end_row, comment_suffix.len()));
15003 } else {
15004 edits.push((prefix_range, empty_str.clone()));
15005 edits.push((suffix_range, empty_str.clone()));
15006 }
15007 } else {
15008 continue;
15009 }
15010 }
15011
15012 drop(snapshot);
15013 this.buffer.update(cx, |buffer, cx| {
15014 buffer.edit(edits, None, cx);
15015 });
15016
15017 // Adjust selections so that they end before any comment suffixes that
15018 // were inserted.
15019 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15020 let mut selections = this.selections.all::<Point>(cx);
15021 let snapshot = this.buffer.read(cx).read(cx);
15022 for selection in &mut selections {
15023 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15024 match row.cmp(&MultiBufferRow(selection.end.row)) {
15025 Ordering::Less => {
15026 suffixes_inserted.next();
15027 continue;
15028 }
15029 Ordering::Greater => break,
15030 Ordering::Equal => {
15031 if selection.end.column == snapshot.line_len(row) {
15032 if selection.is_empty() {
15033 selection.start.column -= suffix_len as u32;
15034 }
15035 selection.end.column -= suffix_len as u32;
15036 }
15037 break;
15038 }
15039 }
15040 }
15041 }
15042
15043 drop(snapshot);
15044 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15045
15046 let selections = this.selections.all::<Point>(cx);
15047 let selections_on_single_row = selections.windows(2).all(|selections| {
15048 selections[0].start.row == selections[1].start.row
15049 && selections[0].end.row == selections[1].end.row
15050 && selections[0].start.row == selections[0].end.row
15051 });
15052 let selections_selecting = selections
15053 .iter()
15054 .any(|selection| selection.start != selection.end);
15055 let advance_downwards = action.advance_downwards
15056 && selections_on_single_row
15057 && !selections_selecting
15058 && !matches!(this.mode, EditorMode::SingleLine);
15059
15060 if advance_downwards {
15061 let snapshot = this.buffer.read(cx).snapshot(cx);
15062
15063 this.change_selections(Default::default(), window, cx, |s| {
15064 s.move_cursors_with(|display_snapshot, display_point, _| {
15065 let mut point = display_point.to_point(display_snapshot);
15066 point.row += 1;
15067 point = snapshot.clip_point(point, Bias::Left);
15068 let display_point = point.to_display_point(display_snapshot);
15069 let goal = SelectionGoal::HorizontalPosition(
15070 display_snapshot
15071 .x_for_display_point(display_point, text_layout_details)
15072 .into(),
15073 );
15074 (display_point, goal)
15075 })
15076 });
15077 }
15078 });
15079 }
15080
15081 pub fn select_enclosing_symbol(
15082 &mut self,
15083 _: &SelectEnclosingSymbol,
15084 window: &mut Window,
15085 cx: &mut Context<Self>,
15086 ) {
15087 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15088
15089 let buffer = self.buffer.read(cx).snapshot(cx);
15090 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
15091
15092 fn update_selection(
15093 selection: &Selection<usize>,
15094 buffer_snap: &MultiBufferSnapshot,
15095 ) -> Option<Selection<usize>> {
15096 let cursor = selection.head();
15097 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15098 for symbol in symbols.iter().rev() {
15099 let start = symbol.range.start.to_offset(buffer_snap);
15100 let end = symbol.range.end.to_offset(buffer_snap);
15101 let new_range = start..end;
15102 if start < selection.start || end > selection.end {
15103 return Some(Selection {
15104 id: selection.id,
15105 start: new_range.start,
15106 end: new_range.end,
15107 goal: SelectionGoal::None,
15108 reversed: selection.reversed,
15109 });
15110 }
15111 }
15112 None
15113 }
15114
15115 let mut selected_larger_symbol = false;
15116 let new_selections = old_selections
15117 .iter()
15118 .map(|selection| match update_selection(selection, &buffer) {
15119 Some(new_selection) => {
15120 if new_selection.range() != selection.range() {
15121 selected_larger_symbol = true;
15122 }
15123 new_selection
15124 }
15125 None => selection.clone(),
15126 })
15127 .collect::<Vec<_>>();
15128
15129 if selected_larger_symbol {
15130 self.change_selections(Default::default(), window, cx, |s| {
15131 s.select(new_selections);
15132 });
15133 }
15134 }
15135
15136 pub fn select_larger_syntax_node(
15137 &mut self,
15138 _: &SelectLargerSyntaxNode,
15139 window: &mut Window,
15140 cx: &mut Context<Self>,
15141 ) {
15142 let Some(visible_row_count) = self.visible_row_count() else {
15143 return;
15144 };
15145 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15146 if old_selections.is_empty() {
15147 return;
15148 }
15149
15150 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15151
15152 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15153 let buffer = self.buffer.read(cx).snapshot(cx);
15154
15155 let mut selected_larger_node = false;
15156 let mut new_selections = old_selections
15157 .iter()
15158 .map(|selection| {
15159 let old_range = selection.start..selection.end;
15160
15161 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15162 // manually select word at selection
15163 if ["string_content", "inline"].contains(&node.kind()) {
15164 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15165 // ignore if word is already selected
15166 if !word_range.is_empty() && old_range != word_range {
15167 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15168 // only select word if start and end point belongs to same word
15169 if word_range == last_word_range {
15170 selected_larger_node = true;
15171 return Selection {
15172 id: selection.id,
15173 start: word_range.start,
15174 end: word_range.end,
15175 goal: SelectionGoal::None,
15176 reversed: selection.reversed,
15177 };
15178 }
15179 }
15180 }
15181 }
15182
15183 let mut new_range = old_range.clone();
15184 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15185 new_range = range;
15186 if !node.is_named() {
15187 continue;
15188 }
15189 if !display_map.intersects_fold(new_range.start)
15190 && !display_map.intersects_fold(new_range.end)
15191 {
15192 break;
15193 }
15194 }
15195
15196 selected_larger_node |= new_range != old_range;
15197 Selection {
15198 id: selection.id,
15199 start: new_range.start,
15200 end: new_range.end,
15201 goal: SelectionGoal::None,
15202 reversed: selection.reversed,
15203 }
15204 })
15205 .collect::<Vec<_>>();
15206
15207 if !selected_larger_node {
15208 return; // don't put this call in the history
15209 }
15210
15211 // scroll based on transformation done to the last selection created by the user
15212 let (last_old, last_new) = old_selections
15213 .last()
15214 .zip(new_selections.last().cloned())
15215 .expect("old_selections isn't empty");
15216
15217 // revert selection
15218 let is_selection_reversed = {
15219 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15220 new_selections.last_mut().expect("checked above").reversed =
15221 should_newest_selection_be_reversed;
15222 should_newest_selection_be_reversed
15223 };
15224
15225 if selected_larger_node {
15226 self.select_syntax_node_history.disable_clearing = true;
15227 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15228 s.select(new_selections.clone());
15229 });
15230 self.select_syntax_node_history.disable_clearing = false;
15231 }
15232
15233 let start_row = last_new.start.to_display_point(&display_map).row().0;
15234 let end_row = last_new.end.to_display_point(&display_map).row().0;
15235 let selection_height = end_row - start_row + 1;
15236 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15237
15238 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15239 let scroll_behavior = if fits_on_the_screen {
15240 self.request_autoscroll(Autoscroll::fit(), cx);
15241 SelectSyntaxNodeScrollBehavior::FitSelection
15242 } else if is_selection_reversed {
15243 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15244 SelectSyntaxNodeScrollBehavior::CursorTop
15245 } else {
15246 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15247 SelectSyntaxNodeScrollBehavior::CursorBottom
15248 };
15249
15250 self.select_syntax_node_history.push((
15251 old_selections,
15252 scroll_behavior,
15253 is_selection_reversed,
15254 ));
15255 }
15256
15257 pub fn select_smaller_syntax_node(
15258 &mut self,
15259 _: &SelectSmallerSyntaxNode,
15260 window: &mut Window,
15261 cx: &mut Context<Self>,
15262 ) {
15263 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15264
15265 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15266 self.select_syntax_node_history.pop()
15267 {
15268 if let Some(selection) = selections.last_mut() {
15269 selection.reversed = is_selection_reversed;
15270 }
15271
15272 self.select_syntax_node_history.disable_clearing = true;
15273 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15274 s.select(selections.to_vec());
15275 });
15276 self.select_syntax_node_history.disable_clearing = false;
15277
15278 match scroll_behavior {
15279 SelectSyntaxNodeScrollBehavior::CursorTop => {
15280 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15281 }
15282 SelectSyntaxNodeScrollBehavior::FitSelection => {
15283 self.request_autoscroll(Autoscroll::fit(), cx);
15284 }
15285 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15286 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15287 }
15288 }
15289 }
15290 }
15291
15292 pub fn unwrap_syntax_node(
15293 &mut self,
15294 _: &UnwrapSyntaxNode,
15295 window: &mut Window,
15296 cx: &mut Context<Self>,
15297 ) {
15298 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15299
15300 let buffer = self.buffer.read(cx).snapshot(cx);
15301 let selections = self
15302 .selections
15303 .all::<usize>(cx)
15304 .into_iter()
15305 // subtracting the offset requires sorting
15306 .sorted_by_key(|i| i.start);
15307
15308 let full_edits = selections
15309 .into_iter()
15310 .filter_map(|selection| {
15311 let child = if selection.is_empty()
15312 && let Some((_, ancestor_range)) =
15313 buffer.syntax_ancestor(selection.start..selection.end)
15314 {
15315 ancestor_range
15316 } else {
15317 selection.range()
15318 };
15319
15320 let mut parent = child.clone();
15321 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15322 parent = ancestor_range;
15323 if parent.start < child.start || parent.end > child.end {
15324 break;
15325 }
15326 }
15327
15328 if parent == child {
15329 return None;
15330 }
15331 let text = buffer.text_for_range(child).collect::<String>();
15332 Some((selection.id, parent, text))
15333 })
15334 .collect::<Vec<_>>();
15335 if full_edits.is_empty() {
15336 return;
15337 }
15338
15339 self.transact(window, cx, |this, window, cx| {
15340 this.buffer.update(cx, |buffer, cx| {
15341 buffer.edit(
15342 full_edits
15343 .iter()
15344 .map(|(_, p, t)| (p.clone(), t.clone()))
15345 .collect::<Vec<_>>(),
15346 None,
15347 cx,
15348 );
15349 });
15350 this.change_selections(Default::default(), window, cx, |s| {
15351 let mut offset = 0;
15352 let mut selections = vec![];
15353 for (id, parent, text) in full_edits {
15354 let start = parent.start - offset;
15355 offset += parent.len() - text.len();
15356 selections.push(Selection {
15357 id,
15358 start,
15359 end: start + text.len(),
15360 reversed: false,
15361 goal: Default::default(),
15362 });
15363 }
15364 s.select(selections);
15365 });
15366 });
15367 }
15368
15369 pub fn select_next_syntax_node(
15370 &mut self,
15371 _: &SelectNextSyntaxNode,
15372 window: &mut Window,
15373 cx: &mut Context<Self>,
15374 ) {
15375 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15376 if old_selections.is_empty() {
15377 return;
15378 }
15379
15380 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15381
15382 let buffer = self.buffer.read(cx).snapshot(cx);
15383 let mut selected_sibling = false;
15384
15385 let new_selections = old_selections
15386 .iter()
15387 .map(|selection| {
15388 let old_range = selection.start..selection.end;
15389
15390 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15391 let new_range = node.byte_range();
15392 selected_sibling = true;
15393 Selection {
15394 id: selection.id,
15395 start: new_range.start,
15396 end: new_range.end,
15397 goal: SelectionGoal::None,
15398 reversed: selection.reversed,
15399 }
15400 } else {
15401 selection.clone()
15402 }
15403 })
15404 .collect::<Vec<_>>();
15405
15406 if selected_sibling {
15407 self.change_selections(
15408 SelectionEffects::scroll(Autoscroll::fit()),
15409 window,
15410 cx,
15411 |s| {
15412 s.select(new_selections);
15413 },
15414 );
15415 }
15416 }
15417
15418 pub fn select_prev_syntax_node(
15419 &mut self,
15420 _: &SelectPreviousSyntaxNode,
15421 window: &mut Window,
15422 cx: &mut Context<Self>,
15423 ) {
15424 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15425 if old_selections.is_empty() {
15426 return;
15427 }
15428
15429 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15430
15431 let buffer = self.buffer.read(cx).snapshot(cx);
15432 let mut selected_sibling = false;
15433
15434 let new_selections = old_selections
15435 .iter()
15436 .map(|selection| {
15437 let old_range = selection.start..selection.end;
15438
15439 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15440 let new_range = node.byte_range();
15441 selected_sibling = true;
15442 Selection {
15443 id: selection.id,
15444 start: new_range.start,
15445 end: new_range.end,
15446 goal: SelectionGoal::None,
15447 reversed: selection.reversed,
15448 }
15449 } else {
15450 selection.clone()
15451 }
15452 })
15453 .collect::<Vec<_>>();
15454
15455 if selected_sibling {
15456 self.change_selections(
15457 SelectionEffects::scroll(Autoscroll::fit()),
15458 window,
15459 cx,
15460 |s| {
15461 s.select(new_selections);
15462 },
15463 );
15464 }
15465 }
15466
15467 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15468 if !EditorSettings::get_global(cx).gutter.runnables {
15469 self.clear_tasks();
15470 return Task::ready(());
15471 }
15472 let project = self.project().map(Entity::downgrade);
15473 let task_sources = self.lsp_task_sources(cx);
15474 let multi_buffer = self.buffer.downgrade();
15475 cx.spawn_in(window, async move |editor, cx| {
15476 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15477 let Some(project) = project.and_then(|p| p.upgrade()) else {
15478 return;
15479 };
15480 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15481 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15482 }) else {
15483 return;
15484 };
15485
15486 let hide_runnables = project
15487 .update(cx, |project, _| project.is_via_collab())
15488 .unwrap_or(true);
15489 if hide_runnables {
15490 return;
15491 }
15492 let new_rows =
15493 cx.background_spawn({
15494 let snapshot = display_snapshot.clone();
15495 async move {
15496 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15497 }
15498 })
15499 .await;
15500 let Ok(lsp_tasks) =
15501 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15502 else {
15503 return;
15504 };
15505 let lsp_tasks = lsp_tasks.await;
15506
15507 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15508 lsp_tasks
15509 .into_iter()
15510 .flat_map(|(kind, tasks)| {
15511 tasks.into_iter().filter_map(move |(location, task)| {
15512 Some((kind.clone(), location?, task))
15513 })
15514 })
15515 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15516 let buffer = location.target.buffer;
15517 let buffer_snapshot = buffer.read(cx).snapshot();
15518 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15519 |(excerpt_id, snapshot, _)| {
15520 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15521 display_snapshot
15522 .buffer_snapshot
15523 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15524 } else {
15525 None
15526 }
15527 },
15528 );
15529 if let Some(offset) = offset {
15530 let task_buffer_range =
15531 location.target.range.to_point(&buffer_snapshot);
15532 let context_buffer_range =
15533 task_buffer_range.to_offset(&buffer_snapshot);
15534 let context_range = BufferOffset(context_buffer_range.start)
15535 ..BufferOffset(context_buffer_range.end);
15536
15537 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15538 .or_insert_with(|| RunnableTasks {
15539 templates: Vec::new(),
15540 offset,
15541 column: task_buffer_range.start.column,
15542 extra_variables: HashMap::default(),
15543 context_range,
15544 })
15545 .templates
15546 .push((kind, task.original_task().clone()));
15547 }
15548
15549 acc
15550 })
15551 }) else {
15552 return;
15553 };
15554
15555 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15556 buffer.language_settings(cx).tasks.prefer_lsp
15557 }) else {
15558 return;
15559 };
15560
15561 let rows = Self::runnable_rows(
15562 project,
15563 display_snapshot,
15564 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15565 new_rows,
15566 cx.clone(),
15567 )
15568 .await;
15569 editor
15570 .update(cx, |editor, _| {
15571 editor.clear_tasks();
15572 for (key, mut value) in rows {
15573 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15574 value.templates.extend(lsp_tasks.templates);
15575 }
15576
15577 editor.insert_tasks(key, value);
15578 }
15579 for (key, value) in lsp_tasks_by_rows {
15580 editor.insert_tasks(key, value);
15581 }
15582 })
15583 .ok();
15584 })
15585 }
15586 fn fetch_runnable_ranges(
15587 snapshot: &DisplaySnapshot,
15588 range: Range<Anchor>,
15589 ) -> Vec<language::RunnableRange> {
15590 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15591 }
15592
15593 fn runnable_rows(
15594 project: Entity<Project>,
15595 snapshot: DisplaySnapshot,
15596 prefer_lsp: bool,
15597 runnable_ranges: Vec<RunnableRange>,
15598 cx: AsyncWindowContext,
15599 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15600 cx.spawn(async move |cx| {
15601 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15602 for mut runnable in runnable_ranges {
15603 let Some(tasks) = cx
15604 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15605 .ok()
15606 else {
15607 continue;
15608 };
15609 let mut tasks = tasks.await;
15610
15611 if prefer_lsp {
15612 tasks.retain(|(task_kind, _)| {
15613 !matches!(task_kind, TaskSourceKind::Language { .. })
15614 });
15615 }
15616 if tasks.is_empty() {
15617 continue;
15618 }
15619
15620 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15621 let Some(row) = snapshot
15622 .buffer_snapshot
15623 .buffer_line_for_row(MultiBufferRow(point.row))
15624 .map(|(_, range)| range.start.row)
15625 else {
15626 continue;
15627 };
15628
15629 let context_range =
15630 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15631 runnable_rows.push((
15632 (runnable.buffer_id, row),
15633 RunnableTasks {
15634 templates: tasks,
15635 offset: snapshot
15636 .buffer_snapshot
15637 .anchor_before(runnable.run_range.start),
15638 context_range,
15639 column: point.column,
15640 extra_variables: runnable.extra_captures,
15641 },
15642 ));
15643 }
15644 runnable_rows
15645 })
15646 }
15647
15648 fn templates_with_tags(
15649 project: &Entity<Project>,
15650 runnable: &mut Runnable,
15651 cx: &mut App,
15652 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15653 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15654 let (worktree_id, file) = project
15655 .buffer_for_id(runnable.buffer, cx)
15656 .and_then(|buffer| buffer.read(cx).file())
15657 .map(|file| (file.worktree_id(cx), file.clone()))
15658 .unzip();
15659
15660 (
15661 project.task_store().read(cx).task_inventory().cloned(),
15662 worktree_id,
15663 file,
15664 )
15665 });
15666
15667 let tags = mem::take(&mut runnable.tags);
15668 let language = runnable.language.clone();
15669 cx.spawn(async move |cx| {
15670 let mut templates_with_tags = Vec::new();
15671 if let Some(inventory) = inventory {
15672 for RunnableTag(tag) in tags {
15673 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15674 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15675 }) else {
15676 return templates_with_tags;
15677 };
15678 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15679 move |(_, template)| {
15680 template.tags.iter().any(|source_tag| source_tag == &tag)
15681 },
15682 ));
15683 }
15684 }
15685 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15686
15687 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15688 // Strongest source wins; if we have worktree tag binding, prefer that to
15689 // global and language bindings;
15690 // if we have a global binding, prefer that to language binding.
15691 let first_mismatch = templates_with_tags
15692 .iter()
15693 .position(|(tag_source, _)| tag_source != leading_tag_source);
15694 if let Some(index) = first_mismatch {
15695 templates_with_tags.truncate(index);
15696 }
15697 }
15698
15699 templates_with_tags
15700 })
15701 }
15702
15703 pub fn move_to_enclosing_bracket(
15704 &mut self,
15705 _: &MoveToEnclosingBracket,
15706 window: &mut Window,
15707 cx: &mut Context<Self>,
15708 ) {
15709 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15710 self.change_selections(Default::default(), window, cx, |s| {
15711 s.move_offsets_with(|snapshot, selection| {
15712 let Some(enclosing_bracket_ranges) =
15713 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15714 else {
15715 return;
15716 };
15717
15718 let mut best_length = usize::MAX;
15719 let mut best_inside = false;
15720 let mut best_in_bracket_range = false;
15721 let mut best_destination = None;
15722 for (open, close) in enclosing_bracket_ranges {
15723 let close = close.to_inclusive();
15724 let length = close.end() - open.start;
15725 let inside = selection.start >= open.end && selection.end <= *close.start();
15726 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15727 || close.contains(&selection.head());
15728
15729 // If best is next to a bracket and current isn't, skip
15730 if !in_bracket_range && best_in_bracket_range {
15731 continue;
15732 }
15733
15734 // Prefer smaller lengths unless best is inside and current isn't
15735 if length > best_length && (best_inside || !inside) {
15736 continue;
15737 }
15738
15739 best_length = length;
15740 best_inside = inside;
15741 best_in_bracket_range = in_bracket_range;
15742 best_destination = Some(
15743 if close.contains(&selection.start) && close.contains(&selection.end) {
15744 if inside { open.end } else { open.start }
15745 } else if inside {
15746 *close.start()
15747 } else {
15748 *close.end()
15749 },
15750 );
15751 }
15752
15753 if let Some(destination) = best_destination {
15754 selection.collapse_to(destination, SelectionGoal::None);
15755 }
15756 })
15757 });
15758 }
15759
15760 pub fn undo_selection(
15761 &mut self,
15762 _: &UndoSelection,
15763 window: &mut Window,
15764 cx: &mut Context<Self>,
15765 ) {
15766 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15767 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15768 self.selection_history.mode = SelectionHistoryMode::Undoing;
15769 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15770 this.end_selection(window, cx);
15771 this.change_selections(
15772 SelectionEffects::scroll(Autoscroll::newest()),
15773 window,
15774 cx,
15775 |s| s.select_anchors(entry.selections.to_vec()),
15776 );
15777 });
15778 self.selection_history.mode = SelectionHistoryMode::Normal;
15779
15780 self.select_next_state = entry.select_next_state;
15781 self.select_prev_state = entry.select_prev_state;
15782 self.add_selections_state = entry.add_selections_state;
15783 }
15784 }
15785
15786 pub fn redo_selection(
15787 &mut self,
15788 _: &RedoSelection,
15789 window: &mut Window,
15790 cx: &mut Context<Self>,
15791 ) {
15792 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15793 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15794 self.selection_history.mode = SelectionHistoryMode::Redoing;
15795 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15796 this.end_selection(window, cx);
15797 this.change_selections(
15798 SelectionEffects::scroll(Autoscroll::newest()),
15799 window,
15800 cx,
15801 |s| s.select_anchors(entry.selections.to_vec()),
15802 );
15803 });
15804 self.selection_history.mode = SelectionHistoryMode::Normal;
15805
15806 self.select_next_state = entry.select_next_state;
15807 self.select_prev_state = entry.select_prev_state;
15808 self.add_selections_state = entry.add_selections_state;
15809 }
15810 }
15811
15812 pub fn expand_excerpts(
15813 &mut self,
15814 action: &ExpandExcerpts,
15815 _: &mut Window,
15816 cx: &mut Context<Self>,
15817 ) {
15818 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15819 }
15820
15821 pub fn expand_excerpts_down(
15822 &mut self,
15823 action: &ExpandExcerptsDown,
15824 _: &mut Window,
15825 cx: &mut Context<Self>,
15826 ) {
15827 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15828 }
15829
15830 pub fn expand_excerpts_up(
15831 &mut self,
15832 action: &ExpandExcerptsUp,
15833 _: &mut Window,
15834 cx: &mut Context<Self>,
15835 ) {
15836 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15837 }
15838
15839 pub fn expand_excerpts_for_direction(
15840 &mut self,
15841 lines: u32,
15842 direction: ExpandExcerptDirection,
15843
15844 cx: &mut Context<Self>,
15845 ) {
15846 let selections = self.selections.disjoint_anchors_arc();
15847
15848 let lines = if lines == 0 {
15849 EditorSettings::get_global(cx).expand_excerpt_lines
15850 } else {
15851 lines
15852 };
15853
15854 self.buffer.update(cx, |buffer, cx| {
15855 let snapshot = buffer.snapshot(cx);
15856 let mut excerpt_ids = selections
15857 .iter()
15858 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15859 .collect::<Vec<_>>();
15860 excerpt_ids.sort();
15861 excerpt_ids.dedup();
15862 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15863 })
15864 }
15865
15866 pub fn expand_excerpt(
15867 &mut self,
15868 excerpt: ExcerptId,
15869 direction: ExpandExcerptDirection,
15870 window: &mut Window,
15871 cx: &mut Context<Self>,
15872 ) {
15873 let current_scroll_position = self.scroll_position(cx);
15874 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15875 let mut should_scroll_up = false;
15876
15877 if direction == ExpandExcerptDirection::Down {
15878 let multi_buffer = self.buffer.read(cx);
15879 let snapshot = multi_buffer.snapshot(cx);
15880 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15881 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15882 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15883 {
15884 let buffer_snapshot = buffer.read(cx).snapshot();
15885 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15886 let last_row = buffer_snapshot.max_point().row;
15887 let lines_below = last_row.saturating_sub(excerpt_end_row);
15888 should_scroll_up = lines_below >= lines_to_expand;
15889 }
15890 }
15891
15892 self.buffer.update(cx, |buffer, cx| {
15893 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15894 });
15895
15896 if should_scroll_up {
15897 let new_scroll_position =
15898 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15899 self.set_scroll_position(new_scroll_position, window, cx);
15900 }
15901 }
15902
15903 pub fn go_to_singleton_buffer_point(
15904 &mut self,
15905 point: Point,
15906 window: &mut Window,
15907 cx: &mut Context<Self>,
15908 ) {
15909 self.go_to_singleton_buffer_range(point..point, window, cx);
15910 }
15911
15912 pub fn go_to_singleton_buffer_range(
15913 &mut self,
15914 range: Range<Point>,
15915 window: &mut Window,
15916 cx: &mut Context<Self>,
15917 ) {
15918 let multibuffer = self.buffer().read(cx);
15919 let Some(buffer) = multibuffer.as_singleton() else {
15920 return;
15921 };
15922 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15923 return;
15924 };
15925 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15926 return;
15927 };
15928 self.change_selections(
15929 SelectionEffects::default().nav_history(true),
15930 window,
15931 cx,
15932 |s| s.select_anchor_ranges([start..end]),
15933 );
15934 }
15935
15936 pub fn go_to_diagnostic(
15937 &mut self,
15938 action: &GoToDiagnostic,
15939 window: &mut Window,
15940 cx: &mut Context<Self>,
15941 ) {
15942 if !self.diagnostics_enabled() {
15943 return;
15944 }
15945 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15946 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15947 }
15948
15949 pub fn go_to_prev_diagnostic(
15950 &mut self,
15951 action: &GoToPreviousDiagnostic,
15952 window: &mut Window,
15953 cx: &mut Context<Self>,
15954 ) {
15955 if !self.diagnostics_enabled() {
15956 return;
15957 }
15958 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15959 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15960 }
15961
15962 pub fn go_to_diagnostic_impl(
15963 &mut self,
15964 direction: Direction,
15965 severity: GoToDiagnosticSeverityFilter,
15966 window: &mut Window,
15967 cx: &mut Context<Self>,
15968 ) {
15969 let buffer = self.buffer.read(cx).snapshot(cx);
15970 let selection = self.selections.newest::<usize>(cx);
15971
15972 let mut active_group_id = None;
15973 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15974 && active_group.active_range.start.to_offset(&buffer) == selection.start
15975 {
15976 active_group_id = Some(active_group.group_id);
15977 }
15978
15979 fn filtered<'a>(
15980 snapshot: EditorSnapshot,
15981 severity: GoToDiagnosticSeverityFilter,
15982 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
15983 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
15984 diagnostics
15985 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15986 .filter(|entry| entry.range.start != entry.range.end)
15987 .filter(|entry| !entry.diagnostic.is_unnecessary)
15988 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15989 }
15990
15991 let snapshot = self.snapshot(window, cx);
15992 let before = filtered(
15993 snapshot.clone(),
15994 severity,
15995 buffer
15996 .diagnostics_in_range(0..selection.start)
15997 .filter(|entry| entry.range.start <= selection.start),
15998 );
15999 let after = filtered(
16000 snapshot,
16001 severity,
16002 buffer
16003 .diagnostics_in_range(selection.start..buffer.len())
16004 .filter(|entry| entry.range.start >= selection.start),
16005 );
16006
16007 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16008 if direction == Direction::Prev {
16009 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16010 {
16011 for diagnostic in prev_diagnostics.into_iter().rev() {
16012 if diagnostic.range.start != selection.start
16013 || active_group_id
16014 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16015 {
16016 found = Some(diagnostic);
16017 break 'outer;
16018 }
16019 }
16020 }
16021 } else {
16022 for diagnostic in after.chain(before) {
16023 if diagnostic.range.start != selection.start
16024 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16025 {
16026 found = Some(diagnostic);
16027 break;
16028 }
16029 }
16030 }
16031 let Some(next_diagnostic) = found else {
16032 return;
16033 };
16034
16035 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16036 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16037 return;
16038 };
16039 self.change_selections(Default::default(), window, cx, |s| {
16040 s.select_ranges(vec![
16041 next_diagnostic.range.start..next_diagnostic.range.start,
16042 ])
16043 });
16044 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16045 self.refresh_edit_prediction(false, true, window, cx);
16046 }
16047
16048 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16049 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16050 let snapshot = self.snapshot(window, cx);
16051 let selection = self.selections.newest::<Point>(cx);
16052 self.go_to_hunk_before_or_after_position(
16053 &snapshot,
16054 selection.head(),
16055 Direction::Next,
16056 window,
16057 cx,
16058 );
16059 }
16060
16061 pub fn go_to_hunk_before_or_after_position(
16062 &mut self,
16063 snapshot: &EditorSnapshot,
16064 position: Point,
16065 direction: Direction,
16066 window: &mut Window,
16067 cx: &mut Context<Editor>,
16068 ) {
16069 let row = if direction == Direction::Next {
16070 self.hunk_after_position(snapshot, position)
16071 .map(|hunk| hunk.row_range.start)
16072 } else {
16073 self.hunk_before_position(snapshot, position)
16074 };
16075
16076 if let Some(row) = row {
16077 let destination = Point::new(row.0, 0);
16078 let autoscroll = Autoscroll::center();
16079
16080 self.unfold_ranges(&[destination..destination], false, false, cx);
16081 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16082 s.select_ranges([destination..destination]);
16083 });
16084 }
16085 }
16086
16087 fn hunk_after_position(
16088 &mut self,
16089 snapshot: &EditorSnapshot,
16090 position: Point,
16091 ) -> Option<MultiBufferDiffHunk> {
16092 snapshot
16093 .buffer_snapshot
16094 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16095 .find(|hunk| hunk.row_range.start.0 > position.row)
16096 .or_else(|| {
16097 snapshot
16098 .buffer_snapshot
16099 .diff_hunks_in_range(Point::zero()..position)
16100 .find(|hunk| hunk.row_range.end.0 < position.row)
16101 })
16102 }
16103
16104 fn go_to_prev_hunk(
16105 &mut self,
16106 _: &GoToPreviousHunk,
16107 window: &mut Window,
16108 cx: &mut Context<Self>,
16109 ) {
16110 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16111 let snapshot = self.snapshot(window, cx);
16112 let selection = self.selections.newest::<Point>(cx);
16113 self.go_to_hunk_before_or_after_position(
16114 &snapshot,
16115 selection.head(),
16116 Direction::Prev,
16117 window,
16118 cx,
16119 );
16120 }
16121
16122 fn hunk_before_position(
16123 &mut self,
16124 snapshot: &EditorSnapshot,
16125 position: Point,
16126 ) -> Option<MultiBufferRow> {
16127 snapshot
16128 .buffer_snapshot
16129 .diff_hunk_before(position)
16130 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
16131 }
16132
16133 fn go_to_next_change(
16134 &mut self,
16135 _: &GoToNextChange,
16136 window: &mut Window,
16137 cx: &mut Context<Self>,
16138 ) {
16139 if let Some(selections) = self
16140 .change_list
16141 .next_change(1, Direction::Next)
16142 .map(|s| s.to_vec())
16143 {
16144 self.change_selections(Default::default(), window, cx, |s| {
16145 let map = s.display_map();
16146 s.select_display_ranges(selections.iter().map(|a| {
16147 let point = a.to_display_point(&map);
16148 point..point
16149 }))
16150 })
16151 }
16152 }
16153
16154 fn go_to_previous_change(
16155 &mut self,
16156 _: &GoToPreviousChange,
16157 window: &mut Window,
16158 cx: &mut Context<Self>,
16159 ) {
16160 if let Some(selections) = self
16161 .change_list
16162 .next_change(1, Direction::Prev)
16163 .map(|s| s.to_vec())
16164 {
16165 self.change_selections(Default::default(), window, cx, |s| {
16166 let map = s.display_map();
16167 s.select_display_ranges(selections.iter().map(|a| {
16168 let point = a.to_display_point(&map);
16169 point..point
16170 }))
16171 })
16172 }
16173 }
16174
16175 pub fn go_to_next_document_highlight(
16176 &mut self,
16177 _: &GoToNextDocumentHighlight,
16178 window: &mut Window,
16179 cx: &mut Context<Self>,
16180 ) {
16181 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16182 }
16183
16184 pub fn go_to_prev_document_highlight(
16185 &mut self,
16186 _: &GoToPreviousDocumentHighlight,
16187 window: &mut Window,
16188 cx: &mut Context<Self>,
16189 ) {
16190 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16191 }
16192
16193 pub fn go_to_document_highlight_before_or_after_position(
16194 &mut self,
16195 direction: Direction,
16196 window: &mut Window,
16197 cx: &mut Context<Editor>,
16198 ) {
16199 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16200 let snapshot = self.snapshot(window, cx);
16201 let buffer = &snapshot.buffer_snapshot;
16202 let position = self.selections.newest::<Point>(cx).head();
16203 let anchor_position = buffer.anchor_after(position);
16204
16205 // Get all document highlights (both read and write)
16206 let mut all_highlights = Vec::new();
16207
16208 if let Some((_, read_highlights)) = self
16209 .background_highlights
16210 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16211 {
16212 all_highlights.extend(read_highlights.iter());
16213 }
16214
16215 if let Some((_, write_highlights)) = self
16216 .background_highlights
16217 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16218 {
16219 all_highlights.extend(write_highlights.iter());
16220 }
16221
16222 if all_highlights.is_empty() {
16223 return;
16224 }
16225
16226 // Sort highlights by position
16227 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16228
16229 let target_highlight = match direction {
16230 Direction::Next => {
16231 // Find the first highlight after the current position
16232 all_highlights
16233 .iter()
16234 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16235 }
16236 Direction::Prev => {
16237 // Find the last highlight before the current position
16238 all_highlights
16239 .iter()
16240 .rev()
16241 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16242 }
16243 };
16244
16245 if let Some(highlight) = target_highlight {
16246 let destination = highlight.start.to_point(buffer);
16247 let autoscroll = Autoscroll::center();
16248
16249 self.unfold_ranges(&[destination..destination], false, false, cx);
16250 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16251 s.select_ranges([destination..destination]);
16252 });
16253 }
16254 }
16255
16256 fn go_to_line<T: 'static>(
16257 &mut self,
16258 position: Anchor,
16259 highlight_color: Option<Hsla>,
16260 window: &mut Window,
16261 cx: &mut Context<Self>,
16262 ) {
16263 let snapshot = self.snapshot(window, cx).display_snapshot;
16264 let position = position.to_point(&snapshot.buffer_snapshot);
16265 let start = snapshot
16266 .buffer_snapshot
16267 .clip_point(Point::new(position.row, 0), Bias::Left);
16268 let end = start + Point::new(1, 0);
16269 let start = snapshot.buffer_snapshot.anchor_before(start);
16270 let end = snapshot.buffer_snapshot.anchor_before(end);
16271
16272 self.highlight_rows::<T>(
16273 start..end,
16274 highlight_color
16275 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16276 Default::default(),
16277 cx,
16278 );
16279
16280 if self.buffer.read(cx).is_singleton() {
16281 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16282 }
16283 }
16284
16285 pub fn go_to_definition(
16286 &mut self,
16287 _: &GoToDefinition,
16288 window: &mut Window,
16289 cx: &mut Context<Self>,
16290 ) -> Task<Result<Navigated>> {
16291 let definition =
16292 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16293 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16294 cx.spawn_in(window, async move |editor, cx| {
16295 if definition.await? == Navigated::Yes {
16296 return Ok(Navigated::Yes);
16297 }
16298 match fallback_strategy {
16299 GoToDefinitionFallback::None => Ok(Navigated::No),
16300 GoToDefinitionFallback::FindAllReferences => {
16301 match editor.update_in(cx, |editor, window, cx| {
16302 editor.find_all_references(&FindAllReferences, window, cx)
16303 })? {
16304 Some(references) => references.await,
16305 None => Ok(Navigated::No),
16306 }
16307 }
16308 }
16309 })
16310 }
16311
16312 pub fn go_to_declaration(
16313 &mut self,
16314 _: &GoToDeclaration,
16315 window: &mut Window,
16316 cx: &mut Context<Self>,
16317 ) -> Task<Result<Navigated>> {
16318 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16319 }
16320
16321 pub fn go_to_declaration_split(
16322 &mut self,
16323 _: &GoToDeclaration,
16324 window: &mut Window,
16325 cx: &mut Context<Self>,
16326 ) -> Task<Result<Navigated>> {
16327 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16328 }
16329
16330 pub fn go_to_implementation(
16331 &mut self,
16332 _: &GoToImplementation,
16333 window: &mut Window,
16334 cx: &mut Context<Self>,
16335 ) -> Task<Result<Navigated>> {
16336 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16337 }
16338
16339 pub fn go_to_implementation_split(
16340 &mut self,
16341 _: &GoToImplementationSplit,
16342 window: &mut Window,
16343 cx: &mut Context<Self>,
16344 ) -> Task<Result<Navigated>> {
16345 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16346 }
16347
16348 pub fn go_to_type_definition(
16349 &mut self,
16350 _: &GoToTypeDefinition,
16351 window: &mut Window,
16352 cx: &mut Context<Self>,
16353 ) -> Task<Result<Navigated>> {
16354 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16355 }
16356
16357 pub fn go_to_definition_split(
16358 &mut self,
16359 _: &GoToDefinitionSplit,
16360 window: &mut Window,
16361 cx: &mut Context<Self>,
16362 ) -> Task<Result<Navigated>> {
16363 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16364 }
16365
16366 pub fn go_to_type_definition_split(
16367 &mut self,
16368 _: &GoToTypeDefinitionSplit,
16369 window: &mut Window,
16370 cx: &mut Context<Self>,
16371 ) -> Task<Result<Navigated>> {
16372 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16373 }
16374
16375 fn go_to_definition_of_kind(
16376 &mut self,
16377 kind: GotoDefinitionKind,
16378 split: bool,
16379 window: &mut Window,
16380 cx: &mut Context<Self>,
16381 ) -> Task<Result<Navigated>> {
16382 let Some(provider) = self.semantics_provider.clone() else {
16383 return Task::ready(Ok(Navigated::No));
16384 };
16385 let head = self.selections.newest::<usize>(cx).head();
16386 let buffer = self.buffer.read(cx);
16387 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16388 return Task::ready(Ok(Navigated::No));
16389 };
16390 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16391 return Task::ready(Ok(Navigated::No));
16392 };
16393
16394 cx.spawn_in(window, async move |editor, cx| {
16395 let Some(definitions) = definitions.await? else {
16396 return Ok(Navigated::No);
16397 };
16398 let navigated = editor
16399 .update_in(cx, |editor, window, cx| {
16400 editor.navigate_to_hover_links(
16401 Some(kind),
16402 definitions
16403 .into_iter()
16404 .filter(|location| {
16405 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16406 })
16407 .map(HoverLink::Text)
16408 .collect::<Vec<_>>(),
16409 split,
16410 window,
16411 cx,
16412 )
16413 })?
16414 .await?;
16415 anyhow::Ok(navigated)
16416 })
16417 }
16418
16419 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16420 let selection = self.selections.newest_anchor();
16421 let head = selection.head();
16422 let tail = selection.tail();
16423
16424 let Some((buffer, start_position)) =
16425 self.buffer.read(cx).text_anchor_for_position(head, cx)
16426 else {
16427 return;
16428 };
16429
16430 let end_position = if head != tail {
16431 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16432 return;
16433 };
16434 Some(pos)
16435 } else {
16436 None
16437 };
16438
16439 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16440 let url = if let Some(end_pos) = end_position {
16441 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16442 } else {
16443 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16444 };
16445
16446 if let Some(url) = url {
16447 cx.update(|window, cx| {
16448 if parse_zed_link(&url, cx).is_some() {
16449 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16450 } else {
16451 cx.open_url(&url);
16452 }
16453 })?;
16454 }
16455
16456 anyhow::Ok(())
16457 });
16458
16459 url_finder.detach();
16460 }
16461
16462 pub fn open_selected_filename(
16463 &mut self,
16464 _: &OpenSelectedFilename,
16465 window: &mut Window,
16466 cx: &mut Context<Self>,
16467 ) {
16468 let Some(workspace) = self.workspace() else {
16469 return;
16470 };
16471
16472 let position = self.selections.newest_anchor().head();
16473
16474 let Some((buffer, buffer_position)) =
16475 self.buffer.read(cx).text_anchor_for_position(position, cx)
16476 else {
16477 return;
16478 };
16479
16480 let project = self.project.clone();
16481
16482 cx.spawn_in(window, async move |_, cx| {
16483 let result = find_file(&buffer, project, buffer_position, cx).await;
16484
16485 if let Some((_, path)) = result {
16486 workspace
16487 .update_in(cx, |workspace, window, cx| {
16488 workspace.open_resolved_path(path, window, cx)
16489 })?
16490 .await?;
16491 }
16492 anyhow::Ok(())
16493 })
16494 .detach();
16495 }
16496
16497 pub(crate) fn navigate_to_hover_links(
16498 &mut self,
16499 kind: Option<GotoDefinitionKind>,
16500 definitions: Vec<HoverLink>,
16501 split: bool,
16502 window: &mut Window,
16503 cx: &mut Context<Editor>,
16504 ) -> Task<Result<Navigated>> {
16505 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16506 let mut first_url_or_file = None;
16507 let definitions: Vec<_> = definitions
16508 .into_iter()
16509 .filter_map(|def| match def {
16510 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16511 HoverLink::InlayHint(lsp_location, server_id) => {
16512 let computation =
16513 self.compute_target_location(lsp_location, server_id, window, cx);
16514 Some(cx.background_spawn(computation))
16515 }
16516 HoverLink::Url(url) => {
16517 first_url_or_file = Some(Either::Left(url));
16518 None
16519 }
16520 HoverLink::File(path) => {
16521 first_url_or_file = Some(Either::Right(path));
16522 None
16523 }
16524 })
16525 .collect();
16526
16527 let workspace = self.workspace();
16528
16529 cx.spawn_in(window, async move |editor, cx| {
16530 let locations: Vec<Location> = future::join_all(definitions)
16531 .await
16532 .into_iter()
16533 .filter_map(|location| location.transpose())
16534 .collect::<Result<_>>()
16535 .context("location tasks")?;
16536 let mut locations = cx.update(|_, cx| {
16537 locations
16538 .into_iter()
16539 .map(|location| {
16540 let buffer = location.buffer.read(cx);
16541 (location.buffer, location.range.to_point(buffer))
16542 })
16543 .into_group_map()
16544 })?;
16545 let mut num_locations = 0;
16546 for ranges in locations.values_mut() {
16547 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16548 ranges.dedup();
16549 num_locations += ranges.len();
16550 }
16551
16552 if num_locations > 1 {
16553 let Some(workspace) = workspace else {
16554 return Ok(Navigated::No);
16555 };
16556
16557 let tab_kind = match kind {
16558 Some(GotoDefinitionKind::Implementation) => "Implementations",
16559 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16560 Some(GotoDefinitionKind::Declaration) => "Declarations",
16561 Some(GotoDefinitionKind::Type) => "Types",
16562 };
16563 let title = editor
16564 .update_in(cx, |_, _, cx| {
16565 let target = locations
16566 .iter()
16567 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16568 .map(|(buffer, location)| {
16569 buffer
16570 .read(cx)
16571 .text_for_range(location.clone())
16572 .collect::<String>()
16573 })
16574 .filter(|text| !text.contains('\n'))
16575 .unique()
16576 .take(3)
16577 .join(", ");
16578 if target.is_empty() {
16579 tab_kind.to_owned()
16580 } else {
16581 format!("{tab_kind} for {target}")
16582 }
16583 })
16584 .context("buffer title")?;
16585
16586 let opened = workspace
16587 .update_in(cx, |workspace, window, cx| {
16588 Self::open_locations_in_multibuffer(
16589 workspace,
16590 locations,
16591 title,
16592 split,
16593 MultibufferSelectionMode::First,
16594 window,
16595 cx,
16596 )
16597 })
16598 .is_ok();
16599
16600 anyhow::Ok(Navigated::from_bool(opened))
16601 } else if num_locations == 0 {
16602 // If there is one url or file, open it directly
16603 match first_url_or_file {
16604 Some(Either::Left(url)) => {
16605 cx.update(|_, cx| cx.open_url(&url))?;
16606 Ok(Navigated::Yes)
16607 }
16608 Some(Either::Right(path)) => {
16609 let Some(workspace) = workspace else {
16610 return Ok(Navigated::No);
16611 };
16612
16613 workspace
16614 .update_in(cx, |workspace, window, cx| {
16615 workspace.open_resolved_path(path, window, cx)
16616 })?
16617 .await?;
16618 Ok(Navigated::Yes)
16619 }
16620 None => Ok(Navigated::No),
16621 }
16622 } else {
16623 let Some(workspace) = workspace else {
16624 return Ok(Navigated::No);
16625 };
16626
16627 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16628 let target_range = target_ranges.first().unwrap().clone();
16629
16630 editor.update_in(cx, |editor, window, cx| {
16631 let range = target_range.to_point(target_buffer.read(cx));
16632 let range = editor.range_for_match(&range);
16633 let range = collapse_multiline_range(range);
16634
16635 if !split
16636 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16637 {
16638 editor.go_to_singleton_buffer_range(range, window, cx);
16639 } else {
16640 let pane = workspace.read(cx).active_pane().clone();
16641 window.defer(cx, move |window, cx| {
16642 let target_editor: Entity<Self> =
16643 workspace.update(cx, |workspace, cx| {
16644 let pane = if split {
16645 workspace.adjacent_pane(window, cx)
16646 } else {
16647 workspace.active_pane().clone()
16648 };
16649
16650 workspace.open_project_item(
16651 pane,
16652 target_buffer.clone(),
16653 true,
16654 true,
16655 window,
16656 cx,
16657 )
16658 });
16659 target_editor.update(cx, |target_editor, cx| {
16660 // When selecting a definition in a different buffer, disable the nav history
16661 // to avoid creating a history entry at the previous cursor location.
16662 pane.update(cx, |pane, _| pane.disable_history());
16663 target_editor.go_to_singleton_buffer_range(range, window, cx);
16664 pane.update(cx, |pane, _| pane.enable_history());
16665 });
16666 });
16667 }
16668 Navigated::Yes
16669 })
16670 }
16671 })
16672 }
16673
16674 fn compute_target_location(
16675 &self,
16676 lsp_location: lsp::Location,
16677 server_id: LanguageServerId,
16678 window: &mut Window,
16679 cx: &mut Context<Self>,
16680 ) -> Task<anyhow::Result<Option<Location>>> {
16681 let Some(project) = self.project.clone() else {
16682 return Task::ready(Ok(None));
16683 };
16684
16685 cx.spawn_in(window, async move |editor, cx| {
16686 let location_task = editor.update(cx, |_, cx| {
16687 project.update(cx, |project, cx| {
16688 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16689 })
16690 })?;
16691 let location = Some({
16692 let target_buffer_handle = location_task.await.context("open local buffer")?;
16693 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16694 let target_start = target_buffer
16695 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16696 let target_end = target_buffer
16697 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16698 target_buffer.anchor_after(target_start)
16699 ..target_buffer.anchor_before(target_end)
16700 })?;
16701 Location {
16702 buffer: target_buffer_handle,
16703 range,
16704 }
16705 });
16706 Ok(location)
16707 })
16708 }
16709
16710 pub fn find_all_references(
16711 &mut self,
16712 _: &FindAllReferences,
16713 window: &mut Window,
16714 cx: &mut Context<Self>,
16715 ) -> Option<Task<Result<Navigated>>> {
16716 let selection = self.selections.newest::<usize>(cx);
16717 let multi_buffer = self.buffer.read(cx);
16718 let head = selection.head();
16719
16720 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16721 let head_anchor = multi_buffer_snapshot.anchor_at(
16722 head,
16723 if head < selection.tail() {
16724 Bias::Right
16725 } else {
16726 Bias::Left
16727 },
16728 );
16729
16730 match self
16731 .find_all_references_task_sources
16732 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16733 {
16734 Ok(_) => {
16735 log::info!(
16736 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16737 );
16738 return None;
16739 }
16740 Err(i) => {
16741 self.find_all_references_task_sources.insert(i, head_anchor);
16742 }
16743 }
16744
16745 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16746 let workspace = self.workspace()?;
16747 let project = workspace.read(cx).project().clone();
16748 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16749 Some(cx.spawn_in(window, async move |editor, cx| {
16750 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16751 if let Ok(i) = editor
16752 .find_all_references_task_sources
16753 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16754 {
16755 editor.find_all_references_task_sources.remove(i);
16756 }
16757 });
16758
16759 let Some(locations) = references.await? else {
16760 return anyhow::Ok(Navigated::No);
16761 };
16762 let mut locations = cx.update(|_, cx| {
16763 locations
16764 .into_iter()
16765 .map(|location| {
16766 let buffer = location.buffer.read(cx);
16767 (location.buffer, location.range.to_point(buffer))
16768 })
16769 .into_group_map()
16770 })?;
16771 if locations.is_empty() {
16772 return anyhow::Ok(Navigated::No);
16773 }
16774 for ranges in locations.values_mut() {
16775 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16776 ranges.dedup();
16777 }
16778
16779 workspace.update_in(cx, |workspace, window, cx| {
16780 let target = locations
16781 .iter()
16782 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16783 .map(|(buffer, location)| {
16784 buffer
16785 .read(cx)
16786 .text_for_range(location.clone())
16787 .collect::<String>()
16788 })
16789 .filter(|text| !text.contains('\n'))
16790 .unique()
16791 .take(3)
16792 .join(", ");
16793 let title = if target.is_empty() {
16794 "References".to_owned()
16795 } else {
16796 format!("References to {target}")
16797 };
16798 Self::open_locations_in_multibuffer(
16799 workspace,
16800 locations,
16801 title,
16802 false,
16803 MultibufferSelectionMode::First,
16804 window,
16805 cx,
16806 );
16807 Navigated::Yes
16808 })
16809 }))
16810 }
16811
16812 /// Opens a multibuffer with the given project locations in it
16813 pub fn open_locations_in_multibuffer(
16814 workspace: &mut Workspace,
16815 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16816 title: String,
16817 split: bool,
16818 multibuffer_selection_mode: MultibufferSelectionMode,
16819 window: &mut Window,
16820 cx: &mut Context<Workspace>,
16821 ) {
16822 if locations.is_empty() {
16823 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16824 return;
16825 }
16826
16827 let capability = workspace.project().read(cx).capability();
16828 let mut ranges = <Vec<Range<Anchor>>>::new();
16829
16830 // a key to find existing multibuffer editors with the same set of locations
16831 // to prevent us from opening more and more multibuffer tabs for searches and the like
16832 let mut key = (title.clone(), vec![]);
16833 let excerpt_buffer = cx.new(|cx| {
16834 let key = &mut key.1;
16835 let mut multibuffer = MultiBuffer::new(capability);
16836 for (buffer, mut ranges_for_buffer) in locations {
16837 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16838 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16839 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16840 PathKey::for_buffer(&buffer, cx),
16841 buffer.clone(),
16842 ranges_for_buffer,
16843 multibuffer_context_lines(cx),
16844 cx,
16845 );
16846 ranges.extend(new_ranges)
16847 }
16848
16849 multibuffer.with_title(title)
16850 });
16851 let existing = workspace.active_pane().update(cx, |pane, cx| {
16852 pane.items()
16853 .filter_map(|item| item.downcast::<Editor>())
16854 .find(|editor| {
16855 editor
16856 .read(cx)
16857 .lookup_key
16858 .as_ref()
16859 .and_then(|it| {
16860 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16861 })
16862 .is_some_and(|it| *it == key)
16863 })
16864 });
16865 let editor = existing.unwrap_or_else(|| {
16866 cx.new(|cx| {
16867 let mut editor = Editor::for_multibuffer(
16868 excerpt_buffer,
16869 Some(workspace.project().clone()),
16870 window,
16871 cx,
16872 );
16873 editor.lookup_key = Some(Box::new(key));
16874 editor
16875 })
16876 });
16877 editor.update(cx, |editor, cx| {
16878 match multibuffer_selection_mode {
16879 MultibufferSelectionMode::First => {
16880 if let Some(first_range) = ranges.first() {
16881 editor.change_selections(
16882 SelectionEffects::no_scroll(),
16883 window,
16884 cx,
16885 |selections| {
16886 selections.clear_disjoint();
16887 selections
16888 .select_anchor_ranges(std::iter::once(first_range.clone()));
16889 },
16890 );
16891 }
16892 editor.highlight_background::<Self>(
16893 &ranges,
16894 |theme| theme.colors().editor_highlighted_line_background,
16895 cx,
16896 );
16897 }
16898 MultibufferSelectionMode::All => {
16899 editor.change_selections(
16900 SelectionEffects::no_scroll(),
16901 window,
16902 cx,
16903 |selections| {
16904 selections.clear_disjoint();
16905 selections.select_anchor_ranges(ranges);
16906 },
16907 );
16908 }
16909 }
16910 editor.register_buffers_with_language_servers(cx);
16911 });
16912
16913 let item = Box::new(editor);
16914 let item_id = item.item_id();
16915
16916 if split {
16917 let pane = workspace.adjacent_pane(window, cx);
16918 workspace.add_item(pane, item, None, true, true, window, cx);
16919 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16920 let (preview_item_id, preview_item_idx) =
16921 workspace.active_pane().read_with(cx, |pane, _| {
16922 (pane.preview_item_id(), pane.preview_item_idx())
16923 });
16924
16925 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16926
16927 if let Some(preview_item_id) = preview_item_id {
16928 workspace.active_pane().update(cx, |pane, cx| {
16929 pane.remove_item(preview_item_id, false, false, window, cx);
16930 });
16931 }
16932 } else {
16933 workspace.add_item_to_active_pane(item, None, true, window, cx);
16934 }
16935 workspace.active_pane().update(cx, |pane, cx| {
16936 pane.set_preview_item_id(Some(item_id), cx);
16937 });
16938 }
16939
16940 pub fn rename(
16941 &mut self,
16942 _: &Rename,
16943 window: &mut Window,
16944 cx: &mut Context<Self>,
16945 ) -> Option<Task<Result<()>>> {
16946 use language::ToOffset as _;
16947
16948 let provider = self.semantics_provider.clone()?;
16949 let selection = self.selections.newest_anchor().clone();
16950 let (cursor_buffer, cursor_buffer_position) = self
16951 .buffer
16952 .read(cx)
16953 .text_anchor_for_position(selection.head(), cx)?;
16954 let (tail_buffer, cursor_buffer_position_end) = self
16955 .buffer
16956 .read(cx)
16957 .text_anchor_for_position(selection.tail(), cx)?;
16958 if tail_buffer != cursor_buffer {
16959 return None;
16960 }
16961
16962 let snapshot = cursor_buffer.read(cx).snapshot();
16963 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16964 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16965 let prepare_rename = provider
16966 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16967 .unwrap_or_else(|| Task::ready(Ok(None)));
16968 drop(snapshot);
16969
16970 Some(cx.spawn_in(window, async move |this, cx| {
16971 let rename_range = if let Some(range) = prepare_rename.await? {
16972 Some(range)
16973 } else {
16974 this.update(cx, |this, cx| {
16975 let buffer = this.buffer.read(cx).snapshot(cx);
16976 let mut buffer_highlights = this
16977 .document_highlights_for_position(selection.head(), &buffer)
16978 .filter(|highlight| {
16979 highlight.start.excerpt_id == selection.head().excerpt_id
16980 && highlight.end.excerpt_id == selection.head().excerpt_id
16981 });
16982 buffer_highlights
16983 .next()
16984 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16985 })?
16986 };
16987 if let Some(rename_range) = rename_range {
16988 this.update_in(cx, |this, window, cx| {
16989 let snapshot = cursor_buffer.read(cx).snapshot();
16990 let rename_buffer_range = rename_range.to_offset(&snapshot);
16991 let cursor_offset_in_rename_range =
16992 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16993 let cursor_offset_in_rename_range_end =
16994 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16995
16996 this.take_rename(false, window, cx);
16997 let buffer = this.buffer.read(cx).read(cx);
16998 let cursor_offset = selection.head().to_offset(&buffer);
16999 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17000 let rename_end = rename_start + rename_buffer_range.len();
17001 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17002 let mut old_highlight_id = None;
17003 let old_name: Arc<str> = buffer
17004 .chunks(rename_start..rename_end, true)
17005 .map(|chunk| {
17006 if old_highlight_id.is_none() {
17007 old_highlight_id = chunk.syntax_highlight_id;
17008 }
17009 chunk.text
17010 })
17011 .collect::<String>()
17012 .into();
17013
17014 drop(buffer);
17015
17016 // Position the selection in the rename editor so that it matches the current selection.
17017 this.show_local_selections = false;
17018 let rename_editor = cx.new(|cx| {
17019 let mut editor = Editor::single_line(window, cx);
17020 editor.buffer.update(cx, |buffer, cx| {
17021 buffer.edit([(0..0, old_name.clone())], None, cx)
17022 });
17023 let rename_selection_range = match cursor_offset_in_rename_range
17024 .cmp(&cursor_offset_in_rename_range_end)
17025 {
17026 Ordering::Equal => {
17027 editor.select_all(&SelectAll, window, cx);
17028 return editor;
17029 }
17030 Ordering::Less => {
17031 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17032 }
17033 Ordering::Greater => {
17034 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17035 }
17036 };
17037 if rename_selection_range.end > old_name.len() {
17038 editor.select_all(&SelectAll, window, cx);
17039 } else {
17040 editor.change_selections(Default::default(), window, cx, |s| {
17041 s.select_ranges([rename_selection_range]);
17042 });
17043 }
17044 editor
17045 });
17046 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17047 if e == &EditorEvent::Focused {
17048 cx.emit(EditorEvent::FocusedIn)
17049 }
17050 })
17051 .detach();
17052
17053 let write_highlights =
17054 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17055 let read_highlights =
17056 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17057 let ranges = write_highlights
17058 .iter()
17059 .flat_map(|(_, ranges)| ranges.iter())
17060 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17061 .cloned()
17062 .collect();
17063
17064 this.highlight_text::<Rename>(
17065 ranges,
17066 HighlightStyle {
17067 fade_out: Some(0.6),
17068 ..Default::default()
17069 },
17070 cx,
17071 );
17072 let rename_focus_handle = rename_editor.focus_handle(cx);
17073 window.focus(&rename_focus_handle);
17074 let block_id = this.insert_blocks(
17075 [BlockProperties {
17076 style: BlockStyle::Flex,
17077 placement: BlockPlacement::Below(range.start),
17078 height: Some(1),
17079 render: Arc::new({
17080 let rename_editor = rename_editor.clone();
17081 move |cx: &mut BlockContext| {
17082 let mut text_style = cx.editor_style.text.clone();
17083 if let Some(highlight_style) = old_highlight_id
17084 .and_then(|h| h.style(&cx.editor_style.syntax))
17085 {
17086 text_style = text_style.highlight(highlight_style);
17087 }
17088 div()
17089 .block_mouse_except_scroll()
17090 .pl(cx.anchor_x)
17091 .child(EditorElement::new(
17092 &rename_editor,
17093 EditorStyle {
17094 background: cx.theme().system().transparent,
17095 local_player: cx.editor_style.local_player,
17096 text: text_style,
17097 scrollbar_width: cx.editor_style.scrollbar_width,
17098 syntax: cx.editor_style.syntax.clone(),
17099 status: cx.editor_style.status.clone(),
17100 inlay_hints_style: HighlightStyle {
17101 font_weight: Some(FontWeight::BOLD),
17102 ..make_inlay_hints_style(cx.app)
17103 },
17104 edit_prediction_styles: make_suggestion_styles(
17105 cx.app,
17106 ),
17107 ..EditorStyle::default()
17108 },
17109 ))
17110 .into_any_element()
17111 }
17112 }),
17113 priority: 0,
17114 }],
17115 Some(Autoscroll::fit()),
17116 cx,
17117 )[0];
17118 this.pending_rename = Some(RenameState {
17119 range,
17120 old_name,
17121 editor: rename_editor,
17122 block_id,
17123 });
17124 })?;
17125 }
17126
17127 Ok(())
17128 }))
17129 }
17130
17131 pub fn confirm_rename(
17132 &mut self,
17133 _: &ConfirmRename,
17134 window: &mut Window,
17135 cx: &mut Context<Self>,
17136 ) -> Option<Task<Result<()>>> {
17137 let rename = self.take_rename(false, window, cx)?;
17138 let workspace = self.workspace()?.downgrade();
17139 let (buffer, start) = self
17140 .buffer
17141 .read(cx)
17142 .text_anchor_for_position(rename.range.start, cx)?;
17143 let (end_buffer, _) = self
17144 .buffer
17145 .read(cx)
17146 .text_anchor_for_position(rename.range.end, cx)?;
17147 if buffer != end_buffer {
17148 return None;
17149 }
17150
17151 let old_name = rename.old_name;
17152 let new_name = rename.editor.read(cx).text(cx);
17153
17154 let rename = self.semantics_provider.as_ref()?.perform_rename(
17155 &buffer,
17156 start,
17157 new_name.clone(),
17158 cx,
17159 )?;
17160
17161 Some(cx.spawn_in(window, async move |editor, cx| {
17162 let project_transaction = rename.await?;
17163 Self::open_project_transaction(
17164 &editor,
17165 workspace,
17166 project_transaction,
17167 format!("Rename: {} → {}", old_name, new_name),
17168 cx,
17169 )
17170 .await?;
17171
17172 editor.update(cx, |editor, cx| {
17173 editor.refresh_document_highlights(cx);
17174 })?;
17175 Ok(())
17176 }))
17177 }
17178
17179 fn take_rename(
17180 &mut self,
17181 moving_cursor: bool,
17182 window: &mut Window,
17183 cx: &mut Context<Self>,
17184 ) -> Option<RenameState> {
17185 let rename = self.pending_rename.take()?;
17186 if rename.editor.focus_handle(cx).is_focused(window) {
17187 window.focus(&self.focus_handle);
17188 }
17189
17190 self.remove_blocks(
17191 [rename.block_id].into_iter().collect(),
17192 Some(Autoscroll::fit()),
17193 cx,
17194 );
17195 self.clear_highlights::<Rename>(cx);
17196 self.show_local_selections = true;
17197
17198 if moving_cursor {
17199 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17200 editor.selections.newest::<usize>(cx).head()
17201 });
17202
17203 // Update the selection to match the position of the selection inside
17204 // the rename editor.
17205 let snapshot = self.buffer.read(cx).read(cx);
17206 let rename_range = rename.range.to_offset(&snapshot);
17207 let cursor_in_editor = snapshot
17208 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17209 .min(rename_range.end);
17210 drop(snapshot);
17211
17212 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17213 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17214 });
17215 } else {
17216 self.refresh_document_highlights(cx);
17217 }
17218
17219 Some(rename)
17220 }
17221
17222 pub fn pending_rename(&self) -> Option<&RenameState> {
17223 self.pending_rename.as_ref()
17224 }
17225
17226 fn format(
17227 &mut self,
17228 _: &Format,
17229 window: &mut Window,
17230 cx: &mut Context<Self>,
17231 ) -> Option<Task<Result<()>>> {
17232 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17233
17234 let project = match &self.project {
17235 Some(project) => project.clone(),
17236 None => return None,
17237 };
17238
17239 Some(self.perform_format(
17240 project,
17241 FormatTrigger::Manual,
17242 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17243 window,
17244 cx,
17245 ))
17246 }
17247
17248 fn format_selections(
17249 &mut self,
17250 _: &FormatSelections,
17251 window: &mut Window,
17252 cx: &mut Context<Self>,
17253 ) -> Option<Task<Result<()>>> {
17254 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17255
17256 let project = match &self.project {
17257 Some(project) => project.clone(),
17258 None => return None,
17259 };
17260
17261 let ranges = self
17262 .selections
17263 .all_adjusted(cx)
17264 .into_iter()
17265 .map(|selection| selection.range())
17266 .collect_vec();
17267
17268 Some(self.perform_format(
17269 project,
17270 FormatTrigger::Manual,
17271 FormatTarget::Ranges(ranges),
17272 window,
17273 cx,
17274 ))
17275 }
17276
17277 fn perform_format(
17278 &mut self,
17279 project: Entity<Project>,
17280 trigger: FormatTrigger,
17281 target: FormatTarget,
17282 window: &mut Window,
17283 cx: &mut Context<Self>,
17284 ) -> Task<Result<()>> {
17285 let buffer = self.buffer.clone();
17286 let (buffers, target) = match target {
17287 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17288 FormatTarget::Ranges(selection_ranges) => {
17289 let multi_buffer = buffer.read(cx);
17290 let snapshot = multi_buffer.read(cx);
17291 let mut buffers = HashSet::default();
17292 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17293 BTreeMap::new();
17294 for selection_range in selection_ranges {
17295 for (buffer, buffer_range, _) in
17296 snapshot.range_to_buffer_ranges(selection_range)
17297 {
17298 let buffer_id = buffer.remote_id();
17299 let start = buffer.anchor_before(buffer_range.start);
17300 let end = buffer.anchor_after(buffer_range.end);
17301 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17302 buffer_id_to_ranges
17303 .entry(buffer_id)
17304 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17305 .or_insert_with(|| vec![start..end]);
17306 }
17307 }
17308 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17309 }
17310 };
17311
17312 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17313 let selections_prev = transaction_id_prev
17314 .and_then(|transaction_id_prev| {
17315 // default to selections as they were after the last edit, if we have them,
17316 // instead of how they are now.
17317 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17318 // will take you back to where you made the last edit, instead of staying where you scrolled
17319 self.selection_history
17320 .transaction(transaction_id_prev)
17321 .map(|t| t.0.clone())
17322 })
17323 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17324
17325 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17326 let format = project.update(cx, |project, cx| {
17327 project.format(buffers, target, true, trigger, cx)
17328 });
17329
17330 cx.spawn_in(window, async move |editor, cx| {
17331 let transaction = futures::select_biased! {
17332 transaction = format.log_err().fuse() => transaction,
17333 () = timeout => {
17334 log::warn!("timed out waiting for formatting");
17335 None
17336 }
17337 };
17338
17339 buffer
17340 .update(cx, |buffer, cx| {
17341 if let Some(transaction) = transaction
17342 && !buffer.is_singleton()
17343 {
17344 buffer.push_transaction(&transaction.0, cx);
17345 }
17346 cx.notify();
17347 })
17348 .ok();
17349
17350 if let Some(transaction_id_now) =
17351 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17352 {
17353 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17354 if has_new_transaction {
17355 _ = editor.update(cx, |editor, _| {
17356 editor
17357 .selection_history
17358 .insert_transaction(transaction_id_now, selections_prev);
17359 });
17360 }
17361 }
17362
17363 Ok(())
17364 })
17365 }
17366
17367 fn organize_imports(
17368 &mut self,
17369 _: &OrganizeImports,
17370 window: &mut Window,
17371 cx: &mut Context<Self>,
17372 ) -> Option<Task<Result<()>>> {
17373 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17374 let project = match &self.project {
17375 Some(project) => project.clone(),
17376 None => return None,
17377 };
17378 Some(self.perform_code_action_kind(
17379 project,
17380 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17381 window,
17382 cx,
17383 ))
17384 }
17385
17386 fn perform_code_action_kind(
17387 &mut self,
17388 project: Entity<Project>,
17389 kind: CodeActionKind,
17390 window: &mut Window,
17391 cx: &mut Context<Self>,
17392 ) -> Task<Result<()>> {
17393 let buffer = self.buffer.clone();
17394 let buffers = buffer.read(cx).all_buffers();
17395 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17396 let apply_action = project.update(cx, |project, cx| {
17397 project.apply_code_action_kind(buffers, kind, true, cx)
17398 });
17399 cx.spawn_in(window, async move |_, cx| {
17400 let transaction = futures::select_biased! {
17401 () = timeout => {
17402 log::warn!("timed out waiting for executing code action");
17403 None
17404 }
17405 transaction = apply_action.log_err().fuse() => transaction,
17406 };
17407 buffer
17408 .update(cx, |buffer, cx| {
17409 // check if we need this
17410 if let Some(transaction) = transaction
17411 && !buffer.is_singleton()
17412 {
17413 buffer.push_transaction(&transaction.0, cx);
17414 }
17415 cx.notify();
17416 })
17417 .ok();
17418 Ok(())
17419 })
17420 }
17421
17422 pub fn restart_language_server(
17423 &mut self,
17424 _: &RestartLanguageServer,
17425 _: &mut Window,
17426 cx: &mut Context<Self>,
17427 ) {
17428 if let Some(project) = self.project.clone() {
17429 self.buffer.update(cx, |multi_buffer, cx| {
17430 project.update(cx, |project, cx| {
17431 project.restart_language_servers_for_buffers(
17432 multi_buffer.all_buffers().into_iter().collect(),
17433 HashSet::default(),
17434 cx,
17435 );
17436 });
17437 })
17438 }
17439 }
17440
17441 pub fn stop_language_server(
17442 &mut self,
17443 _: &StopLanguageServer,
17444 _: &mut Window,
17445 cx: &mut Context<Self>,
17446 ) {
17447 if let Some(project) = self.project.clone() {
17448 self.buffer.update(cx, |multi_buffer, cx| {
17449 project.update(cx, |project, cx| {
17450 project.stop_language_servers_for_buffers(
17451 multi_buffer.all_buffers().into_iter().collect(),
17452 HashSet::default(),
17453 cx,
17454 );
17455 cx.emit(project::Event::RefreshInlayHints);
17456 });
17457 });
17458 }
17459 }
17460
17461 fn cancel_language_server_work(
17462 workspace: &mut Workspace,
17463 _: &actions::CancelLanguageServerWork,
17464 _: &mut Window,
17465 cx: &mut Context<Workspace>,
17466 ) {
17467 let project = workspace.project();
17468 let buffers = workspace
17469 .active_item(cx)
17470 .and_then(|item| item.act_as::<Editor>(cx))
17471 .map_or(HashSet::default(), |editor| {
17472 editor.read(cx).buffer.read(cx).all_buffers()
17473 });
17474 project.update(cx, |project, cx| {
17475 project.cancel_language_server_work_for_buffers(buffers, cx);
17476 });
17477 }
17478
17479 fn show_character_palette(
17480 &mut self,
17481 _: &ShowCharacterPalette,
17482 window: &mut Window,
17483 _: &mut Context<Self>,
17484 ) {
17485 window.show_character_palette();
17486 }
17487
17488 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17489 if !self.diagnostics_enabled() {
17490 return;
17491 }
17492
17493 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17494 let buffer = self.buffer.read(cx).snapshot(cx);
17495 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17496 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17497 let is_valid = buffer
17498 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17499 .any(|entry| {
17500 entry.diagnostic.is_primary
17501 && !entry.range.is_empty()
17502 && entry.range.start == primary_range_start
17503 && entry.diagnostic.message == active_diagnostics.active_message
17504 });
17505
17506 if !is_valid {
17507 self.dismiss_diagnostics(cx);
17508 }
17509 }
17510 }
17511
17512 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17513 match &self.active_diagnostics {
17514 ActiveDiagnostic::Group(group) => Some(group),
17515 _ => None,
17516 }
17517 }
17518
17519 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17520 if !self.diagnostics_enabled() {
17521 return;
17522 }
17523 self.dismiss_diagnostics(cx);
17524 self.active_diagnostics = ActiveDiagnostic::All;
17525 }
17526
17527 fn activate_diagnostics(
17528 &mut self,
17529 buffer_id: BufferId,
17530 diagnostic: DiagnosticEntryRef<'_, usize>,
17531 window: &mut Window,
17532 cx: &mut Context<Self>,
17533 ) {
17534 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17535 return;
17536 }
17537 self.dismiss_diagnostics(cx);
17538 let snapshot = self.snapshot(window, cx);
17539 let buffer = self.buffer.read(cx).snapshot(cx);
17540 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17541 return;
17542 };
17543
17544 let diagnostic_group = buffer
17545 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17546 .collect::<Vec<_>>();
17547
17548 let blocks =
17549 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17550
17551 let blocks = self.display_map.update(cx, |display_map, cx| {
17552 display_map.insert_blocks(blocks, cx).into_iter().collect()
17553 });
17554 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17555 active_range: buffer.anchor_before(diagnostic.range.start)
17556 ..buffer.anchor_after(diagnostic.range.end),
17557 active_message: diagnostic.diagnostic.message.clone(),
17558 group_id: diagnostic.diagnostic.group_id,
17559 blocks,
17560 });
17561 cx.notify();
17562 }
17563
17564 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17565 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17566 return;
17567 };
17568
17569 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17570 if let ActiveDiagnostic::Group(group) = prev {
17571 self.display_map.update(cx, |display_map, cx| {
17572 display_map.remove_blocks(group.blocks, cx);
17573 });
17574 cx.notify();
17575 }
17576 }
17577
17578 /// Disable inline diagnostics rendering for this editor.
17579 pub fn disable_inline_diagnostics(&mut self) {
17580 self.inline_diagnostics_enabled = false;
17581 self.inline_diagnostics_update = Task::ready(());
17582 self.inline_diagnostics.clear();
17583 }
17584
17585 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17586 self.diagnostics_enabled = false;
17587 self.dismiss_diagnostics(cx);
17588 self.inline_diagnostics_update = Task::ready(());
17589 self.inline_diagnostics.clear();
17590 }
17591
17592 pub fn disable_word_completions(&mut self) {
17593 self.word_completions_enabled = false;
17594 }
17595
17596 pub fn diagnostics_enabled(&self) -> bool {
17597 self.diagnostics_enabled && self.mode.is_full()
17598 }
17599
17600 pub fn inline_diagnostics_enabled(&self) -> bool {
17601 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17602 }
17603
17604 pub fn show_inline_diagnostics(&self) -> bool {
17605 self.show_inline_diagnostics
17606 }
17607
17608 pub fn toggle_inline_diagnostics(
17609 &mut self,
17610 _: &ToggleInlineDiagnostics,
17611 window: &mut Window,
17612 cx: &mut Context<Editor>,
17613 ) {
17614 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17615 self.refresh_inline_diagnostics(false, window, cx);
17616 }
17617
17618 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17619 self.diagnostics_max_severity = severity;
17620 self.display_map.update(cx, |display_map, _| {
17621 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17622 });
17623 }
17624
17625 pub fn toggle_diagnostics(
17626 &mut self,
17627 _: &ToggleDiagnostics,
17628 window: &mut Window,
17629 cx: &mut Context<Editor>,
17630 ) {
17631 if !self.diagnostics_enabled() {
17632 return;
17633 }
17634
17635 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17636 EditorSettings::get_global(cx)
17637 .diagnostics_max_severity
17638 .filter(|severity| severity != &DiagnosticSeverity::Off)
17639 .unwrap_or(DiagnosticSeverity::Hint)
17640 } else {
17641 DiagnosticSeverity::Off
17642 };
17643 self.set_max_diagnostics_severity(new_severity, cx);
17644 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17645 self.active_diagnostics = ActiveDiagnostic::None;
17646 self.inline_diagnostics_update = Task::ready(());
17647 self.inline_diagnostics.clear();
17648 } else {
17649 self.refresh_inline_diagnostics(false, window, cx);
17650 }
17651
17652 cx.notify();
17653 }
17654
17655 pub fn toggle_minimap(
17656 &mut self,
17657 _: &ToggleMinimap,
17658 window: &mut Window,
17659 cx: &mut Context<Editor>,
17660 ) {
17661 if self.supports_minimap(cx) {
17662 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17663 }
17664 }
17665
17666 fn refresh_inline_diagnostics(
17667 &mut self,
17668 debounce: bool,
17669 window: &mut Window,
17670 cx: &mut Context<Self>,
17671 ) {
17672 let max_severity = ProjectSettings::get_global(cx)
17673 .diagnostics
17674 .inline
17675 .max_severity
17676 .unwrap_or(self.diagnostics_max_severity);
17677
17678 if !self.inline_diagnostics_enabled()
17679 || !self.show_inline_diagnostics
17680 || max_severity == DiagnosticSeverity::Off
17681 {
17682 self.inline_diagnostics_update = Task::ready(());
17683 self.inline_diagnostics.clear();
17684 return;
17685 }
17686
17687 let debounce_ms = ProjectSettings::get_global(cx)
17688 .diagnostics
17689 .inline
17690 .update_debounce_ms;
17691 let debounce = if debounce && debounce_ms > 0 {
17692 Some(Duration::from_millis(debounce_ms))
17693 } else {
17694 None
17695 };
17696 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17697 if let Some(debounce) = debounce {
17698 cx.background_executor().timer(debounce).await;
17699 }
17700 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17701 editor
17702 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17703 .ok()
17704 }) else {
17705 return;
17706 };
17707
17708 let new_inline_diagnostics = cx
17709 .background_spawn(async move {
17710 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17711 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17712 let message = diagnostic_entry
17713 .diagnostic
17714 .message
17715 .split_once('\n')
17716 .map(|(line, _)| line)
17717 .map(SharedString::new)
17718 .unwrap_or_else(|| {
17719 SharedString::new(&*diagnostic_entry.diagnostic.message)
17720 });
17721 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17722 let (Ok(i) | Err(i)) = inline_diagnostics
17723 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17724 inline_diagnostics.insert(
17725 i,
17726 (
17727 start_anchor,
17728 InlineDiagnostic {
17729 message,
17730 group_id: diagnostic_entry.diagnostic.group_id,
17731 start: diagnostic_entry.range.start.to_point(&snapshot),
17732 is_primary: diagnostic_entry.diagnostic.is_primary,
17733 severity: diagnostic_entry.diagnostic.severity,
17734 },
17735 ),
17736 );
17737 }
17738 inline_diagnostics
17739 })
17740 .await;
17741
17742 editor
17743 .update(cx, |editor, cx| {
17744 editor.inline_diagnostics = new_inline_diagnostics;
17745 cx.notify();
17746 })
17747 .ok();
17748 });
17749 }
17750
17751 fn pull_diagnostics(
17752 &mut self,
17753 buffer_id: Option<BufferId>,
17754 window: &Window,
17755 cx: &mut Context<Self>,
17756 ) -> Option<()> {
17757 if !self.mode().is_full() {
17758 return None;
17759 }
17760 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17761 .diagnostics
17762 .lsp_pull_diagnostics;
17763 if !pull_diagnostics_settings.enabled {
17764 return None;
17765 }
17766 let project = self.project()?.downgrade();
17767 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17768 let mut buffers = self.buffer.read(cx).all_buffers();
17769 if let Some(buffer_id) = buffer_id {
17770 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17771 }
17772
17773 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17774 cx.background_executor().timer(debounce).await;
17775
17776 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17777 buffers
17778 .into_iter()
17779 .filter_map(|buffer| {
17780 project
17781 .update(cx, |project, cx| {
17782 project.lsp_store().update(cx, |lsp_store, cx| {
17783 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17784 })
17785 })
17786 .ok()
17787 })
17788 .collect::<FuturesUnordered<_>>()
17789 }) else {
17790 return;
17791 };
17792
17793 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17794 match pull_task {
17795 Ok(()) => {
17796 if editor
17797 .update_in(cx, |editor, window, cx| {
17798 editor.update_diagnostics_state(window, cx);
17799 })
17800 .is_err()
17801 {
17802 return;
17803 }
17804 }
17805 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17806 }
17807 }
17808 });
17809
17810 Some(())
17811 }
17812
17813 pub fn set_selections_from_remote(
17814 &mut self,
17815 selections: Vec<Selection<Anchor>>,
17816 pending_selection: Option<Selection<Anchor>>,
17817 window: &mut Window,
17818 cx: &mut Context<Self>,
17819 ) {
17820 let old_cursor_position = self.selections.newest_anchor().head();
17821 self.selections.change_with(cx, |s| {
17822 s.select_anchors(selections);
17823 if let Some(pending_selection) = pending_selection {
17824 s.set_pending(pending_selection, SelectMode::Character);
17825 } else {
17826 s.clear_pending();
17827 }
17828 });
17829 self.selections_did_change(
17830 false,
17831 &old_cursor_position,
17832 SelectionEffects::default(),
17833 window,
17834 cx,
17835 );
17836 }
17837
17838 pub fn transact(
17839 &mut self,
17840 window: &mut Window,
17841 cx: &mut Context<Self>,
17842 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17843 ) -> Option<TransactionId> {
17844 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17845 this.start_transaction_at(Instant::now(), window, cx);
17846 update(this, window, cx);
17847 this.end_transaction_at(Instant::now(), cx)
17848 })
17849 }
17850
17851 pub fn start_transaction_at(
17852 &mut self,
17853 now: Instant,
17854 window: &mut Window,
17855 cx: &mut Context<Self>,
17856 ) -> Option<TransactionId> {
17857 self.end_selection(window, cx);
17858 if let Some(tx_id) = self
17859 .buffer
17860 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17861 {
17862 self.selection_history
17863 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17864 cx.emit(EditorEvent::TransactionBegun {
17865 transaction_id: tx_id,
17866 });
17867 Some(tx_id)
17868 } else {
17869 None
17870 }
17871 }
17872
17873 pub fn end_transaction_at(
17874 &mut self,
17875 now: Instant,
17876 cx: &mut Context<Self>,
17877 ) -> Option<TransactionId> {
17878 if let Some(transaction_id) = self
17879 .buffer
17880 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17881 {
17882 if let Some((_, end_selections)) =
17883 self.selection_history.transaction_mut(transaction_id)
17884 {
17885 *end_selections = Some(self.selections.disjoint_anchors_arc());
17886 } else {
17887 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17888 }
17889
17890 cx.emit(EditorEvent::Edited { transaction_id });
17891 Some(transaction_id)
17892 } else {
17893 None
17894 }
17895 }
17896
17897 pub fn modify_transaction_selection_history(
17898 &mut self,
17899 transaction_id: TransactionId,
17900 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17901 ) -> bool {
17902 self.selection_history
17903 .transaction_mut(transaction_id)
17904 .map(modify)
17905 .is_some()
17906 }
17907
17908 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17909 if self.selection_mark_mode {
17910 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17911 s.move_with(|_, sel| {
17912 sel.collapse_to(sel.head(), SelectionGoal::None);
17913 });
17914 })
17915 }
17916 self.selection_mark_mode = true;
17917 cx.notify();
17918 }
17919
17920 pub fn swap_selection_ends(
17921 &mut self,
17922 _: &actions::SwapSelectionEnds,
17923 window: &mut Window,
17924 cx: &mut Context<Self>,
17925 ) {
17926 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17927 s.move_with(|_, sel| {
17928 if sel.start != sel.end {
17929 sel.reversed = !sel.reversed
17930 }
17931 });
17932 });
17933 self.request_autoscroll(Autoscroll::newest(), cx);
17934 cx.notify();
17935 }
17936
17937 pub fn toggle_focus(
17938 workspace: &mut Workspace,
17939 _: &actions::ToggleFocus,
17940 window: &mut Window,
17941 cx: &mut Context<Workspace>,
17942 ) {
17943 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17944 return;
17945 };
17946 workspace.activate_item(&item, true, true, window, cx);
17947 }
17948
17949 pub fn toggle_fold(
17950 &mut self,
17951 _: &actions::ToggleFold,
17952 window: &mut Window,
17953 cx: &mut Context<Self>,
17954 ) {
17955 if self.is_singleton(cx) {
17956 let selection = self.selections.newest::<Point>(cx);
17957
17958 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17959 let range = if selection.is_empty() {
17960 let point = selection.head().to_display_point(&display_map);
17961 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17962 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17963 .to_point(&display_map);
17964 start..end
17965 } else {
17966 selection.range()
17967 };
17968 if display_map.folds_in_range(range).next().is_some() {
17969 self.unfold_lines(&Default::default(), window, cx)
17970 } else {
17971 self.fold(&Default::default(), window, cx)
17972 }
17973 } else {
17974 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17975 let buffer_ids: HashSet<_> = self
17976 .selections
17977 .disjoint_anchor_ranges()
17978 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17979 .collect();
17980
17981 let should_unfold = buffer_ids
17982 .iter()
17983 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17984
17985 for buffer_id in buffer_ids {
17986 if should_unfold {
17987 self.unfold_buffer(buffer_id, cx);
17988 } else {
17989 self.fold_buffer(buffer_id, cx);
17990 }
17991 }
17992 }
17993 }
17994
17995 pub fn toggle_fold_recursive(
17996 &mut self,
17997 _: &actions::ToggleFoldRecursive,
17998 window: &mut Window,
17999 cx: &mut Context<Self>,
18000 ) {
18001 let selection = self.selections.newest::<Point>(cx);
18002
18003 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18004 let range = if selection.is_empty() {
18005 let point = selection.head().to_display_point(&display_map);
18006 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18007 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18008 .to_point(&display_map);
18009 start..end
18010 } else {
18011 selection.range()
18012 };
18013 if display_map.folds_in_range(range).next().is_some() {
18014 self.unfold_recursive(&Default::default(), window, cx)
18015 } else {
18016 self.fold_recursive(&Default::default(), window, cx)
18017 }
18018 }
18019
18020 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18021 if self.is_singleton(cx) {
18022 let mut to_fold = Vec::new();
18023 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18024 let selections = self.selections.all_adjusted(cx);
18025
18026 for selection in selections {
18027 let range = selection.range().sorted();
18028 let buffer_start_row = range.start.row;
18029
18030 if range.start.row != range.end.row {
18031 let mut found = false;
18032 let mut row = range.start.row;
18033 while row <= range.end.row {
18034 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18035 {
18036 found = true;
18037 row = crease.range().end.row + 1;
18038 to_fold.push(crease);
18039 } else {
18040 row += 1
18041 }
18042 }
18043 if found {
18044 continue;
18045 }
18046 }
18047
18048 for row in (0..=range.start.row).rev() {
18049 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18050 && crease.range().end.row >= buffer_start_row
18051 {
18052 to_fold.push(crease);
18053 if row <= range.start.row {
18054 break;
18055 }
18056 }
18057 }
18058 }
18059
18060 self.fold_creases(to_fold, true, window, cx);
18061 } else {
18062 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18063 let buffer_ids = self
18064 .selections
18065 .disjoint_anchor_ranges()
18066 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18067 .collect::<HashSet<_>>();
18068 for buffer_id in buffer_ids {
18069 self.fold_buffer(buffer_id, cx);
18070 }
18071 }
18072 }
18073
18074 pub fn toggle_fold_all(
18075 &mut self,
18076 _: &actions::ToggleFoldAll,
18077 window: &mut Window,
18078 cx: &mut Context<Self>,
18079 ) {
18080 if self.buffer.read(cx).is_singleton() {
18081 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18082 let has_folds = display_map
18083 .folds_in_range(0..display_map.buffer_snapshot.len())
18084 .next()
18085 .is_some();
18086
18087 if has_folds {
18088 self.unfold_all(&actions::UnfoldAll, window, cx);
18089 } else {
18090 self.fold_all(&actions::FoldAll, window, cx);
18091 }
18092 } else {
18093 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18094 let should_unfold = buffer_ids
18095 .iter()
18096 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18097
18098 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18099 editor
18100 .update_in(cx, |editor, _, cx| {
18101 for buffer_id in buffer_ids {
18102 if should_unfold {
18103 editor.unfold_buffer(buffer_id, cx);
18104 } else {
18105 editor.fold_buffer(buffer_id, cx);
18106 }
18107 }
18108 })
18109 .ok();
18110 });
18111 }
18112 }
18113
18114 fn fold_at_level(
18115 &mut self,
18116 fold_at: &FoldAtLevel,
18117 window: &mut Window,
18118 cx: &mut Context<Self>,
18119 ) {
18120 if !self.buffer.read(cx).is_singleton() {
18121 return;
18122 }
18123
18124 let fold_at_level = fold_at.0;
18125 let snapshot = self.buffer.read(cx).snapshot(cx);
18126 let mut to_fold = Vec::new();
18127 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18128
18129 let row_ranges_to_keep: Vec<Range<u32>> = self
18130 .selections
18131 .all::<Point>(cx)
18132 .into_iter()
18133 .map(|sel| sel.start.row..sel.end.row)
18134 .collect();
18135
18136 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18137 while start_row < end_row {
18138 match self
18139 .snapshot(window, cx)
18140 .crease_for_buffer_row(MultiBufferRow(start_row))
18141 {
18142 Some(crease) => {
18143 let nested_start_row = crease.range().start.row + 1;
18144 let nested_end_row = crease.range().end.row;
18145
18146 if current_level < fold_at_level {
18147 stack.push((nested_start_row, nested_end_row, current_level + 1));
18148 } else if current_level == fold_at_level {
18149 // Fold iff there is no selection completely contained within the fold region
18150 if !row_ranges_to_keep.iter().any(|selection| {
18151 selection.end >= nested_start_row
18152 && selection.start <= nested_end_row
18153 }) {
18154 to_fold.push(crease);
18155 }
18156 }
18157
18158 start_row = nested_end_row + 1;
18159 }
18160 None => start_row += 1,
18161 }
18162 }
18163 }
18164
18165 self.fold_creases(to_fold, true, window, cx);
18166 }
18167
18168 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18169 if self.buffer.read(cx).is_singleton() {
18170 let mut fold_ranges = Vec::new();
18171 let snapshot = self.buffer.read(cx).snapshot(cx);
18172
18173 for row in 0..snapshot.max_row().0 {
18174 if let Some(foldable_range) = self
18175 .snapshot(window, cx)
18176 .crease_for_buffer_row(MultiBufferRow(row))
18177 {
18178 fold_ranges.push(foldable_range);
18179 }
18180 }
18181
18182 self.fold_creases(fold_ranges, true, window, cx);
18183 } else {
18184 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18185 editor
18186 .update_in(cx, |editor, _, cx| {
18187 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18188 editor.fold_buffer(buffer_id, cx);
18189 }
18190 })
18191 .ok();
18192 });
18193 }
18194 }
18195
18196 pub fn fold_function_bodies(
18197 &mut self,
18198 _: &actions::FoldFunctionBodies,
18199 window: &mut Window,
18200 cx: &mut Context<Self>,
18201 ) {
18202 let snapshot = self.buffer.read(cx).snapshot(cx);
18203
18204 let ranges = snapshot
18205 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18206 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18207 .collect::<Vec<_>>();
18208
18209 let creases = ranges
18210 .into_iter()
18211 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18212 .collect();
18213
18214 self.fold_creases(creases, true, window, cx);
18215 }
18216
18217 pub fn fold_recursive(
18218 &mut self,
18219 _: &actions::FoldRecursive,
18220 window: &mut Window,
18221 cx: &mut Context<Self>,
18222 ) {
18223 let mut to_fold = Vec::new();
18224 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18225 let selections = self.selections.all_adjusted(cx);
18226
18227 for selection in selections {
18228 let range = selection.range().sorted();
18229 let buffer_start_row = range.start.row;
18230
18231 if range.start.row != range.end.row {
18232 let mut found = false;
18233 for row in range.start.row..=range.end.row {
18234 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18235 found = true;
18236 to_fold.push(crease);
18237 }
18238 }
18239 if found {
18240 continue;
18241 }
18242 }
18243
18244 for row in (0..=range.start.row).rev() {
18245 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18246 if crease.range().end.row >= buffer_start_row {
18247 to_fold.push(crease);
18248 } else {
18249 break;
18250 }
18251 }
18252 }
18253 }
18254
18255 self.fold_creases(to_fold, true, window, cx);
18256 }
18257
18258 pub fn fold_at(
18259 &mut self,
18260 buffer_row: MultiBufferRow,
18261 window: &mut Window,
18262 cx: &mut Context<Self>,
18263 ) {
18264 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18265
18266 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18267 let autoscroll = self
18268 .selections
18269 .all::<Point>(cx)
18270 .iter()
18271 .any(|selection| crease.range().overlaps(&selection.range()));
18272
18273 self.fold_creases(vec![crease], autoscroll, window, cx);
18274 }
18275 }
18276
18277 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18278 if self.is_singleton(cx) {
18279 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18280 let buffer = &display_map.buffer_snapshot;
18281 let selections = self.selections.all::<Point>(cx);
18282 let ranges = selections
18283 .iter()
18284 .map(|s| {
18285 let range = s.display_range(&display_map).sorted();
18286 let mut start = range.start.to_point(&display_map);
18287 let mut end = range.end.to_point(&display_map);
18288 start.column = 0;
18289 end.column = buffer.line_len(MultiBufferRow(end.row));
18290 start..end
18291 })
18292 .collect::<Vec<_>>();
18293
18294 self.unfold_ranges(&ranges, true, true, cx);
18295 } else {
18296 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18297 let buffer_ids = self
18298 .selections
18299 .disjoint_anchor_ranges()
18300 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18301 .collect::<HashSet<_>>();
18302 for buffer_id in buffer_ids {
18303 self.unfold_buffer(buffer_id, cx);
18304 }
18305 }
18306 }
18307
18308 pub fn unfold_recursive(
18309 &mut self,
18310 _: &UnfoldRecursive,
18311 _window: &mut Window,
18312 cx: &mut Context<Self>,
18313 ) {
18314 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18315 let selections = self.selections.all::<Point>(cx);
18316 let ranges = selections
18317 .iter()
18318 .map(|s| {
18319 let mut range = s.display_range(&display_map).sorted();
18320 *range.start.column_mut() = 0;
18321 *range.end.column_mut() = display_map.line_len(range.end.row());
18322 let start = range.start.to_point(&display_map);
18323 let end = range.end.to_point(&display_map);
18324 start..end
18325 })
18326 .collect::<Vec<_>>();
18327
18328 self.unfold_ranges(&ranges, true, true, cx);
18329 }
18330
18331 pub fn unfold_at(
18332 &mut self,
18333 buffer_row: MultiBufferRow,
18334 _window: &mut Window,
18335 cx: &mut Context<Self>,
18336 ) {
18337 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18338
18339 let intersection_range = Point::new(buffer_row.0, 0)
18340 ..Point::new(
18341 buffer_row.0,
18342 display_map.buffer_snapshot.line_len(buffer_row),
18343 );
18344
18345 let autoscroll = self
18346 .selections
18347 .all::<Point>(cx)
18348 .iter()
18349 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18350
18351 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18352 }
18353
18354 pub fn unfold_all(
18355 &mut self,
18356 _: &actions::UnfoldAll,
18357 _window: &mut Window,
18358 cx: &mut Context<Self>,
18359 ) {
18360 if self.buffer.read(cx).is_singleton() {
18361 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18362 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18363 } else {
18364 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18365 editor
18366 .update(cx, |editor, cx| {
18367 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18368 editor.unfold_buffer(buffer_id, cx);
18369 }
18370 })
18371 .ok();
18372 });
18373 }
18374 }
18375
18376 pub fn fold_selected_ranges(
18377 &mut self,
18378 _: &FoldSelectedRanges,
18379 window: &mut Window,
18380 cx: &mut Context<Self>,
18381 ) {
18382 let selections = self.selections.all_adjusted(cx);
18383 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18384 let ranges = selections
18385 .into_iter()
18386 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18387 .collect::<Vec<_>>();
18388 self.fold_creases(ranges, true, window, cx);
18389 }
18390
18391 pub fn fold_ranges<T: ToOffset + Clone>(
18392 &mut self,
18393 ranges: Vec<Range<T>>,
18394 auto_scroll: bool,
18395 window: &mut Window,
18396 cx: &mut Context<Self>,
18397 ) {
18398 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18399 let ranges = ranges
18400 .into_iter()
18401 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18402 .collect::<Vec<_>>();
18403 self.fold_creases(ranges, auto_scroll, window, cx);
18404 }
18405
18406 pub fn fold_creases<T: ToOffset + Clone>(
18407 &mut self,
18408 creases: Vec<Crease<T>>,
18409 auto_scroll: bool,
18410 _window: &mut Window,
18411 cx: &mut Context<Self>,
18412 ) {
18413 if creases.is_empty() {
18414 return;
18415 }
18416
18417 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18418
18419 if auto_scroll {
18420 self.request_autoscroll(Autoscroll::fit(), cx);
18421 }
18422
18423 cx.notify();
18424
18425 self.scrollbar_marker_state.dirty = true;
18426 self.folds_did_change(cx);
18427 }
18428
18429 /// Removes any folds whose ranges intersect any of the given ranges.
18430 pub fn unfold_ranges<T: ToOffset + Clone>(
18431 &mut self,
18432 ranges: &[Range<T>],
18433 inclusive: bool,
18434 auto_scroll: bool,
18435 cx: &mut Context<Self>,
18436 ) {
18437 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18438 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18439 });
18440 self.folds_did_change(cx);
18441 }
18442
18443 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18444 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18445 return;
18446 }
18447 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18448 self.display_map.update(cx, |display_map, cx| {
18449 display_map.fold_buffers([buffer_id], cx)
18450 });
18451 cx.emit(EditorEvent::BufferFoldToggled {
18452 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18453 folded: true,
18454 });
18455 cx.notify();
18456 }
18457
18458 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18459 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18460 return;
18461 }
18462 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18463 self.display_map.update(cx, |display_map, cx| {
18464 display_map.unfold_buffers([buffer_id], cx);
18465 });
18466 cx.emit(EditorEvent::BufferFoldToggled {
18467 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18468 folded: false,
18469 });
18470 cx.notify();
18471 }
18472
18473 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18474 self.display_map.read(cx).is_buffer_folded(buffer)
18475 }
18476
18477 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18478 self.display_map.read(cx).folded_buffers()
18479 }
18480
18481 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18482 self.display_map.update(cx, |display_map, cx| {
18483 display_map.disable_header_for_buffer(buffer_id, cx);
18484 });
18485 cx.notify();
18486 }
18487
18488 /// Removes any folds with the given ranges.
18489 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18490 &mut self,
18491 ranges: &[Range<T>],
18492 type_id: TypeId,
18493 auto_scroll: bool,
18494 cx: &mut Context<Self>,
18495 ) {
18496 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18497 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18498 });
18499 self.folds_did_change(cx);
18500 }
18501
18502 fn remove_folds_with<T: ToOffset + Clone>(
18503 &mut self,
18504 ranges: &[Range<T>],
18505 auto_scroll: bool,
18506 cx: &mut Context<Self>,
18507 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18508 ) {
18509 if ranges.is_empty() {
18510 return;
18511 }
18512
18513 let mut buffers_affected = HashSet::default();
18514 let multi_buffer = self.buffer().read(cx);
18515 for range in ranges {
18516 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18517 buffers_affected.insert(buffer.read(cx).remote_id());
18518 };
18519 }
18520
18521 self.display_map.update(cx, update);
18522
18523 if auto_scroll {
18524 self.request_autoscroll(Autoscroll::fit(), cx);
18525 }
18526
18527 cx.notify();
18528 self.scrollbar_marker_state.dirty = true;
18529 self.active_indent_guides_state.dirty = true;
18530 }
18531
18532 pub fn update_renderer_widths(
18533 &mut self,
18534 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18535 cx: &mut Context<Self>,
18536 ) -> bool {
18537 self.display_map
18538 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18539 }
18540
18541 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18542 self.display_map.read(cx).fold_placeholder.clone()
18543 }
18544
18545 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18546 self.buffer.update(cx, |buffer, cx| {
18547 buffer.set_all_diff_hunks_expanded(cx);
18548 });
18549 }
18550
18551 pub fn expand_all_diff_hunks(
18552 &mut self,
18553 _: &ExpandAllDiffHunks,
18554 _window: &mut Window,
18555 cx: &mut Context<Self>,
18556 ) {
18557 self.buffer.update(cx, |buffer, cx| {
18558 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18559 });
18560 }
18561
18562 pub fn toggle_selected_diff_hunks(
18563 &mut self,
18564 _: &ToggleSelectedDiffHunks,
18565 _window: &mut Window,
18566 cx: &mut Context<Self>,
18567 ) {
18568 let ranges: Vec<_> = self
18569 .selections
18570 .disjoint_anchors()
18571 .iter()
18572 .map(|s| s.range())
18573 .collect();
18574 self.toggle_diff_hunks_in_ranges(ranges, cx);
18575 }
18576
18577 pub fn diff_hunks_in_ranges<'a>(
18578 &'a self,
18579 ranges: &'a [Range<Anchor>],
18580 buffer: &'a MultiBufferSnapshot,
18581 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18582 ranges.iter().flat_map(move |range| {
18583 let end_excerpt_id = range.end.excerpt_id;
18584 let range = range.to_point(buffer);
18585 let mut peek_end = range.end;
18586 if range.end.row < buffer.max_row().0 {
18587 peek_end = Point::new(range.end.row + 1, 0);
18588 }
18589 buffer
18590 .diff_hunks_in_range(range.start..peek_end)
18591 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18592 })
18593 }
18594
18595 pub fn has_stageable_diff_hunks_in_ranges(
18596 &self,
18597 ranges: &[Range<Anchor>],
18598 snapshot: &MultiBufferSnapshot,
18599 ) -> bool {
18600 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18601 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18602 }
18603
18604 pub fn toggle_staged_selected_diff_hunks(
18605 &mut self,
18606 _: &::git::ToggleStaged,
18607 _: &mut Window,
18608 cx: &mut Context<Self>,
18609 ) {
18610 let snapshot = self.buffer.read(cx).snapshot(cx);
18611 let ranges: Vec<_> = self
18612 .selections
18613 .disjoint_anchors()
18614 .iter()
18615 .map(|s| s.range())
18616 .collect();
18617 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18618 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18619 }
18620
18621 pub fn set_render_diff_hunk_controls(
18622 &mut self,
18623 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18624 cx: &mut Context<Self>,
18625 ) {
18626 self.render_diff_hunk_controls = render_diff_hunk_controls;
18627 cx.notify();
18628 }
18629
18630 pub fn stage_and_next(
18631 &mut self,
18632 _: &::git::StageAndNext,
18633 window: &mut Window,
18634 cx: &mut Context<Self>,
18635 ) {
18636 self.do_stage_or_unstage_and_next(true, window, cx);
18637 }
18638
18639 pub fn unstage_and_next(
18640 &mut self,
18641 _: &::git::UnstageAndNext,
18642 window: &mut Window,
18643 cx: &mut Context<Self>,
18644 ) {
18645 self.do_stage_or_unstage_and_next(false, window, cx);
18646 }
18647
18648 pub fn stage_or_unstage_diff_hunks(
18649 &mut self,
18650 stage: bool,
18651 ranges: Vec<Range<Anchor>>,
18652 cx: &mut Context<Self>,
18653 ) {
18654 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18655 cx.spawn(async move |this, cx| {
18656 task.await?;
18657 this.update(cx, |this, cx| {
18658 let snapshot = this.buffer.read(cx).snapshot(cx);
18659 let chunk_by = this
18660 .diff_hunks_in_ranges(&ranges, &snapshot)
18661 .chunk_by(|hunk| hunk.buffer_id);
18662 for (buffer_id, hunks) in &chunk_by {
18663 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18664 }
18665 })
18666 })
18667 .detach_and_log_err(cx);
18668 }
18669
18670 fn save_buffers_for_ranges_if_needed(
18671 &mut self,
18672 ranges: &[Range<Anchor>],
18673 cx: &mut Context<Editor>,
18674 ) -> Task<Result<()>> {
18675 let multibuffer = self.buffer.read(cx);
18676 let snapshot = multibuffer.read(cx);
18677 let buffer_ids: HashSet<_> = ranges
18678 .iter()
18679 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18680 .collect();
18681 drop(snapshot);
18682
18683 let mut buffers = HashSet::default();
18684 for buffer_id in buffer_ids {
18685 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18686 let buffer = buffer_entity.read(cx);
18687 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18688 {
18689 buffers.insert(buffer_entity);
18690 }
18691 }
18692 }
18693
18694 if let Some(project) = &self.project {
18695 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18696 } else {
18697 Task::ready(Ok(()))
18698 }
18699 }
18700
18701 fn do_stage_or_unstage_and_next(
18702 &mut self,
18703 stage: bool,
18704 window: &mut Window,
18705 cx: &mut Context<Self>,
18706 ) {
18707 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18708
18709 if ranges.iter().any(|range| range.start != range.end) {
18710 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18711 return;
18712 }
18713
18714 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18715 let snapshot = self.snapshot(window, cx);
18716 let position = self.selections.newest::<Point>(cx).head();
18717 let mut row = snapshot
18718 .buffer_snapshot
18719 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18720 .find(|hunk| hunk.row_range.start.0 > position.row)
18721 .map(|hunk| hunk.row_range.start);
18722
18723 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18724 // Outside of the project diff editor, wrap around to the beginning.
18725 if !all_diff_hunks_expanded {
18726 row = row.or_else(|| {
18727 snapshot
18728 .buffer_snapshot
18729 .diff_hunks_in_range(Point::zero()..position)
18730 .find(|hunk| hunk.row_range.end.0 < position.row)
18731 .map(|hunk| hunk.row_range.start)
18732 });
18733 }
18734
18735 if let Some(row) = row {
18736 let destination = Point::new(row.0, 0);
18737 let autoscroll = Autoscroll::center();
18738
18739 self.unfold_ranges(&[destination..destination], false, false, cx);
18740 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18741 s.select_ranges([destination..destination]);
18742 });
18743 }
18744 }
18745
18746 fn do_stage_or_unstage(
18747 &self,
18748 stage: bool,
18749 buffer_id: BufferId,
18750 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18751 cx: &mut App,
18752 ) -> Option<()> {
18753 let project = self.project()?;
18754 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18755 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18756 let buffer_snapshot = buffer.read(cx).snapshot();
18757 let file_exists = buffer_snapshot
18758 .file()
18759 .is_some_and(|file| file.disk_state().exists());
18760 diff.update(cx, |diff, cx| {
18761 diff.stage_or_unstage_hunks(
18762 stage,
18763 &hunks
18764 .map(|hunk| buffer_diff::DiffHunk {
18765 buffer_range: hunk.buffer_range,
18766 diff_base_byte_range: hunk.diff_base_byte_range,
18767 secondary_status: hunk.secondary_status,
18768 range: Point::zero()..Point::zero(), // unused
18769 })
18770 .collect::<Vec<_>>(),
18771 &buffer_snapshot,
18772 file_exists,
18773 cx,
18774 )
18775 });
18776 None
18777 }
18778
18779 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18780 let ranges: Vec<_> = self
18781 .selections
18782 .disjoint_anchors()
18783 .iter()
18784 .map(|s| s.range())
18785 .collect();
18786 self.buffer
18787 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18788 }
18789
18790 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18791 self.buffer.update(cx, |buffer, cx| {
18792 let ranges = vec![Anchor::min()..Anchor::max()];
18793 if !buffer.all_diff_hunks_expanded()
18794 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18795 {
18796 buffer.collapse_diff_hunks(ranges, cx);
18797 true
18798 } else {
18799 false
18800 }
18801 })
18802 }
18803
18804 fn toggle_diff_hunks_in_ranges(
18805 &mut self,
18806 ranges: Vec<Range<Anchor>>,
18807 cx: &mut Context<Editor>,
18808 ) {
18809 self.buffer.update(cx, |buffer, cx| {
18810 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18811 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18812 })
18813 }
18814
18815 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18816 self.buffer.update(cx, |buffer, cx| {
18817 let snapshot = buffer.snapshot(cx);
18818 let excerpt_id = range.end.excerpt_id;
18819 let point_range = range.to_point(&snapshot);
18820 let expand = !buffer.single_hunk_is_expanded(range, cx);
18821 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18822 })
18823 }
18824
18825 pub(crate) fn apply_all_diff_hunks(
18826 &mut self,
18827 _: &ApplyAllDiffHunks,
18828 window: &mut Window,
18829 cx: &mut Context<Self>,
18830 ) {
18831 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18832
18833 let buffers = self.buffer.read(cx).all_buffers();
18834 for branch_buffer in buffers {
18835 branch_buffer.update(cx, |branch_buffer, cx| {
18836 branch_buffer.merge_into_base(Vec::new(), cx);
18837 });
18838 }
18839
18840 if let Some(project) = self.project.clone() {
18841 self.save(
18842 SaveOptions {
18843 format: true,
18844 autosave: false,
18845 },
18846 project,
18847 window,
18848 cx,
18849 )
18850 .detach_and_log_err(cx);
18851 }
18852 }
18853
18854 pub(crate) fn apply_selected_diff_hunks(
18855 &mut self,
18856 _: &ApplyDiffHunk,
18857 window: &mut Window,
18858 cx: &mut Context<Self>,
18859 ) {
18860 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18861 let snapshot = self.snapshot(window, cx);
18862 let hunks = snapshot.hunks_for_ranges(
18863 self.selections
18864 .all(cx)
18865 .into_iter()
18866 .map(|selection| selection.range()),
18867 );
18868 let mut ranges_by_buffer = HashMap::default();
18869 self.transact(window, cx, |editor, _window, cx| {
18870 for hunk in hunks {
18871 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18872 ranges_by_buffer
18873 .entry(buffer.clone())
18874 .or_insert_with(Vec::new)
18875 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18876 }
18877 }
18878
18879 for (buffer, ranges) in ranges_by_buffer {
18880 buffer.update(cx, |buffer, cx| {
18881 buffer.merge_into_base(ranges, cx);
18882 });
18883 }
18884 });
18885
18886 if let Some(project) = self.project.clone() {
18887 self.save(
18888 SaveOptions {
18889 format: true,
18890 autosave: false,
18891 },
18892 project,
18893 window,
18894 cx,
18895 )
18896 .detach_and_log_err(cx);
18897 }
18898 }
18899
18900 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18901 if hovered != self.gutter_hovered {
18902 self.gutter_hovered = hovered;
18903 cx.notify();
18904 }
18905 }
18906
18907 pub fn insert_blocks(
18908 &mut self,
18909 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18910 autoscroll: Option<Autoscroll>,
18911 cx: &mut Context<Self>,
18912 ) -> Vec<CustomBlockId> {
18913 let blocks = self
18914 .display_map
18915 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18916 if let Some(autoscroll) = autoscroll {
18917 self.request_autoscroll(autoscroll, cx);
18918 }
18919 cx.notify();
18920 blocks
18921 }
18922
18923 pub fn resize_blocks(
18924 &mut self,
18925 heights: HashMap<CustomBlockId, u32>,
18926 autoscroll: Option<Autoscroll>,
18927 cx: &mut Context<Self>,
18928 ) {
18929 self.display_map
18930 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18931 if let Some(autoscroll) = autoscroll {
18932 self.request_autoscroll(autoscroll, cx);
18933 }
18934 cx.notify();
18935 }
18936
18937 pub fn replace_blocks(
18938 &mut self,
18939 renderers: HashMap<CustomBlockId, RenderBlock>,
18940 autoscroll: Option<Autoscroll>,
18941 cx: &mut Context<Self>,
18942 ) {
18943 self.display_map
18944 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18945 if let Some(autoscroll) = autoscroll {
18946 self.request_autoscroll(autoscroll, cx);
18947 }
18948 cx.notify();
18949 }
18950
18951 pub fn remove_blocks(
18952 &mut self,
18953 block_ids: HashSet<CustomBlockId>,
18954 autoscroll: Option<Autoscroll>,
18955 cx: &mut Context<Self>,
18956 ) {
18957 self.display_map.update(cx, |display_map, cx| {
18958 display_map.remove_blocks(block_ids, cx)
18959 });
18960 if let Some(autoscroll) = autoscroll {
18961 self.request_autoscroll(autoscroll, cx);
18962 }
18963 cx.notify();
18964 }
18965
18966 pub fn row_for_block(
18967 &self,
18968 block_id: CustomBlockId,
18969 cx: &mut Context<Self>,
18970 ) -> Option<DisplayRow> {
18971 self.display_map
18972 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18973 }
18974
18975 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18976 self.focused_block = Some(focused_block);
18977 }
18978
18979 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18980 self.focused_block.take()
18981 }
18982
18983 pub fn insert_creases(
18984 &mut self,
18985 creases: impl IntoIterator<Item = Crease<Anchor>>,
18986 cx: &mut Context<Self>,
18987 ) -> Vec<CreaseId> {
18988 self.display_map
18989 .update(cx, |map, cx| map.insert_creases(creases, cx))
18990 }
18991
18992 pub fn remove_creases(
18993 &mut self,
18994 ids: impl IntoIterator<Item = CreaseId>,
18995 cx: &mut Context<Self>,
18996 ) -> Vec<(CreaseId, Range<Anchor>)> {
18997 self.display_map
18998 .update(cx, |map, cx| map.remove_creases(ids, cx))
18999 }
19000
19001 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19002 self.display_map
19003 .update(cx, |map, cx| map.snapshot(cx))
19004 .longest_row()
19005 }
19006
19007 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19008 self.display_map
19009 .update(cx, |map, cx| map.snapshot(cx))
19010 .max_point()
19011 }
19012
19013 pub fn text(&self, cx: &App) -> String {
19014 self.buffer.read(cx).read(cx).text()
19015 }
19016
19017 pub fn is_empty(&self, cx: &App) -> bool {
19018 self.buffer.read(cx).read(cx).is_empty()
19019 }
19020
19021 pub fn text_option(&self, cx: &App) -> Option<String> {
19022 let text = self.text(cx);
19023 let text = text.trim();
19024
19025 if text.is_empty() {
19026 return None;
19027 }
19028
19029 Some(text.to_string())
19030 }
19031
19032 pub fn set_text(
19033 &mut self,
19034 text: impl Into<Arc<str>>,
19035 window: &mut Window,
19036 cx: &mut Context<Self>,
19037 ) {
19038 self.transact(window, cx, |this, _, cx| {
19039 this.buffer
19040 .read(cx)
19041 .as_singleton()
19042 .expect("you can only call set_text on editors for singleton buffers")
19043 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19044 });
19045 }
19046
19047 pub fn display_text(&self, cx: &mut App) -> String {
19048 self.display_map
19049 .update(cx, |map, cx| map.snapshot(cx))
19050 .text()
19051 }
19052
19053 fn create_minimap(
19054 &self,
19055 minimap_settings: MinimapSettings,
19056 window: &mut Window,
19057 cx: &mut Context<Self>,
19058 ) -> Option<Entity<Self>> {
19059 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
19060 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19061 }
19062
19063 fn initialize_new_minimap(
19064 &self,
19065 minimap_settings: MinimapSettings,
19066 window: &mut Window,
19067 cx: &mut Context<Self>,
19068 ) -> Entity<Self> {
19069 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19070
19071 let mut minimap = Editor::new_internal(
19072 EditorMode::Minimap {
19073 parent: cx.weak_entity(),
19074 },
19075 self.buffer.clone(),
19076 None,
19077 Some(self.display_map.clone()),
19078 window,
19079 cx,
19080 );
19081 minimap.scroll_manager.clone_state(&self.scroll_manager);
19082 minimap.set_text_style_refinement(TextStyleRefinement {
19083 font_size: Some(MINIMAP_FONT_SIZE),
19084 font_weight: Some(MINIMAP_FONT_WEIGHT),
19085 ..Default::default()
19086 });
19087 minimap.update_minimap_configuration(minimap_settings, cx);
19088 cx.new(|_| minimap)
19089 }
19090
19091 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19092 let current_line_highlight = minimap_settings
19093 .current_line_highlight
19094 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19095 self.set_current_line_highlight(Some(current_line_highlight));
19096 }
19097
19098 pub fn minimap(&self) -> Option<&Entity<Self>> {
19099 self.minimap
19100 .as_ref()
19101 .filter(|_| self.minimap_visibility.visible())
19102 }
19103
19104 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19105 let mut wrap_guides = smallvec![];
19106
19107 if self.show_wrap_guides == Some(false) {
19108 return wrap_guides;
19109 }
19110
19111 let settings = self.buffer.read(cx).language_settings(cx);
19112 if settings.show_wrap_guides {
19113 match self.soft_wrap_mode(cx) {
19114 SoftWrap::Column(soft_wrap) => {
19115 wrap_guides.push((soft_wrap as usize, true));
19116 }
19117 SoftWrap::Bounded(soft_wrap) => {
19118 wrap_guides.push((soft_wrap as usize, true));
19119 }
19120 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19121 }
19122 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19123 }
19124
19125 wrap_guides
19126 }
19127
19128 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19129 let settings = self.buffer.read(cx).language_settings(cx);
19130 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19131 match mode {
19132 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19133 SoftWrap::None
19134 }
19135 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19136 language_settings::SoftWrap::PreferredLineLength => {
19137 SoftWrap::Column(settings.preferred_line_length)
19138 }
19139 language_settings::SoftWrap::Bounded => {
19140 SoftWrap::Bounded(settings.preferred_line_length)
19141 }
19142 }
19143 }
19144
19145 pub fn set_soft_wrap_mode(
19146 &mut self,
19147 mode: language_settings::SoftWrap,
19148
19149 cx: &mut Context<Self>,
19150 ) {
19151 self.soft_wrap_mode_override = Some(mode);
19152 cx.notify();
19153 }
19154
19155 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19156 self.hard_wrap = hard_wrap;
19157 cx.notify();
19158 }
19159
19160 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19161 self.text_style_refinement = Some(style);
19162 }
19163
19164 /// called by the Element so we know what style we were most recently rendered with.
19165 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19166 // We intentionally do not inform the display map about the minimap style
19167 // so that wrapping is not recalculated and stays consistent for the editor
19168 // and its linked minimap.
19169 if !self.mode.is_minimap() {
19170 let font = style.text.font();
19171 let font_size = style.text.font_size.to_pixels(window.rem_size());
19172 let display_map = self
19173 .placeholder_display_map
19174 .as_ref()
19175 .filter(|_| self.is_empty(cx))
19176 .unwrap_or(&self.display_map);
19177
19178 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19179 }
19180 self.style = Some(style);
19181 }
19182
19183 pub fn style(&self) -> Option<&EditorStyle> {
19184 self.style.as_ref()
19185 }
19186
19187 // Called by the element. This method is not designed to be called outside of the editor
19188 // element's layout code because it does not notify when rewrapping is computed synchronously.
19189 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19190 if self.is_empty(cx) {
19191 self.placeholder_display_map
19192 .as_ref()
19193 .map_or(false, |display_map| {
19194 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19195 })
19196 } else {
19197 self.display_map
19198 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19199 }
19200 }
19201
19202 pub fn set_soft_wrap(&mut self) {
19203 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19204 }
19205
19206 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19207 if self.soft_wrap_mode_override.is_some() {
19208 self.soft_wrap_mode_override.take();
19209 } else {
19210 let soft_wrap = match self.soft_wrap_mode(cx) {
19211 SoftWrap::GitDiff => return,
19212 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19213 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19214 language_settings::SoftWrap::None
19215 }
19216 };
19217 self.soft_wrap_mode_override = Some(soft_wrap);
19218 }
19219 cx.notify();
19220 }
19221
19222 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19223 let Some(workspace) = self.workspace() else {
19224 return;
19225 };
19226 let fs = workspace.read(cx).app_state().fs.clone();
19227 let current_show = TabBarSettings::get_global(cx).show;
19228 update_settings_file(fs, cx, move |setting, _| {
19229 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19230 });
19231 }
19232
19233 pub fn toggle_indent_guides(
19234 &mut self,
19235 _: &ToggleIndentGuides,
19236 _: &mut Window,
19237 cx: &mut Context<Self>,
19238 ) {
19239 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19240 self.buffer
19241 .read(cx)
19242 .language_settings(cx)
19243 .indent_guides
19244 .enabled
19245 });
19246 self.show_indent_guides = Some(!currently_enabled);
19247 cx.notify();
19248 }
19249
19250 fn should_show_indent_guides(&self) -> Option<bool> {
19251 self.show_indent_guides
19252 }
19253
19254 pub fn toggle_line_numbers(
19255 &mut self,
19256 _: &ToggleLineNumbers,
19257 _: &mut Window,
19258 cx: &mut Context<Self>,
19259 ) {
19260 let mut editor_settings = EditorSettings::get_global(cx).clone();
19261 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19262 EditorSettings::override_global(editor_settings, cx);
19263 }
19264
19265 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19266 if let Some(show_line_numbers) = self.show_line_numbers {
19267 return show_line_numbers;
19268 }
19269 EditorSettings::get_global(cx).gutter.line_numbers
19270 }
19271
19272 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19273 self.use_relative_line_numbers
19274 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19275 }
19276
19277 pub fn toggle_relative_line_numbers(
19278 &mut self,
19279 _: &ToggleRelativeLineNumbers,
19280 _: &mut Window,
19281 cx: &mut Context<Self>,
19282 ) {
19283 let is_relative = self.should_use_relative_line_numbers(cx);
19284 self.set_relative_line_number(Some(!is_relative), cx)
19285 }
19286
19287 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19288 self.use_relative_line_numbers = is_relative;
19289 cx.notify();
19290 }
19291
19292 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19293 self.show_gutter = show_gutter;
19294 cx.notify();
19295 }
19296
19297 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19298 self.show_scrollbars = ScrollbarAxes {
19299 horizontal: show,
19300 vertical: show,
19301 };
19302 cx.notify();
19303 }
19304
19305 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19306 self.show_scrollbars.vertical = show;
19307 cx.notify();
19308 }
19309
19310 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19311 self.show_scrollbars.horizontal = show;
19312 cx.notify();
19313 }
19314
19315 pub fn set_minimap_visibility(
19316 &mut self,
19317 minimap_visibility: MinimapVisibility,
19318 window: &mut Window,
19319 cx: &mut Context<Self>,
19320 ) {
19321 if self.minimap_visibility != minimap_visibility {
19322 if minimap_visibility.visible() && self.minimap.is_none() {
19323 let minimap_settings = EditorSettings::get_global(cx).minimap;
19324 self.minimap =
19325 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19326 }
19327 self.minimap_visibility = minimap_visibility;
19328 cx.notify();
19329 }
19330 }
19331
19332 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19333 self.set_show_scrollbars(false, cx);
19334 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19335 }
19336
19337 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19338 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19339 }
19340
19341 /// Normally the text in full mode and auto height editors is padded on the
19342 /// left side by roughly half a character width for improved hit testing.
19343 ///
19344 /// Use this method to disable this for cases where this is not wanted (e.g.
19345 /// if you want to align the editor text with some other text above or below)
19346 /// or if you want to add this padding to single-line editors.
19347 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19348 self.offset_content = offset_content;
19349 cx.notify();
19350 }
19351
19352 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19353 self.show_line_numbers = Some(show_line_numbers);
19354 cx.notify();
19355 }
19356
19357 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19358 self.disable_expand_excerpt_buttons = true;
19359 cx.notify();
19360 }
19361
19362 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19363 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19364 cx.notify();
19365 }
19366
19367 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19368 self.show_code_actions = Some(show_code_actions);
19369 cx.notify();
19370 }
19371
19372 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19373 self.show_runnables = Some(show_runnables);
19374 cx.notify();
19375 }
19376
19377 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19378 self.show_breakpoints = Some(show_breakpoints);
19379 cx.notify();
19380 }
19381
19382 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19383 if self.display_map.read(cx).masked != masked {
19384 self.display_map.update(cx, |map, _| map.masked = masked);
19385 }
19386 cx.notify()
19387 }
19388
19389 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19390 self.show_wrap_guides = Some(show_wrap_guides);
19391 cx.notify();
19392 }
19393
19394 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19395 self.show_indent_guides = Some(show_indent_guides);
19396 cx.notify();
19397 }
19398
19399 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19400 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19401 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19402 && let Some(dir) = file.abs_path(cx).parent()
19403 {
19404 return Some(dir.to_owned());
19405 }
19406 }
19407
19408 None
19409 }
19410
19411 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19412 self.active_excerpt(cx)?
19413 .1
19414 .read(cx)
19415 .file()
19416 .and_then(|f| f.as_local())
19417 }
19418
19419 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19420 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19421 let buffer = buffer.read(cx);
19422 if let Some(project_path) = buffer.project_path(cx) {
19423 let project = self.project()?.read(cx);
19424 project.absolute_path(&project_path, cx)
19425 } else {
19426 buffer
19427 .file()
19428 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19429 }
19430 })
19431 }
19432
19433 pub fn reveal_in_finder(
19434 &mut self,
19435 _: &RevealInFileManager,
19436 _window: &mut Window,
19437 cx: &mut Context<Self>,
19438 ) {
19439 if let Some(target) = self.target_file(cx) {
19440 cx.reveal_path(&target.abs_path(cx));
19441 }
19442 }
19443
19444 pub fn copy_path(
19445 &mut self,
19446 _: &zed_actions::workspace::CopyPath,
19447 _window: &mut Window,
19448 cx: &mut Context<Self>,
19449 ) {
19450 if let Some(path) = self.target_file_abs_path(cx)
19451 && let Some(path) = path.to_str()
19452 {
19453 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19454 } else {
19455 cx.propagate();
19456 }
19457 }
19458
19459 pub fn copy_relative_path(
19460 &mut self,
19461 _: &zed_actions::workspace::CopyRelativePath,
19462 _window: &mut Window,
19463 cx: &mut Context<Self>,
19464 ) {
19465 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19466 let project = self.project()?.read(cx);
19467 let path = buffer.read(cx).file()?.path();
19468 let path = path.display(project.path_style(cx));
19469 Some(path)
19470 }) {
19471 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19472 } else {
19473 cx.propagate();
19474 }
19475 }
19476
19477 /// Returns the project path for the editor's buffer, if any buffer is
19478 /// opened in the editor.
19479 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19480 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19481 buffer.read(cx).project_path(cx)
19482 } else {
19483 None
19484 }
19485 }
19486
19487 // Returns true if the editor handled a go-to-line request
19488 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19489 maybe!({
19490 let breakpoint_store = self.breakpoint_store.as_ref()?;
19491
19492 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19493 else {
19494 self.clear_row_highlights::<ActiveDebugLine>();
19495 return None;
19496 };
19497
19498 let position = active_stack_frame.position;
19499 let buffer_id = position.buffer_id?;
19500 let snapshot = self
19501 .project
19502 .as_ref()?
19503 .read(cx)
19504 .buffer_for_id(buffer_id, cx)?
19505 .read(cx)
19506 .snapshot();
19507
19508 let mut handled = false;
19509 for (id, ExcerptRange { context, .. }) in
19510 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19511 {
19512 if context.start.cmp(&position, &snapshot).is_ge()
19513 || context.end.cmp(&position, &snapshot).is_lt()
19514 {
19515 continue;
19516 }
19517 let snapshot = self.buffer.read(cx).snapshot(cx);
19518 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19519
19520 handled = true;
19521 self.clear_row_highlights::<ActiveDebugLine>();
19522
19523 self.go_to_line::<ActiveDebugLine>(
19524 multibuffer_anchor,
19525 Some(cx.theme().colors().editor_debugger_active_line_background),
19526 window,
19527 cx,
19528 );
19529
19530 cx.notify();
19531 }
19532
19533 handled.then_some(())
19534 })
19535 .is_some()
19536 }
19537
19538 pub fn copy_file_name_without_extension(
19539 &mut self,
19540 _: &CopyFileNameWithoutExtension,
19541 _: &mut Window,
19542 cx: &mut Context<Self>,
19543 ) {
19544 if let Some(file) = self.target_file(cx)
19545 && let Some(file_stem) = file.path().file_stem()
19546 {
19547 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19548 }
19549 }
19550
19551 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19552 if let Some(file) = self.target_file(cx)
19553 && let Some(name) = file.path().file_name()
19554 {
19555 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19556 }
19557 }
19558
19559 pub fn toggle_git_blame(
19560 &mut self,
19561 _: &::git::Blame,
19562 window: &mut Window,
19563 cx: &mut Context<Self>,
19564 ) {
19565 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19566
19567 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19568 self.start_git_blame(true, window, cx);
19569 }
19570
19571 cx.notify();
19572 }
19573
19574 pub fn toggle_git_blame_inline(
19575 &mut self,
19576 _: &ToggleGitBlameInline,
19577 window: &mut Window,
19578 cx: &mut Context<Self>,
19579 ) {
19580 self.toggle_git_blame_inline_internal(true, window, cx);
19581 cx.notify();
19582 }
19583
19584 pub fn open_git_blame_commit(
19585 &mut self,
19586 _: &OpenGitBlameCommit,
19587 window: &mut Window,
19588 cx: &mut Context<Self>,
19589 ) {
19590 self.open_git_blame_commit_internal(window, cx);
19591 }
19592
19593 fn open_git_blame_commit_internal(
19594 &mut self,
19595 window: &mut Window,
19596 cx: &mut Context<Self>,
19597 ) -> Option<()> {
19598 let blame = self.blame.as_ref()?;
19599 let snapshot = self.snapshot(window, cx);
19600 let cursor = self.selections.newest::<Point>(cx).head();
19601 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19602 let (_, blame_entry) = blame
19603 .update(cx, |blame, cx| {
19604 blame
19605 .blame_for_rows(
19606 &[RowInfo {
19607 buffer_id: Some(buffer.remote_id()),
19608 buffer_row: Some(point.row),
19609 ..Default::default()
19610 }],
19611 cx,
19612 )
19613 .next()
19614 })
19615 .flatten()?;
19616 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19617 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19618 let workspace = self.workspace()?.downgrade();
19619 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19620 None
19621 }
19622
19623 pub fn git_blame_inline_enabled(&self) -> bool {
19624 self.git_blame_inline_enabled
19625 }
19626
19627 pub fn toggle_selection_menu(
19628 &mut self,
19629 _: &ToggleSelectionMenu,
19630 _: &mut Window,
19631 cx: &mut Context<Self>,
19632 ) {
19633 self.show_selection_menu = self
19634 .show_selection_menu
19635 .map(|show_selections_menu| !show_selections_menu)
19636 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19637
19638 cx.notify();
19639 }
19640
19641 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19642 self.show_selection_menu
19643 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19644 }
19645
19646 fn start_git_blame(
19647 &mut self,
19648 user_triggered: bool,
19649 window: &mut Window,
19650 cx: &mut Context<Self>,
19651 ) {
19652 if let Some(project) = self.project() {
19653 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19654 && buffer.read(cx).file().is_none()
19655 {
19656 return;
19657 }
19658
19659 let focused = self.focus_handle(cx).contains_focused(window, cx);
19660
19661 let project = project.clone();
19662 let blame = cx
19663 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19664 self.blame_subscription =
19665 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19666 self.blame = Some(blame);
19667 }
19668 }
19669
19670 fn toggle_git_blame_inline_internal(
19671 &mut self,
19672 user_triggered: bool,
19673 window: &mut Window,
19674 cx: &mut Context<Self>,
19675 ) {
19676 if self.git_blame_inline_enabled {
19677 self.git_blame_inline_enabled = false;
19678 self.show_git_blame_inline = false;
19679 self.show_git_blame_inline_delay_task.take();
19680 } else {
19681 self.git_blame_inline_enabled = true;
19682 self.start_git_blame_inline(user_triggered, window, cx);
19683 }
19684
19685 cx.notify();
19686 }
19687
19688 fn start_git_blame_inline(
19689 &mut self,
19690 user_triggered: bool,
19691 window: &mut Window,
19692 cx: &mut Context<Self>,
19693 ) {
19694 self.start_git_blame(user_triggered, window, cx);
19695
19696 if ProjectSettings::get_global(cx)
19697 .git
19698 .inline_blame_delay()
19699 .is_some()
19700 {
19701 self.start_inline_blame_timer(window, cx);
19702 } else {
19703 self.show_git_blame_inline = true
19704 }
19705 }
19706
19707 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19708 self.blame.as_ref()
19709 }
19710
19711 pub fn show_git_blame_gutter(&self) -> bool {
19712 self.show_git_blame_gutter
19713 }
19714
19715 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19716 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19717 }
19718
19719 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19720 self.show_git_blame_inline
19721 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19722 && !self.newest_selection_head_on_empty_line(cx)
19723 && self.has_blame_entries(cx)
19724 }
19725
19726 fn has_blame_entries(&self, cx: &App) -> bool {
19727 self.blame()
19728 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19729 }
19730
19731 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19732 let cursor_anchor = self.selections.newest_anchor().head();
19733
19734 let snapshot = self.buffer.read(cx).snapshot(cx);
19735 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19736
19737 snapshot.line_len(buffer_row) == 0
19738 }
19739
19740 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19741 let buffer_and_selection = maybe!({
19742 let selection = self.selections.newest::<Point>(cx);
19743 let selection_range = selection.range();
19744
19745 let multi_buffer = self.buffer().read(cx);
19746 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19747 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19748
19749 let (buffer, range, _) = if selection.reversed {
19750 buffer_ranges.first()
19751 } else {
19752 buffer_ranges.last()
19753 }?;
19754
19755 let selection = text::ToPoint::to_point(&range.start, buffer).row
19756 ..text::ToPoint::to_point(&range.end, buffer).row;
19757 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19758 });
19759
19760 let Some((buffer, selection)) = buffer_and_selection else {
19761 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19762 };
19763
19764 let Some(project) = self.project() else {
19765 return Task::ready(Err(anyhow!("editor does not have project")));
19766 };
19767
19768 project.update(cx, |project, cx| {
19769 project.get_permalink_to_line(&buffer, selection, cx)
19770 })
19771 }
19772
19773 pub fn copy_permalink_to_line(
19774 &mut self,
19775 _: &CopyPermalinkToLine,
19776 window: &mut Window,
19777 cx: &mut Context<Self>,
19778 ) {
19779 let permalink_task = self.get_permalink_to_line(cx);
19780 let workspace = self.workspace();
19781
19782 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19783 Ok(permalink) => {
19784 cx.update(|_, cx| {
19785 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19786 })
19787 .ok();
19788 }
19789 Err(err) => {
19790 let message = format!("Failed to copy permalink: {err}");
19791
19792 anyhow::Result::<()>::Err(err).log_err();
19793
19794 if let Some(workspace) = workspace {
19795 workspace
19796 .update_in(cx, |workspace, _, cx| {
19797 struct CopyPermalinkToLine;
19798
19799 workspace.show_toast(
19800 Toast::new(
19801 NotificationId::unique::<CopyPermalinkToLine>(),
19802 message,
19803 ),
19804 cx,
19805 )
19806 })
19807 .ok();
19808 }
19809 }
19810 })
19811 .detach();
19812 }
19813
19814 pub fn copy_file_location(
19815 &mut self,
19816 _: &CopyFileLocation,
19817 _: &mut Window,
19818 cx: &mut Context<Self>,
19819 ) {
19820 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19821 if let Some(file) = self.target_file(cx) {
19822 let path = file.path().display(file.path_style(cx));
19823 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19824 }
19825 }
19826
19827 pub fn open_permalink_to_line(
19828 &mut self,
19829 _: &OpenPermalinkToLine,
19830 window: &mut Window,
19831 cx: &mut Context<Self>,
19832 ) {
19833 let permalink_task = self.get_permalink_to_line(cx);
19834 let workspace = self.workspace();
19835
19836 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19837 Ok(permalink) => {
19838 cx.update(|_, cx| {
19839 cx.open_url(permalink.as_ref());
19840 })
19841 .ok();
19842 }
19843 Err(err) => {
19844 let message = format!("Failed to open permalink: {err}");
19845
19846 anyhow::Result::<()>::Err(err).log_err();
19847
19848 if let Some(workspace) = workspace {
19849 workspace
19850 .update(cx, |workspace, cx| {
19851 struct OpenPermalinkToLine;
19852
19853 workspace.show_toast(
19854 Toast::new(
19855 NotificationId::unique::<OpenPermalinkToLine>(),
19856 message,
19857 ),
19858 cx,
19859 )
19860 })
19861 .ok();
19862 }
19863 }
19864 })
19865 .detach();
19866 }
19867
19868 pub fn insert_uuid_v4(
19869 &mut self,
19870 _: &InsertUuidV4,
19871 window: &mut Window,
19872 cx: &mut Context<Self>,
19873 ) {
19874 self.insert_uuid(UuidVersion::V4, window, cx);
19875 }
19876
19877 pub fn insert_uuid_v7(
19878 &mut self,
19879 _: &InsertUuidV7,
19880 window: &mut Window,
19881 cx: &mut Context<Self>,
19882 ) {
19883 self.insert_uuid(UuidVersion::V7, window, cx);
19884 }
19885
19886 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19887 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19888 self.transact(window, cx, |this, window, cx| {
19889 let edits = this
19890 .selections
19891 .all::<Point>(cx)
19892 .into_iter()
19893 .map(|selection| {
19894 let uuid = match version {
19895 UuidVersion::V4 => uuid::Uuid::new_v4(),
19896 UuidVersion::V7 => uuid::Uuid::now_v7(),
19897 };
19898
19899 (selection.range(), uuid.to_string())
19900 });
19901 this.edit(edits, cx);
19902 this.refresh_edit_prediction(true, false, window, cx);
19903 });
19904 }
19905
19906 pub fn open_selections_in_multibuffer(
19907 &mut self,
19908 _: &OpenSelectionsInMultibuffer,
19909 window: &mut Window,
19910 cx: &mut Context<Self>,
19911 ) {
19912 let multibuffer = self.buffer.read(cx);
19913
19914 let Some(buffer) = multibuffer.as_singleton() else {
19915 return;
19916 };
19917
19918 let Some(workspace) = self.workspace() else {
19919 return;
19920 };
19921
19922 let title = multibuffer.title(cx).to_string();
19923
19924 let locations = self
19925 .selections
19926 .all_anchors(cx)
19927 .iter()
19928 .map(|selection| {
19929 (
19930 buffer.clone(),
19931 (selection.start.text_anchor..selection.end.text_anchor)
19932 .to_point(buffer.read(cx)),
19933 )
19934 })
19935 .into_group_map();
19936
19937 cx.spawn_in(window, async move |_, cx| {
19938 workspace.update_in(cx, |workspace, window, cx| {
19939 Self::open_locations_in_multibuffer(
19940 workspace,
19941 locations,
19942 format!("Selections for '{title}'"),
19943 false,
19944 MultibufferSelectionMode::All,
19945 window,
19946 cx,
19947 );
19948 })
19949 })
19950 .detach();
19951 }
19952
19953 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19954 /// last highlight added will be used.
19955 ///
19956 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19957 pub fn highlight_rows<T: 'static>(
19958 &mut self,
19959 range: Range<Anchor>,
19960 color: Hsla,
19961 options: RowHighlightOptions,
19962 cx: &mut Context<Self>,
19963 ) {
19964 let snapshot = self.buffer().read(cx).snapshot(cx);
19965 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19966 let ix = row_highlights.binary_search_by(|highlight| {
19967 Ordering::Equal
19968 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19969 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19970 });
19971
19972 if let Err(mut ix) = ix {
19973 let index = post_inc(&mut self.highlight_order);
19974
19975 // If this range intersects with the preceding highlight, then merge it with
19976 // the preceding highlight. Otherwise insert a new highlight.
19977 let mut merged = false;
19978 if ix > 0 {
19979 let prev_highlight = &mut row_highlights[ix - 1];
19980 if prev_highlight
19981 .range
19982 .end
19983 .cmp(&range.start, &snapshot)
19984 .is_ge()
19985 {
19986 ix -= 1;
19987 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19988 prev_highlight.range.end = range.end;
19989 }
19990 merged = true;
19991 prev_highlight.index = index;
19992 prev_highlight.color = color;
19993 prev_highlight.options = options;
19994 }
19995 }
19996
19997 if !merged {
19998 row_highlights.insert(
19999 ix,
20000 RowHighlight {
20001 range,
20002 index,
20003 color,
20004 options,
20005 type_id: TypeId::of::<T>(),
20006 },
20007 );
20008 }
20009
20010 // If any of the following highlights intersect with this one, merge them.
20011 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20012 let highlight = &row_highlights[ix];
20013 if next_highlight
20014 .range
20015 .start
20016 .cmp(&highlight.range.end, &snapshot)
20017 .is_le()
20018 {
20019 if next_highlight
20020 .range
20021 .end
20022 .cmp(&highlight.range.end, &snapshot)
20023 .is_gt()
20024 {
20025 row_highlights[ix].range.end = next_highlight.range.end;
20026 }
20027 row_highlights.remove(ix + 1);
20028 } else {
20029 break;
20030 }
20031 }
20032 }
20033 }
20034
20035 /// Remove any highlighted row ranges of the given type that intersect the
20036 /// given ranges.
20037 pub fn remove_highlighted_rows<T: 'static>(
20038 &mut self,
20039 ranges_to_remove: Vec<Range<Anchor>>,
20040 cx: &mut Context<Self>,
20041 ) {
20042 let snapshot = self.buffer().read(cx).snapshot(cx);
20043 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20044 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20045 row_highlights.retain(|highlight| {
20046 while let Some(range_to_remove) = ranges_to_remove.peek() {
20047 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20048 Ordering::Less | Ordering::Equal => {
20049 ranges_to_remove.next();
20050 }
20051 Ordering::Greater => {
20052 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20053 Ordering::Less | Ordering::Equal => {
20054 return false;
20055 }
20056 Ordering::Greater => break,
20057 }
20058 }
20059 }
20060 }
20061
20062 true
20063 })
20064 }
20065
20066 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20067 pub fn clear_row_highlights<T: 'static>(&mut self) {
20068 self.highlighted_rows.remove(&TypeId::of::<T>());
20069 }
20070
20071 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20072 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20073 self.highlighted_rows
20074 .get(&TypeId::of::<T>())
20075 .map_or(&[] as &[_], |vec| vec.as_slice())
20076 .iter()
20077 .map(|highlight| (highlight.range.clone(), highlight.color))
20078 }
20079
20080 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20081 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20082 /// Allows to ignore certain kinds of highlights.
20083 pub fn highlighted_display_rows(
20084 &self,
20085 window: &mut Window,
20086 cx: &mut App,
20087 ) -> BTreeMap<DisplayRow, LineHighlight> {
20088 let snapshot = self.snapshot(window, cx);
20089 let mut used_highlight_orders = HashMap::default();
20090 self.highlighted_rows
20091 .iter()
20092 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20093 .fold(
20094 BTreeMap::<DisplayRow, LineHighlight>::new(),
20095 |mut unique_rows, highlight| {
20096 let start = highlight.range.start.to_display_point(&snapshot);
20097 let end = highlight.range.end.to_display_point(&snapshot);
20098 let start_row = start.row().0;
20099 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20100 && end.column() == 0
20101 {
20102 end.row().0.saturating_sub(1)
20103 } else {
20104 end.row().0
20105 };
20106 for row in start_row..=end_row {
20107 let used_index =
20108 used_highlight_orders.entry(row).or_insert(highlight.index);
20109 if highlight.index >= *used_index {
20110 *used_index = highlight.index;
20111 unique_rows.insert(
20112 DisplayRow(row),
20113 LineHighlight {
20114 include_gutter: highlight.options.include_gutter,
20115 border: None,
20116 background: highlight.color.into(),
20117 type_id: Some(highlight.type_id),
20118 },
20119 );
20120 }
20121 }
20122 unique_rows
20123 },
20124 )
20125 }
20126
20127 pub fn highlighted_display_row_for_autoscroll(
20128 &self,
20129 snapshot: &DisplaySnapshot,
20130 ) -> Option<DisplayRow> {
20131 self.highlighted_rows
20132 .values()
20133 .flat_map(|highlighted_rows| highlighted_rows.iter())
20134 .filter_map(|highlight| {
20135 if highlight.options.autoscroll {
20136 Some(highlight.range.start.to_display_point(snapshot).row())
20137 } else {
20138 None
20139 }
20140 })
20141 .min()
20142 }
20143
20144 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20145 self.highlight_background::<SearchWithinRange>(
20146 ranges,
20147 |colors| colors.colors().editor_document_highlight_read_background,
20148 cx,
20149 )
20150 }
20151
20152 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20153 self.breadcrumb_header = Some(new_header);
20154 }
20155
20156 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20157 self.clear_background_highlights::<SearchWithinRange>(cx);
20158 }
20159
20160 pub fn highlight_background<T: 'static>(
20161 &mut self,
20162 ranges: &[Range<Anchor>],
20163 color_fetcher: fn(&Theme) -> Hsla,
20164 cx: &mut Context<Self>,
20165 ) {
20166 self.background_highlights.insert(
20167 HighlightKey::Type(TypeId::of::<T>()),
20168 (color_fetcher, Arc::from(ranges)),
20169 );
20170 self.scrollbar_marker_state.dirty = true;
20171 cx.notify();
20172 }
20173
20174 pub fn highlight_background_key<T: 'static>(
20175 &mut self,
20176 key: usize,
20177 ranges: &[Range<Anchor>],
20178 color_fetcher: fn(&Theme) -> Hsla,
20179 cx: &mut Context<Self>,
20180 ) {
20181 self.background_highlights.insert(
20182 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20183 (color_fetcher, Arc::from(ranges)),
20184 );
20185 self.scrollbar_marker_state.dirty = true;
20186 cx.notify();
20187 }
20188
20189 pub fn clear_background_highlights<T: 'static>(
20190 &mut self,
20191 cx: &mut Context<Self>,
20192 ) -> Option<BackgroundHighlight> {
20193 let text_highlights = self
20194 .background_highlights
20195 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20196 if !text_highlights.1.is_empty() {
20197 self.scrollbar_marker_state.dirty = true;
20198 cx.notify();
20199 }
20200 Some(text_highlights)
20201 }
20202
20203 pub fn highlight_gutter<T: 'static>(
20204 &mut self,
20205 ranges: impl Into<Vec<Range<Anchor>>>,
20206 color_fetcher: fn(&App) -> Hsla,
20207 cx: &mut Context<Self>,
20208 ) {
20209 self.gutter_highlights
20210 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20211 cx.notify();
20212 }
20213
20214 pub fn clear_gutter_highlights<T: 'static>(
20215 &mut self,
20216 cx: &mut Context<Self>,
20217 ) -> Option<GutterHighlight> {
20218 cx.notify();
20219 self.gutter_highlights.remove(&TypeId::of::<T>())
20220 }
20221
20222 pub fn insert_gutter_highlight<T: 'static>(
20223 &mut self,
20224 range: Range<Anchor>,
20225 color_fetcher: fn(&App) -> Hsla,
20226 cx: &mut Context<Self>,
20227 ) {
20228 let snapshot = self.buffer().read(cx).snapshot(cx);
20229 let mut highlights = self
20230 .gutter_highlights
20231 .remove(&TypeId::of::<T>())
20232 .map(|(_, highlights)| highlights)
20233 .unwrap_or_default();
20234 let ix = highlights.binary_search_by(|highlight| {
20235 Ordering::Equal
20236 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20237 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20238 });
20239 if let Err(ix) = ix {
20240 highlights.insert(ix, range);
20241 }
20242 self.gutter_highlights
20243 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20244 }
20245
20246 pub fn remove_gutter_highlights<T: 'static>(
20247 &mut self,
20248 ranges_to_remove: Vec<Range<Anchor>>,
20249 cx: &mut Context<Self>,
20250 ) {
20251 let snapshot = self.buffer().read(cx).snapshot(cx);
20252 let Some((color_fetcher, mut gutter_highlights)) =
20253 self.gutter_highlights.remove(&TypeId::of::<T>())
20254 else {
20255 return;
20256 };
20257 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20258 gutter_highlights.retain(|highlight| {
20259 while let Some(range_to_remove) = ranges_to_remove.peek() {
20260 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20261 Ordering::Less | Ordering::Equal => {
20262 ranges_to_remove.next();
20263 }
20264 Ordering::Greater => {
20265 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20266 Ordering::Less | Ordering::Equal => {
20267 return false;
20268 }
20269 Ordering::Greater => break,
20270 }
20271 }
20272 }
20273 }
20274
20275 true
20276 });
20277 self.gutter_highlights
20278 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20279 }
20280
20281 #[cfg(feature = "test-support")]
20282 pub fn all_text_highlights(
20283 &self,
20284 window: &mut Window,
20285 cx: &mut Context<Self>,
20286 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20287 let snapshot = self.snapshot(window, cx);
20288 self.display_map.update(cx, |display_map, _| {
20289 display_map
20290 .all_text_highlights()
20291 .map(|highlight| {
20292 let (style, ranges) = highlight.as_ref();
20293 (
20294 *style,
20295 ranges
20296 .iter()
20297 .map(|range| range.clone().to_display_points(&snapshot))
20298 .collect(),
20299 )
20300 })
20301 .collect()
20302 })
20303 }
20304
20305 #[cfg(feature = "test-support")]
20306 pub fn all_text_background_highlights(
20307 &self,
20308 window: &mut Window,
20309 cx: &mut Context<Self>,
20310 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20311 let snapshot = self.snapshot(window, cx);
20312 let buffer = &snapshot.buffer_snapshot;
20313 let start = buffer.anchor_before(0);
20314 let end = buffer.anchor_after(buffer.len());
20315 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20316 }
20317
20318 #[cfg(any(test, feature = "test-support"))]
20319 pub fn sorted_background_highlights_in_range(
20320 &self,
20321 search_range: Range<Anchor>,
20322 display_snapshot: &DisplaySnapshot,
20323 theme: &Theme,
20324 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20325 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20326 res.sort_by(|a, b| {
20327 a.0.start
20328 .cmp(&b.0.start)
20329 .then_with(|| a.0.end.cmp(&b.0.end))
20330 .then_with(|| a.1.cmp(&b.1))
20331 });
20332 res
20333 }
20334
20335 #[cfg(feature = "test-support")]
20336 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20337 let snapshot = self.buffer().read(cx).snapshot(cx);
20338
20339 let highlights = self
20340 .background_highlights
20341 .get(&HighlightKey::Type(TypeId::of::<
20342 items::BufferSearchHighlights,
20343 >()));
20344
20345 if let Some((_color, ranges)) = highlights {
20346 ranges
20347 .iter()
20348 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20349 .collect_vec()
20350 } else {
20351 vec![]
20352 }
20353 }
20354
20355 fn document_highlights_for_position<'a>(
20356 &'a self,
20357 position: Anchor,
20358 buffer: &'a MultiBufferSnapshot,
20359 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20360 let read_highlights = self
20361 .background_highlights
20362 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20363 .map(|h| &h.1);
20364 let write_highlights = self
20365 .background_highlights
20366 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20367 .map(|h| &h.1);
20368 let left_position = position.bias_left(buffer);
20369 let right_position = position.bias_right(buffer);
20370 read_highlights
20371 .into_iter()
20372 .chain(write_highlights)
20373 .flat_map(move |ranges| {
20374 let start_ix = match ranges.binary_search_by(|probe| {
20375 let cmp = probe.end.cmp(&left_position, buffer);
20376 if cmp.is_ge() {
20377 Ordering::Greater
20378 } else {
20379 Ordering::Less
20380 }
20381 }) {
20382 Ok(i) | Err(i) => i,
20383 };
20384
20385 ranges[start_ix..]
20386 .iter()
20387 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20388 })
20389 }
20390
20391 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20392 self.background_highlights
20393 .get(&HighlightKey::Type(TypeId::of::<T>()))
20394 .is_some_and(|(_, highlights)| !highlights.is_empty())
20395 }
20396
20397 /// Returns all background highlights for a given range.
20398 ///
20399 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20400 pub fn background_highlights_in_range(
20401 &self,
20402 search_range: Range<Anchor>,
20403 display_snapshot: &DisplaySnapshot,
20404 theme: &Theme,
20405 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20406 let mut results = Vec::new();
20407 for (color_fetcher, ranges) in self.background_highlights.values() {
20408 let color = color_fetcher(theme);
20409 let start_ix = match ranges.binary_search_by(|probe| {
20410 let cmp = probe
20411 .end
20412 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20413 if cmp.is_gt() {
20414 Ordering::Greater
20415 } else {
20416 Ordering::Less
20417 }
20418 }) {
20419 Ok(i) | Err(i) => i,
20420 };
20421 for range in &ranges[start_ix..] {
20422 if range
20423 .start
20424 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20425 .is_ge()
20426 {
20427 break;
20428 }
20429
20430 let start = range.start.to_display_point(display_snapshot);
20431 let end = range.end.to_display_point(display_snapshot);
20432 results.push((start..end, color))
20433 }
20434 }
20435 results
20436 }
20437
20438 pub fn gutter_highlights_in_range(
20439 &self,
20440 search_range: Range<Anchor>,
20441 display_snapshot: &DisplaySnapshot,
20442 cx: &App,
20443 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20444 let mut results = Vec::new();
20445 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20446 let color = color_fetcher(cx);
20447 let start_ix = match ranges.binary_search_by(|probe| {
20448 let cmp = probe
20449 .end
20450 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20451 if cmp.is_gt() {
20452 Ordering::Greater
20453 } else {
20454 Ordering::Less
20455 }
20456 }) {
20457 Ok(i) | Err(i) => i,
20458 };
20459 for range in &ranges[start_ix..] {
20460 if range
20461 .start
20462 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20463 .is_ge()
20464 {
20465 break;
20466 }
20467
20468 let start = range.start.to_display_point(display_snapshot);
20469 let end = range.end.to_display_point(display_snapshot);
20470 results.push((start..end, color))
20471 }
20472 }
20473 results
20474 }
20475
20476 /// Get the text ranges corresponding to the redaction query
20477 pub fn redacted_ranges(
20478 &self,
20479 search_range: Range<Anchor>,
20480 display_snapshot: &DisplaySnapshot,
20481 cx: &App,
20482 ) -> Vec<Range<DisplayPoint>> {
20483 display_snapshot
20484 .buffer_snapshot
20485 .redacted_ranges(search_range, |file| {
20486 if let Some(file) = file {
20487 file.is_private()
20488 && EditorSettings::get(
20489 Some(SettingsLocation {
20490 worktree_id: file.worktree_id(cx),
20491 path: file.path().as_ref(),
20492 }),
20493 cx,
20494 )
20495 .redact_private_values
20496 } else {
20497 false
20498 }
20499 })
20500 .map(|range| {
20501 range.start.to_display_point(display_snapshot)
20502 ..range.end.to_display_point(display_snapshot)
20503 })
20504 .collect()
20505 }
20506
20507 pub fn highlight_text_key<T: 'static>(
20508 &mut self,
20509 key: usize,
20510 ranges: Vec<Range<Anchor>>,
20511 style: HighlightStyle,
20512 cx: &mut Context<Self>,
20513 ) {
20514 self.display_map.update(cx, |map, _| {
20515 map.highlight_text(
20516 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20517 ranges,
20518 style,
20519 );
20520 });
20521 cx.notify();
20522 }
20523
20524 pub fn highlight_text<T: 'static>(
20525 &mut self,
20526 ranges: Vec<Range<Anchor>>,
20527 style: HighlightStyle,
20528 cx: &mut Context<Self>,
20529 ) {
20530 self.display_map.update(cx, |map, _| {
20531 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20532 });
20533 cx.notify();
20534 }
20535
20536 pub(crate) fn highlight_inlays<T: 'static>(
20537 &mut self,
20538 highlights: Vec<InlayHighlight>,
20539 style: HighlightStyle,
20540 cx: &mut Context<Self>,
20541 ) {
20542 self.display_map.update(cx, |map, _| {
20543 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20544 });
20545 cx.notify();
20546 }
20547
20548 pub fn text_highlights<'a, T: 'static>(
20549 &'a self,
20550 cx: &'a App,
20551 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20552 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20553 }
20554
20555 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20556 let cleared = self
20557 .display_map
20558 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20559 if cleared {
20560 cx.notify();
20561 }
20562 }
20563
20564 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20565 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20566 && self.focus_handle.is_focused(window)
20567 }
20568
20569 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20570 self.show_cursor_when_unfocused = is_enabled;
20571 cx.notify();
20572 }
20573
20574 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20575 cx.notify();
20576 }
20577
20578 fn on_debug_session_event(
20579 &mut self,
20580 _session: Entity<Session>,
20581 event: &SessionEvent,
20582 cx: &mut Context<Self>,
20583 ) {
20584 if let SessionEvent::InvalidateInlineValue = event {
20585 self.refresh_inline_values(cx);
20586 }
20587 }
20588
20589 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20590 let Some(project) = self.project.clone() else {
20591 return;
20592 };
20593
20594 if !self.inline_value_cache.enabled {
20595 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20596 self.splice_inlays(&inlays, Vec::new(), cx);
20597 return;
20598 }
20599
20600 let current_execution_position = self
20601 .highlighted_rows
20602 .get(&TypeId::of::<ActiveDebugLine>())
20603 .and_then(|lines| lines.last().map(|line| line.range.end));
20604
20605 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20606 let inline_values = editor
20607 .update(cx, |editor, cx| {
20608 let Some(current_execution_position) = current_execution_position else {
20609 return Some(Task::ready(Ok(Vec::new())));
20610 };
20611
20612 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20613 let snapshot = buffer.snapshot(cx);
20614
20615 let excerpt = snapshot.excerpt_containing(
20616 current_execution_position..current_execution_position,
20617 )?;
20618
20619 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20620 })?;
20621
20622 let range =
20623 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20624
20625 project.inline_values(buffer, range, cx)
20626 })
20627 .ok()
20628 .flatten()?
20629 .await
20630 .context("refreshing debugger inlays")
20631 .log_err()?;
20632
20633 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20634
20635 for (buffer_id, inline_value) in inline_values
20636 .into_iter()
20637 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20638 {
20639 buffer_inline_values
20640 .entry(buffer_id)
20641 .or_default()
20642 .push(inline_value);
20643 }
20644
20645 editor
20646 .update(cx, |editor, cx| {
20647 let snapshot = editor.buffer.read(cx).snapshot(cx);
20648 let mut new_inlays = Vec::default();
20649
20650 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20651 let buffer_id = buffer_snapshot.remote_id();
20652 buffer_inline_values
20653 .get(&buffer_id)
20654 .into_iter()
20655 .flatten()
20656 .for_each(|hint| {
20657 let inlay = Inlay::debugger(
20658 post_inc(&mut editor.next_inlay_id),
20659 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20660 hint.text(),
20661 );
20662 if !inlay.text().chars().contains(&'\n') {
20663 new_inlays.push(inlay);
20664 }
20665 });
20666 }
20667
20668 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20669 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20670
20671 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20672 })
20673 .ok()?;
20674 Some(())
20675 });
20676 }
20677
20678 fn on_buffer_event(
20679 &mut self,
20680 multibuffer: &Entity<MultiBuffer>,
20681 event: &multi_buffer::Event,
20682 window: &mut Window,
20683 cx: &mut Context<Self>,
20684 ) {
20685 match event {
20686 multi_buffer::Event::Edited {
20687 singleton_buffer_edited,
20688 edited_buffer,
20689 } => {
20690 self.scrollbar_marker_state.dirty = true;
20691 self.active_indent_guides_state.dirty = true;
20692 self.refresh_active_diagnostics(cx);
20693 self.refresh_code_actions(window, cx);
20694 self.refresh_selected_text_highlights(true, window, cx);
20695 self.refresh_single_line_folds(window, cx);
20696 refresh_matching_bracket_highlights(self, window, cx);
20697 if self.has_active_edit_prediction() {
20698 self.update_visible_edit_prediction(window, cx);
20699 }
20700 if let Some(project) = self.project.as_ref()
20701 && let Some(edited_buffer) = edited_buffer
20702 {
20703 project.update(cx, |project, cx| {
20704 self.registered_buffers
20705 .entry(edited_buffer.read(cx).remote_id())
20706 .or_insert_with(|| {
20707 project.register_buffer_with_language_servers(edited_buffer, cx)
20708 });
20709 });
20710 }
20711 cx.emit(EditorEvent::BufferEdited);
20712 cx.emit(SearchEvent::MatchesInvalidated);
20713
20714 if let Some(buffer) = edited_buffer {
20715 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20716 }
20717
20718 if *singleton_buffer_edited {
20719 if let Some(buffer) = edited_buffer
20720 && buffer.read(cx).file().is_none()
20721 {
20722 cx.emit(EditorEvent::TitleChanged);
20723 }
20724 if let Some(project) = &self.project {
20725 #[allow(clippy::mutable_key_type)]
20726 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20727 multibuffer
20728 .all_buffers()
20729 .into_iter()
20730 .filter_map(|buffer| {
20731 buffer.update(cx, |buffer, cx| {
20732 let language = buffer.language()?;
20733 let should_discard = project.update(cx, |project, cx| {
20734 project.is_local()
20735 && !project.has_language_servers_for(buffer, cx)
20736 });
20737 should_discard.not().then_some(language.clone())
20738 })
20739 })
20740 .collect::<HashSet<_>>()
20741 });
20742 if !languages_affected.is_empty() {
20743 self.refresh_inlay_hints(
20744 InlayHintRefreshReason::BufferEdited(languages_affected),
20745 cx,
20746 );
20747 }
20748 }
20749 }
20750
20751 let Some(project) = &self.project else { return };
20752 let (telemetry, is_via_ssh) = {
20753 let project = project.read(cx);
20754 let telemetry = project.client().telemetry().clone();
20755 let is_via_ssh = project.is_via_remote_server();
20756 (telemetry, is_via_ssh)
20757 };
20758 refresh_linked_ranges(self, window, cx);
20759 telemetry.log_edit_event("editor", is_via_ssh);
20760 }
20761 multi_buffer::Event::ExcerptsAdded {
20762 buffer,
20763 predecessor,
20764 excerpts,
20765 } => {
20766 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20767 let buffer_id = buffer.read(cx).remote_id();
20768 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20769 && let Some(project) = &self.project
20770 {
20771 update_uncommitted_diff_for_buffer(
20772 cx.entity(),
20773 project,
20774 [buffer.clone()],
20775 self.buffer.clone(),
20776 cx,
20777 )
20778 .detach();
20779 }
20780 if self.active_diagnostics != ActiveDiagnostic::All {
20781 self.update_lsp_data(false, Some(buffer_id), window, cx);
20782 }
20783 cx.emit(EditorEvent::ExcerptsAdded {
20784 buffer: buffer.clone(),
20785 predecessor: *predecessor,
20786 excerpts: excerpts.clone(),
20787 });
20788 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20789 }
20790 multi_buffer::Event::ExcerptsRemoved {
20791 ids,
20792 removed_buffer_ids,
20793 } => {
20794 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20795 let buffer = self.buffer.read(cx);
20796 self.registered_buffers
20797 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20798 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20799 cx.emit(EditorEvent::ExcerptsRemoved {
20800 ids: ids.clone(),
20801 removed_buffer_ids: removed_buffer_ids.clone(),
20802 });
20803 }
20804 multi_buffer::Event::ExcerptsEdited {
20805 excerpt_ids,
20806 buffer_ids,
20807 } => {
20808 self.display_map.update(cx, |map, cx| {
20809 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20810 });
20811 cx.emit(EditorEvent::ExcerptsEdited {
20812 ids: excerpt_ids.clone(),
20813 });
20814 }
20815 multi_buffer::Event::ExcerptsExpanded { ids } => {
20816 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20817 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20818 }
20819 multi_buffer::Event::Reparsed(buffer_id) => {
20820 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20821 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20822
20823 cx.emit(EditorEvent::Reparsed(*buffer_id));
20824 }
20825 multi_buffer::Event::DiffHunksToggled => {
20826 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20827 }
20828 multi_buffer::Event::LanguageChanged(buffer_id) => {
20829 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20830 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20831 cx.emit(EditorEvent::Reparsed(*buffer_id));
20832 cx.notify();
20833 }
20834 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20835 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20836 multi_buffer::Event::FileHandleChanged
20837 | multi_buffer::Event::Reloaded
20838 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20839 multi_buffer::Event::DiagnosticsUpdated => {
20840 self.update_diagnostics_state(window, cx);
20841 }
20842 _ => {}
20843 };
20844 }
20845
20846 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20847 if !self.diagnostics_enabled() {
20848 return;
20849 }
20850 self.refresh_active_diagnostics(cx);
20851 self.refresh_inline_diagnostics(true, window, cx);
20852 self.scrollbar_marker_state.dirty = true;
20853 cx.notify();
20854 }
20855
20856 pub fn start_temporary_diff_override(&mut self) {
20857 self.load_diff_task.take();
20858 self.temporary_diff_override = true;
20859 }
20860
20861 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20862 self.temporary_diff_override = false;
20863 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20864 self.buffer.update(cx, |buffer, cx| {
20865 buffer.set_all_diff_hunks_collapsed(cx);
20866 });
20867
20868 if let Some(project) = self.project.clone() {
20869 self.load_diff_task = Some(
20870 update_uncommitted_diff_for_buffer(
20871 cx.entity(),
20872 &project,
20873 self.buffer.read(cx).all_buffers(),
20874 self.buffer.clone(),
20875 cx,
20876 )
20877 .shared(),
20878 );
20879 }
20880 }
20881
20882 fn on_display_map_changed(
20883 &mut self,
20884 _: Entity<DisplayMap>,
20885 _: &mut Window,
20886 cx: &mut Context<Self>,
20887 ) {
20888 cx.notify();
20889 }
20890
20891 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20892 if self.diagnostics_enabled() {
20893 let new_severity = EditorSettings::get_global(cx)
20894 .diagnostics_max_severity
20895 .unwrap_or(DiagnosticSeverity::Hint);
20896 self.set_max_diagnostics_severity(new_severity, cx);
20897 }
20898 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20899 self.update_edit_prediction_settings(cx);
20900 self.refresh_edit_prediction(true, false, window, cx);
20901 self.refresh_inline_values(cx);
20902 self.refresh_inlay_hints(
20903 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20904 self.selections.newest_anchor().head(),
20905 &self.buffer.read(cx).snapshot(cx),
20906 cx,
20907 )),
20908 cx,
20909 );
20910
20911 let old_cursor_shape = self.cursor_shape;
20912 let old_show_breadcrumbs = self.show_breadcrumbs;
20913
20914 {
20915 let editor_settings = EditorSettings::get_global(cx);
20916 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20917 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20918 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20919 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20920 }
20921
20922 if old_cursor_shape != self.cursor_shape {
20923 cx.emit(EditorEvent::CursorShapeChanged);
20924 }
20925
20926 if old_show_breadcrumbs != self.show_breadcrumbs {
20927 cx.emit(EditorEvent::BreadcrumbsChanged);
20928 }
20929
20930 let project_settings = ProjectSettings::get_global(cx);
20931 self.serialize_dirty_buffers =
20932 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20933
20934 if self.mode.is_full() {
20935 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20936 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20937 if self.show_inline_diagnostics != show_inline_diagnostics {
20938 self.show_inline_diagnostics = show_inline_diagnostics;
20939 self.refresh_inline_diagnostics(false, window, cx);
20940 }
20941
20942 if self.git_blame_inline_enabled != inline_blame_enabled {
20943 self.toggle_git_blame_inline_internal(false, window, cx);
20944 }
20945
20946 let minimap_settings = EditorSettings::get_global(cx).minimap;
20947 if self.minimap_visibility != MinimapVisibility::Disabled {
20948 if self.minimap_visibility.settings_visibility()
20949 != minimap_settings.minimap_enabled()
20950 {
20951 self.set_minimap_visibility(
20952 MinimapVisibility::for_mode(self.mode(), cx),
20953 window,
20954 cx,
20955 );
20956 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20957 minimap_entity.update(cx, |minimap_editor, cx| {
20958 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20959 })
20960 }
20961 }
20962 }
20963
20964 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20965 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20966 }) {
20967 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20968 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20969 }
20970 self.refresh_colors(false, None, window, cx);
20971 }
20972
20973 cx.notify();
20974 }
20975
20976 pub fn set_searchable(&mut self, searchable: bool) {
20977 self.searchable = searchable;
20978 }
20979
20980 pub fn searchable(&self) -> bool {
20981 self.searchable
20982 }
20983
20984 fn open_proposed_changes_editor(
20985 &mut self,
20986 _: &OpenProposedChangesEditor,
20987 window: &mut Window,
20988 cx: &mut Context<Self>,
20989 ) {
20990 let Some(workspace) = self.workspace() else {
20991 cx.propagate();
20992 return;
20993 };
20994
20995 let selections = self.selections.all::<usize>(cx);
20996 let multi_buffer = self.buffer.read(cx);
20997 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20998 let mut new_selections_by_buffer = HashMap::default();
20999 for selection in selections {
21000 for (buffer, range, _) in
21001 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21002 {
21003 let mut range = range.to_point(buffer);
21004 range.start.column = 0;
21005 range.end.column = buffer.line_len(range.end.row);
21006 new_selections_by_buffer
21007 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21008 .or_insert(Vec::new())
21009 .push(range)
21010 }
21011 }
21012
21013 let proposed_changes_buffers = new_selections_by_buffer
21014 .into_iter()
21015 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21016 .collect::<Vec<_>>();
21017 let proposed_changes_editor = cx.new(|cx| {
21018 ProposedChangesEditor::new(
21019 "Proposed changes",
21020 proposed_changes_buffers,
21021 self.project.clone(),
21022 window,
21023 cx,
21024 )
21025 });
21026
21027 window.defer(cx, move |window, cx| {
21028 workspace.update(cx, |workspace, cx| {
21029 workspace.active_pane().update(cx, |pane, cx| {
21030 pane.add_item(
21031 Box::new(proposed_changes_editor),
21032 true,
21033 true,
21034 None,
21035 window,
21036 cx,
21037 );
21038 });
21039 });
21040 });
21041 }
21042
21043 pub fn open_excerpts_in_split(
21044 &mut self,
21045 _: &OpenExcerptsSplit,
21046 window: &mut Window,
21047 cx: &mut Context<Self>,
21048 ) {
21049 self.open_excerpts_common(None, true, window, cx)
21050 }
21051
21052 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21053 self.open_excerpts_common(None, false, window, cx)
21054 }
21055
21056 fn open_excerpts_common(
21057 &mut self,
21058 jump_data: Option<JumpData>,
21059 split: bool,
21060 window: &mut Window,
21061 cx: &mut Context<Self>,
21062 ) {
21063 let Some(workspace) = self.workspace() else {
21064 cx.propagate();
21065 return;
21066 };
21067
21068 if self.buffer.read(cx).is_singleton() {
21069 cx.propagate();
21070 return;
21071 }
21072
21073 let mut new_selections_by_buffer = HashMap::default();
21074 match &jump_data {
21075 Some(JumpData::MultiBufferPoint {
21076 excerpt_id,
21077 position,
21078 anchor,
21079 line_offset_from_top,
21080 }) => {
21081 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21082 if let Some(buffer) = multi_buffer_snapshot
21083 .buffer_id_for_excerpt(*excerpt_id)
21084 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21085 {
21086 let buffer_snapshot = buffer.read(cx).snapshot();
21087 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21088 language::ToPoint::to_point(anchor, &buffer_snapshot)
21089 } else {
21090 buffer_snapshot.clip_point(*position, Bias::Left)
21091 };
21092 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21093 new_selections_by_buffer.insert(
21094 buffer,
21095 (
21096 vec![jump_to_offset..jump_to_offset],
21097 Some(*line_offset_from_top),
21098 ),
21099 );
21100 }
21101 }
21102 Some(JumpData::MultiBufferRow {
21103 row,
21104 line_offset_from_top,
21105 }) => {
21106 let point = MultiBufferPoint::new(row.0, 0);
21107 if let Some((buffer, buffer_point, _)) =
21108 self.buffer.read(cx).point_to_buffer_point(point, cx)
21109 {
21110 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21111 new_selections_by_buffer
21112 .entry(buffer)
21113 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21114 .0
21115 .push(buffer_offset..buffer_offset)
21116 }
21117 }
21118 None => {
21119 let selections = self.selections.all::<usize>(cx);
21120 let multi_buffer = self.buffer.read(cx);
21121 for selection in selections {
21122 for (snapshot, range, _, anchor) in multi_buffer
21123 .snapshot(cx)
21124 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21125 {
21126 if let Some(anchor) = anchor {
21127 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21128 else {
21129 continue;
21130 };
21131 let offset = text::ToOffset::to_offset(
21132 &anchor.text_anchor,
21133 &buffer_handle.read(cx).snapshot(),
21134 );
21135 let range = offset..offset;
21136 new_selections_by_buffer
21137 .entry(buffer_handle)
21138 .or_insert((Vec::new(), None))
21139 .0
21140 .push(range)
21141 } else {
21142 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21143 else {
21144 continue;
21145 };
21146 new_selections_by_buffer
21147 .entry(buffer_handle)
21148 .or_insert((Vec::new(), None))
21149 .0
21150 .push(range)
21151 }
21152 }
21153 }
21154 }
21155 }
21156
21157 new_selections_by_buffer
21158 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21159
21160 if new_selections_by_buffer.is_empty() {
21161 return;
21162 }
21163
21164 // We defer the pane interaction because we ourselves are a workspace item
21165 // and activating a new item causes the pane to call a method on us reentrantly,
21166 // which panics if we're on the stack.
21167 window.defer(cx, move |window, cx| {
21168 workspace.update(cx, |workspace, cx| {
21169 let pane = if split {
21170 workspace.adjacent_pane(window, cx)
21171 } else {
21172 workspace.active_pane().clone()
21173 };
21174
21175 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21176 let editor = buffer
21177 .read(cx)
21178 .file()
21179 .is_none()
21180 .then(|| {
21181 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21182 // so `workspace.open_project_item` will never find them, always opening a new editor.
21183 // Instead, we try to activate the existing editor in the pane first.
21184 let (editor, pane_item_index) =
21185 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21186 let editor = item.downcast::<Editor>()?;
21187 let singleton_buffer =
21188 editor.read(cx).buffer().read(cx).as_singleton()?;
21189 if singleton_buffer == buffer {
21190 Some((editor, i))
21191 } else {
21192 None
21193 }
21194 })?;
21195 pane.update(cx, |pane, cx| {
21196 pane.activate_item(pane_item_index, true, true, window, cx)
21197 });
21198 Some(editor)
21199 })
21200 .flatten()
21201 .unwrap_or_else(|| {
21202 workspace.open_project_item::<Self>(
21203 pane.clone(),
21204 buffer,
21205 true,
21206 true,
21207 window,
21208 cx,
21209 )
21210 });
21211
21212 editor.update(cx, |editor, cx| {
21213 let autoscroll = match scroll_offset {
21214 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21215 None => Autoscroll::newest(),
21216 };
21217 let nav_history = editor.nav_history.take();
21218 editor.change_selections(
21219 SelectionEffects::scroll(autoscroll),
21220 window,
21221 cx,
21222 |s| {
21223 s.select_ranges(ranges);
21224 },
21225 );
21226 editor.nav_history = nav_history;
21227 });
21228 }
21229 })
21230 });
21231 }
21232
21233 // For now, don't allow opening excerpts in buffers that aren't backed by
21234 // regular project files.
21235 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21236 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21237 }
21238
21239 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21240 let snapshot = self.buffer.read(cx).read(cx);
21241 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21242 Some(
21243 ranges
21244 .iter()
21245 .map(move |range| {
21246 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21247 })
21248 .collect(),
21249 )
21250 }
21251
21252 fn selection_replacement_ranges(
21253 &self,
21254 range: Range<OffsetUtf16>,
21255 cx: &mut App,
21256 ) -> Vec<Range<OffsetUtf16>> {
21257 let selections = self.selections.all::<OffsetUtf16>(cx);
21258 let newest_selection = selections
21259 .iter()
21260 .max_by_key(|selection| selection.id)
21261 .unwrap();
21262 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21263 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21264 let snapshot = self.buffer.read(cx).read(cx);
21265 selections
21266 .into_iter()
21267 .map(|mut selection| {
21268 selection.start.0 =
21269 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21270 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21271 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21272 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21273 })
21274 .collect()
21275 }
21276
21277 fn report_editor_event(
21278 &self,
21279 reported_event: ReportEditorEvent,
21280 file_extension: Option<String>,
21281 cx: &App,
21282 ) {
21283 if cfg!(any(test, feature = "test-support")) {
21284 return;
21285 }
21286
21287 let Some(project) = &self.project else { return };
21288
21289 // If None, we are in a file without an extension
21290 let file = self
21291 .buffer
21292 .read(cx)
21293 .as_singleton()
21294 .and_then(|b| b.read(cx).file());
21295 let file_extension = file_extension.or(file
21296 .as_ref()
21297 .and_then(|file| Path::new(file.file_name(cx)).extension())
21298 .and_then(|e| e.to_str())
21299 .map(|a| a.to_string()));
21300
21301 let vim_mode = vim_enabled(cx);
21302
21303 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21304 let copilot_enabled = edit_predictions_provider
21305 == language::language_settings::EditPredictionProvider::Copilot;
21306 let copilot_enabled_for_language = self
21307 .buffer
21308 .read(cx)
21309 .language_settings(cx)
21310 .show_edit_predictions;
21311
21312 let project = project.read(cx);
21313 let event_type = reported_event.event_type();
21314
21315 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21316 telemetry::event!(
21317 event_type,
21318 type = if auto_saved {"autosave"} else {"manual"},
21319 file_extension,
21320 vim_mode,
21321 copilot_enabled,
21322 copilot_enabled_for_language,
21323 edit_predictions_provider,
21324 is_via_ssh = project.is_via_remote_server(),
21325 );
21326 } else {
21327 telemetry::event!(
21328 event_type,
21329 file_extension,
21330 vim_mode,
21331 copilot_enabled,
21332 copilot_enabled_for_language,
21333 edit_predictions_provider,
21334 is_via_ssh = project.is_via_remote_server(),
21335 );
21336 };
21337 }
21338
21339 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21340 /// with each line being an array of {text, highlight} objects.
21341 fn copy_highlight_json(
21342 &mut self,
21343 _: &CopyHighlightJson,
21344 window: &mut Window,
21345 cx: &mut Context<Self>,
21346 ) {
21347 #[derive(Serialize)]
21348 struct Chunk<'a> {
21349 text: String,
21350 highlight: Option<&'a str>,
21351 }
21352
21353 let snapshot = self.buffer.read(cx).snapshot(cx);
21354 let range = self
21355 .selected_text_range(false, window, cx)
21356 .and_then(|selection| {
21357 if selection.range.is_empty() {
21358 None
21359 } else {
21360 Some(selection.range)
21361 }
21362 })
21363 .unwrap_or_else(|| 0..snapshot.len());
21364
21365 let chunks = snapshot.chunks(range, true);
21366 let mut lines = Vec::new();
21367 let mut line: VecDeque<Chunk> = VecDeque::new();
21368
21369 let Some(style) = self.style.as_ref() else {
21370 return;
21371 };
21372
21373 for chunk in chunks {
21374 let highlight = chunk
21375 .syntax_highlight_id
21376 .and_then(|id| id.name(&style.syntax));
21377 let mut chunk_lines = chunk.text.split('\n').peekable();
21378 while let Some(text) = chunk_lines.next() {
21379 let mut merged_with_last_token = false;
21380 if let Some(last_token) = line.back_mut()
21381 && last_token.highlight == highlight
21382 {
21383 last_token.text.push_str(text);
21384 merged_with_last_token = true;
21385 }
21386
21387 if !merged_with_last_token {
21388 line.push_back(Chunk {
21389 text: text.into(),
21390 highlight,
21391 });
21392 }
21393
21394 if chunk_lines.peek().is_some() {
21395 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21396 line.pop_front();
21397 }
21398 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21399 line.pop_back();
21400 }
21401
21402 lines.push(mem::take(&mut line));
21403 }
21404 }
21405 }
21406
21407 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21408 return;
21409 };
21410 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21411 }
21412
21413 pub fn open_context_menu(
21414 &mut self,
21415 _: &OpenContextMenu,
21416 window: &mut Window,
21417 cx: &mut Context<Self>,
21418 ) {
21419 self.request_autoscroll(Autoscroll::newest(), cx);
21420 let position = self.selections.newest_display(cx).start;
21421 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21422 }
21423
21424 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21425 &self.inlay_hint_cache
21426 }
21427
21428 pub fn replay_insert_event(
21429 &mut self,
21430 text: &str,
21431 relative_utf16_range: Option<Range<isize>>,
21432 window: &mut Window,
21433 cx: &mut Context<Self>,
21434 ) {
21435 if !self.input_enabled {
21436 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21437 return;
21438 }
21439 if let Some(relative_utf16_range) = relative_utf16_range {
21440 let selections = self.selections.all::<OffsetUtf16>(cx);
21441 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21442 let new_ranges = selections.into_iter().map(|range| {
21443 let start = OffsetUtf16(
21444 range
21445 .head()
21446 .0
21447 .saturating_add_signed(relative_utf16_range.start),
21448 );
21449 let end = OffsetUtf16(
21450 range
21451 .head()
21452 .0
21453 .saturating_add_signed(relative_utf16_range.end),
21454 );
21455 start..end
21456 });
21457 s.select_ranges(new_ranges);
21458 });
21459 }
21460
21461 self.handle_input(text, window, cx);
21462 }
21463
21464 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21465 let Some(provider) = self.semantics_provider.as_ref() else {
21466 return false;
21467 };
21468
21469 let mut supports = false;
21470 self.buffer().update(cx, |this, cx| {
21471 this.for_each_buffer(|buffer| {
21472 supports |= provider.supports_inlay_hints(buffer, cx);
21473 });
21474 });
21475
21476 supports
21477 }
21478
21479 pub fn is_focused(&self, window: &Window) -> bool {
21480 self.focus_handle.is_focused(window)
21481 }
21482
21483 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21484 cx.emit(EditorEvent::Focused);
21485
21486 if let Some(descendant) = self
21487 .last_focused_descendant
21488 .take()
21489 .and_then(|descendant| descendant.upgrade())
21490 {
21491 window.focus(&descendant);
21492 } else {
21493 if let Some(blame) = self.blame.as_ref() {
21494 blame.update(cx, GitBlame::focus)
21495 }
21496
21497 self.blink_manager.update(cx, BlinkManager::enable);
21498 self.show_cursor_names(window, cx);
21499 self.buffer.update(cx, |buffer, cx| {
21500 buffer.finalize_last_transaction(cx);
21501 if self.leader_id.is_none() {
21502 buffer.set_active_selections(
21503 &self.selections.disjoint_anchors_arc(),
21504 self.selections.line_mode(),
21505 self.cursor_shape,
21506 cx,
21507 );
21508 }
21509 });
21510 }
21511 }
21512
21513 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21514 cx.emit(EditorEvent::FocusedIn)
21515 }
21516
21517 fn handle_focus_out(
21518 &mut self,
21519 event: FocusOutEvent,
21520 _window: &mut Window,
21521 cx: &mut Context<Self>,
21522 ) {
21523 if event.blurred != self.focus_handle {
21524 self.last_focused_descendant = Some(event.blurred);
21525 }
21526 self.selection_drag_state = SelectionDragState::None;
21527 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21528 }
21529
21530 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21531 self.blink_manager.update(cx, BlinkManager::disable);
21532 self.buffer
21533 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21534
21535 if let Some(blame) = self.blame.as_ref() {
21536 blame.update(cx, GitBlame::blur)
21537 }
21538 if !self.hover_state.focused(window, cx) {
21539 hide_hover(self, cx);
21540 }
21541 if !self
21542 .context_menu
21543 .borrow()
21544 .as_ref()
21545 .is_some_and(|context_menu| context_menu.focused(window, cx))
21546 {
21547 self.hide_context_menu(window, cx);
21548 }
21549 self.take_active_edit_prediction(cx);
21550 cx.emit(EditorEvent::Blurred);
21551 cx.notify();
21552 }
21553
21554 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21555 let mut pending: String = window
21556 .pending_input_keystrokes()
21557 .into_iter()
21558 .flatten()
21559 .filter_map(|keystroke| {
21560 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21561 keystroke.key_char.clone()
21562 } else {
21563 None
21564 }
21565 })
21566 .collect();
21567
21568 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21569 pending = "".to_string();
21570 }
21571
21572 let existing_pending = self
21573 .text_highlights::<PendingInput>(cx)
21574 .map(|(_, ranges)| ranges.to_vec());
21575 if existing_pending.is_none() && pending.is_empty() {
21576 return;
21577 }
21578 let transaction =
21579 self.transact(window, cx, |this, window, cx| {
21580 let selections = this.selections.all::<usize>(cx);
21581 let edits = selections
21582 .iter()
21583 .map(|selection| (selection.end..selection.end, pending.clone()));
21584 this.edit(edits, cx);
21585 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21586 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21587 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21588 }));
21589 });
21590 if let Some(existing_ranges) = existing_pending {
21591 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21592 this.edit(edits, cx);
21593 }
21594 });
21595
21596 let snapshot = self.snapshot(window, cx);
21597 let ranges = self
21598 .selections
21599 .all::<usize>(cx)
21600 .into_iter()
21601 .map(|selection| {
21602 snapshot.buffer_snapshot.anchor_after(selection.end)
21603 ..snapshot
21604 .buffer_snapshot
21605 .anchor_before(selection.end + pending.len())
21606 })
21607 .collect();
21608
21609 if pending.is_empty() {
21610 self.clear_highlights::<PendingInput>(cx);
21611 } else {
21612 self.highlight_text::<PendingInput>(
21613 ranges,
21614 HighlightStyle {
21615 underline: Some(UnderlineStyle {
21616 thickness: px(1.),
21617 color: None,
21618 wavy: false,
21619 }),
21620 ..Default::default()
21621 },
21622 cx,
21623 );
21624 }
21625
21626 self.ime_transaction = self.ime_transaction.or(transaction);
21627 if let Some(transaction) = self.ime_transaction {
21628 self.buffer.update(cx, |buffer, cx| {
21629 buffer.group_until_transaction(transaction, cx);
21630 });
21631 }
21632
21633 if self.text_highlights::<PendingInput>(cx).is_none() {
21634 self.ime_transaction.take();
21635 }
21636 }
21637
21638 pub fn register_action_renderer(
21639 &mut self,
21640 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21641 ) -> Subscription {
21642 let id = self.next_editor_action_id.post_inc();
21643 self.editor_actions
21644 .borrow_mut()
21645 .insert(id, Box::new(listener));
21646
21647 let editor_actions = self.editor_actions.clone();
21648 Subscription::new(move || {
21649 editor_actions.borrow_mut().remove(&id);
21650 })
21651 }
21652
21653 pub fn register_action<A: Action>(
21654 &mut self,
21655 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21656 ) -> Subscription {
21657 let id = self.next_editor_action_id.post_inc();
21658 let listener = Arc::new(listener);
21659 self.editor_actions.borrow_mut().insert(
21660 id,
21661 Box::new(move |_, window, _| {
21662 let listener = listener.clone();
21663 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21664 let action = action.downcast_ref().unwrap();
21665 if phase == DispatchPhase::Bubble {
21666 listener(action, window, cx)
21667 }
21668 })
21669 }),
21670 );
21671
21672 let editor_actions = self.editor_actions.clone();
21673 Subscription::new(move || {
21674 editor_actions.borrow_mut().remove(&id);
21675 })
21676 }
21677
21678 pub fn file_header_size(&self) -> u32 {
21679 FILE_HEADER_HEIGHT
21680 }
21681
21682 pub fn restore(
21683 &mut self,
21684 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21685 window: &mut Window,
21686 cx: &mut Context<Self>,
21687 ) {
21688 let workspace = self.workspace();
21689 let project = self.project();
21690 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21691 let mut tasks = Vec::new();
21692 for (buffer_id, changes) in revert_changes {
21693 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21694 buffer.update(cx, |buffer, cx| {
21695 buffer.edit(
21696 changes
21697 .into_iter()
21698 .map(|(range, text)| (range, text.to_string())),
21699 None,
21700 cx,
21701 );
21702 });
21703
21704 if let Some(project) =
21705 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21706 {
21707 project.update(cx, |project, cx| {
21708 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21709 })
21710 }
21711 }
21712 }
21713 tasks
21714 });
21715 cx.spawn_in(window, async move |_, cx| {
21716 for (buffer, task) in save_tasks {
21717 let result = task.await;
21718 if result.is_err() {
21719 let Some(path) = buffer
21720 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21721 .ok()
21722 else {
21723 continue;
21724 };
21725 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21726 let Some(task) = cx
21727 .update_window_entity(workspace, |workspace, window, cx| {
21728 workspace
21729 .open_path_preview(path, None, false, false, false, window, cx)
21730 })
21731 .ok()
21732 else {
21733 continue;
21734 };
21735 task.await.log_err();
21736 }
21737 }
21738 }
21739 })
21740 .detach();
21741 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21742 selections.refresh()
21743 });
21744 }
21745
21746 pub fn to_pixel_point(
21747 &self,
21748 source: multi_buffer::Anchor,
21749 editor_snapshot: &EditorSnapshot,
21750 window: &mut Window,
21751 ) -> Option<gpui::Point<Pixels>> {
21752 let source_point = source.to_display_point(editor_snapshot);
21753 self.display_to_pixel_point(source_point, editor_snapshot, window)
21754 }
21755
21756 pub fn display_to_pixel_point(
21757 &self,
21758 source: DisplayPoint,
21759 editor_snapshot: &EditorSnapshot,
21760 window: &mut Window,
21761 ) -> Option<gpui::Point<Pixels>> {
21762 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21763 let text_layout_details = self.text_layout_details(window);
21764 let scroll_top = text_layout_details
21765 .scroll_anchor
21766 .scroll_position(editor_snapshot)
21767 .y;
21768
21769 if source.row().as_f64() < scroll_top.floor() {
21770 return None;
21771 }
21772 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21773 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21774 Some(gpui::Point::new(source_x, source_y))
21775 }
21776
21777 pub fn has_visible_completions_menu(&self) -> bool {
21778 !self.edit_prediction_preview_is_active()
21779 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21780 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21781 })
21782 }
21783
21784 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21785 if self.mode.is_minimap() {
21786 return;
21787 }
21788 self.addons
21789 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21790 }
21791
21792 pub fn unregister_addon<T: Addon>(&mut self) {
21793 self.addons.remove(&std::any::TypeId::of::<T>());
21794 }
21795
21796 pub fn addon<T: Addon>(&self) -> Option<&T> {
21797 let type_id = std::any::TypeId::of::<T>();
21798 self.addons
21799 .get(&type_id)
21800 .and_then(|item| item.to_any().downcast_ref::<T>())
21801 }
21802
21803 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21804 let type_id = std::any::TypeId::of::<T>();
21805 self.addons
21806 .get_mut(&type_id)
21807 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21808 }
21809
21810 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21811 let text_layout_details = self.text_layout_details(window);
21812 let style = &text_layout_details.editor_style;
21813 let font_id = window.text_system().resolve_font(&style.text.font());
21814 let font_size = style.text.font_size.to_pixels(window.rem_size());
21815 let line_height = style.text.line_height_in_pixels(window.rem_size());
21816 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21817 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21818
21819 CharacterDimensions {
21820 em_width,
21821 em_advance,
21822 line_height,
21823 }
21824 }
21825
21826 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21827 self.load_diff_task.clone()
21828 }
21829
21830 fn read_metadata_from_db(
21831 &mut self,
21832 item_id: u64,
21833 workspace_id: WorkspaceId,
21834 window: &mut Window,
21835 cx: &mut Context<Editor>,
21836 ) {
21837 if self.is_singleton(cx)
21838 && !self.mode.is_minimap()
21839 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21840 {
21841 let buffer_snapshot = OnceCell::new();
21842
21843 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21844 && !folds.is_empty()
21845 {
21846 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21847 self.fold_ranges(
21848 folds
21849 .into_iter()
21850 .map(|(start, end)| {
21851 snapshot.clip_offset(start, Bias::Left)
21852 ..snapshot.clip_offset(end, Bias::Right)
21853 })
21854 .collect(),
21855 false,
21856 window,
21857 cx,
21858 );
21859 }
21860
21861 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21862 && !selections.is_empty()
21863 {
21864 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21865 // skip adding the initial selection to selection history
21866 self.selection_history.mode = SelectionHistoryMode::Skipping;
21867 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21868 s.select_ranges(selections.into_iter().map(|(start, end)| {
21869 snapshot.clip_offset(start, Bias::Left)
21870 ..snapshot.clip_offset(end, Bias::Right)
21871 }));
21872 });
21873 self.selection_history.mode = SelectionHistoryMode::Normal;
21874 };
21875 }
21876
21877 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21878 }
21879
21880 fn update_lsp_data(
21881 &mut self,
21882 ignore_cache: bool,
21883 for_buffer: Option<BufferId>,
21884 window: &mut Window,
21885 cx: &mut Context<'_, Self>,
21886 ) {
21887 self.pull_diagnostics(for_buffer, window, cx);
21888 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21889 }
21890}
21891
21892fn edit_for_markdown_paste<'a>(
21893 buffer: &MultiBufferSnapshot,
21894 range: Range<usize>,
21895 to_insert: &'a str,
21896 url: Option<url::Url>,
21897) -> (Range<usize>, Cow<'a, str>) {
21898 if url.is_none() {
21899 return (range, Cow::Borrowed(to_insert));
21900 };
21901
21902 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
21903
21904 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
21905 Cow::Borrowed(to_insert)
21906 } else {
21907 Cow::Owned(format!("[{old_text}]({to_insert})"))
21908 };
21909 (range, new_text)
21910}
21911
21912fn vim_enabled(cx: &App) -> bool {
21913 vim_mode_setting::VimModeSetting::try_get(cx)
21914 .map(|vim_mode| vim_mode.0)
21915 .unwrap_or(false)
21916}
21917
21918fn process_completion_for_edit(
21919 completion: &Completion,
21920 intent: CompletionIntent,
21921 buffer: &Entity<Buffer>,
21922 cursor_position: &text::Anchor,
21923 cx: &mut Context<Editor>,
21924) -> CompletionEdit {
21925 let buffer = buffer.read(cx);
21926 let buffer_snapshot = buffer.snapshot();
21927 let (snippet, new_text) = if completion.is_snippet() {
21928 // Workaround for typescript language server issues so that methods don't expand within
21929 // strings and functions with type expressions. The previous point is used because the query
21930 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21931 let mut snippet_source = completion.new_text.clone();
21932 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21933 previous_point.column = previous_point.column.saturating_sub(1);
21934 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21935 && scope.prefers_label_for_snippet_in_completion()
21936 && let Some(label) = completion.label()
21937 && matches!(
21938 completion.kind(),
21939 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21940 )
21941 {
21942 snippet_source = label;
21943 }
21944 match Snippet::parse(&snippet_source).log_err() {
21945 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21946 None => (None, completion.new_text.clone()),
21947 }
21948 } else {
21949 (None, completion.new_text.clone())
21950 };
21951
21952 let mut range_to_replace = {
21953 let replace_range = &completion.replace_range;
21954 if let CompletionSource::Lsp {
21955 insert_range: Some(insert_range),
21956 ..
21957 } = &completion.source
21958 {
21959 debug_assert_eq!(
21960 insert_range.start, replace_range.start,
21961 "insert_range and replace_range should start at the same position"
21962 );
21963 debug_assert!(
21964 insert_range
21965 .start
21966 .cmp(cursor_position, &buffer_snapshot)
21967 .is_le(),
21968 "insert_range should start before or at cursor position"
21969 );
21970 debug_assert!(
21971 replace_range
21972 .start
21973 .cmp(cursor_position, &buffer_snapshot)
21974 .is_le(),
21975 "replace_range should start before or at cursor position"
21976 );
21977
21978 let should_replace = match intent {
21979 CompletionIntent::CompleteWithInsert => false,
21980 CompletionIntent::CompleteWithReplace => true,
21981 CompletionIntent::Complete | CompletionIntent::Compose => {
21982 let insert_mode =
21983 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21984 .completions
21985 .lsp_insert_mode;
21986 match insert_mode {
21987 LspInsertMode::Insert => false,
21988 LspInsertMode::Replace => true,
21989 LspInsertMode::ReplaceSubsequence => {
21990 let mut text_to_replace = buffer.chars_for_range(
21991 buffer.anchor_before(replace_range.start)
21992 ..buffer.anchor_after(replace_range.end),
21993 );
21994 let mut current_needle = text_to_replace.next();
21995 for haystack_ch in completion.label.text.chars() {
21996 if let Some(needle_ch) = current_needle
21997 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21998 {
21999 current_needle = text_to_replace.next();
22000 }
22001 }
22002 current_needle.is_none()
22003 }
22004 LspInsertMode::ReplaceSuffix => {
22005 if replace_range
22006 .end
22007 .cmp(cursor_position, &buffer_snapshot)
22008 .is_gt()
22009 {
22010 let range_after_cursor = *cursor_position..replace_range.end;
22011 let text_after_cursor = buffer
22012 .text_for_range(
22013 buffer.anchor_before(range_after_cursor.start)
22014 ..buffer.anchor_after(range_after_cursor.end),
22015 )
22016 .collect::<String>()
22017 .to_ascii_lowercase();
22018 completion
22019 .label
22020 .text
22021 .to_ascii_lowercase()
22022 .ends_with(&text_after_cursor)
22023 } else {
22024 true
22025 }
22026 }
22027 }
22028 }
22029 };
22030
22031 if should_replace {
22032 replace_range.clone()
22033 } else {
22034 insert_range.clone()
22035 }
22036 } else {
22037 replace_range.clone()
22038 }
22039 };
22040
22041 if range_to_replace
22042 .end
22043 .cmp(cursor_position, &buffer_snapshot)
22044 .is_lt()
22045 {
22046 range_to_replace.end = *cursor_position;
22047 }
22048
22049 CompletionEdit {
22050 new_text,
22051 replace_range: range_to_replace.to_offset(buffer),
22052 snippet,
22053 }
22054}
22055
22056struct CompletionEdit {
22057 new_text: String,
22058 replace_range: Range<usize>,
22059 snippet: Option<Snippet>,
22060}
22061
22062fn insert_extra_newline_brackets(
22063 buffer: &MultiBufferSnapshot,
22064 range: Range<usize>,
22065 language: &language::LanguageScope,
22066) -> bool {
22067 let leading_whitespace_len = buffer
22068 .reversed_chars_at(range.start)
22069 .take_while(|c| c.is_whitespace() && *c != '\n')
22070 .map(|c| c.len_utf8())
22071 .sum::<usize>();
22072 let trailing_whitespace_len = buffer
22073 .chars_at(range.end)
22074 .take_while(|c| c.is_whitespace() && *c != '\n')
22075 .map(|c| c.len_utf8())
22076 .sum::<usize>();
22077 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22078
22079 language.brackets().any(|(pair, enabled)| {
22080 let pair_start = pair.start.trim_end();
22081 let pair_end = pair.end.trim_start();
22082
22083 enabled
22084 && pair.newline
22085 && buffer.contains_str_at(range.end, pair_end)
22086 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22087 })
22088}
22089
22090fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22091 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22092 [(buffer, range, _)] => (*buffer, range.clone()),
22093 _ => return false,
22094 };
22095 let pair = {
22096 let mut result: Option<BracketMatch> = None;
22097
22098 for pair in buffer
22099 .all_bracket_ranges(range.clone())
22100 .filter(move |pair| {
22101 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22102 })
22103 {
22104 let len = pair.close_range.end - pair.open_range.start;
22105
22106 if let Some(existing) = &result {
22107 let existing_len = existing.close_range.end - existing.open_range.start;
22108 if len > existing_len {
22109 continue;
22110 }
22111 }
22112
22113 result = Some(pair);
22114 }
22115
22116 result
22117 };
22118 let Some(pair) = pair else {
22119 return false;
22120 };
22121 pair.newline_only
22122 && buffer
22123 .chars_for_range(pair.open_range.end..range.start)
22124 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22125 .all(|c| c.is_whitespace() && c != '\n')
22126}
22127
22128fn update_uncommitted_diff_for_buffer(
22129 editor: Entity<Editor>,
22130 project: &Entity<Project>,
22131 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22132 buffer: Entity<MultiBuffer>,
22133 cx: &mut App,
22134) -> Task<()> {
22135 let mut tasks = Vec::new();
22136 project.update(cx, |project, cx| {
22137 for buffer in buffers {
22138 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22139 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22140 }
22141 }
22142 });
22143 cx.spawn(async move |cx| {
22144 let diffs = future::join_all(tasks).await;
22145 if editor
22146 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22147 .unwrap_or(false)
22148 {
22149 return;
22150 }
22151
22152 buffer
22153 .update(cx, |buffer, cx| {
22154 for diff in diffs.into_iter().flatten() {
22155 buffer.add_diff(diff, cx);
22156 }
22157 })
22158 .ok();
22159 })
22160}
22161
22162fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22163 let tab_size = tab_size.get() as usize;
22164 let mut width = offset;
22165
22166 for ch in text.chars() {
22167 width += if ch == '\t' {
22168 tab_size - (width % tab_size)
22169 } else {
22170 1
22171 };
22172 }
22173
22174 width - offset
22175}
22176
22177#[cfg(test)]
22178mod tests {
22179 use super::*;
22180
22181 #[test]
22182 fn test_string_size_with_expanded_tabs() {
22183 let nz = |val| NonZeroU32::new(val).unwrap();
22184 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22185 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22186 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22187 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22188 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22189 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22190 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22191 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22192 }
22193}
22194
22195/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22196struct WordBreakingTokenizer<'a> {
22197 input: &'a str,
22198}
22199
22200impl<'a> WordBreakingTokenizer<'a> {
22201 fn new(input: &'a str) -> Self {
22202 Self { input }
22203 }
22204}
22205
22206fn is_char_ideographic(ch: char) -> bool {
22207 use unicode_script::Script::*;
22208 use unicode_script::UnicodeScript;
22209 matches!(ch.script(), Han | Tangut | Yi)
22210}
22211
22212fn is_grapheme_ideographic(text: &str) -> bool {
22213 text.chars().any(is_char_ideographic)
22214}
22215
22216fn is_grapheme_whitespace(text: &str) -> bool {
22217 text.chars().any(|x| x.is_whitespace())
22218}
22219
22220fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22221 text.chars()
22222 .next()
22223 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22224}
22225
22226#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22227enum WordBreakToken<'a> {
22228 Word { token: &'a str, grapheme_len: usize },
22229 InlineWhitespace { token: &'a str, grapheme_len: usize },
22230 Newline,
22231}
22232
22233impl<'a> Iterator for WordBreakingTokenizer<'a> {
22234 /// Yields a span, the count of graphemes in the token, and whether it was
22235 /// whitespace. Note that it also breaks at word boundaries.
22236 type Item = WordBreakToken<'a>;
22237
22238 fn next(&mut self) -> Option<Self::Item> {
22239 use unicode_segmentation::UnicodeSegmentation;
22240 if self.input.is_empty() {
22241 return None;
22242 }
22243
22244 let mut iter = self.input.graphemes(true).peekable();
22245 let mut offset = 0;
22246 let mut grapheme_len = 0;
22247 if let Some(first_grapheme) = iter.next() {
22248 let is_newline = first_grapheme == "\n";
22249 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22250 offset += first_grapheme.len();
22251 grapheme_len += 1;
22252 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22253 if let Some(grapheme) = iter.peek().copied()
22254 && should_stay_with_preceding_ideograph(grapheme)
22255 {
22256 offset += grapheme.len();
22257 grapheme_len += 1;
22258 }
22259 } else {
22260 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22261 let mut next_word_bound = words.peek().copied();
22262 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22263 next_word_bound = words.next();
22264 }
22265 while let Some(grapheme) = iter.peek().copied() {
22266 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22267 break;
22268 };
22269 if is_grapheme_whitespace(grapheme) != is_whitespace
22270 || (grapheme == "\n") != is_newline
22271 {
22272 break;
22273 };
22274 offset += grapheme.len();
22275 grapheme_len += 1;
22276 iter.next();
22277 }
22278 }
22279 let token = &self.input[..offset];
22280 self.input = &self.input[offset..];
22281 if token == "\n" {
22282 Some(WordBreakToken::Newline)
22283 } else if is_whitespace {
22284 Some(WordBreakToken::InlineWhitespace {
22285 token,
22286 grapheme_len,
22287 })
22288 } else {
22289 Some(WordBreakToken::Word {
22290 token,
22291 grapheme_len,
22292 })
22293 }
22294 } else {
22295 None
22296 }
22297 }
22298}
22299
22300#[test]
22301fn test_word_breaking_tokenizer() {
22302 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22303 ("", &[]),
22304 (" ", &[whitespace(" ", 2)]),
22305 ("Ʒ", &[word("Ʒ", 1)]),
22306 ("Ǽ", &[word("Ǽ", 1)]),
22307 ("⋑", &[word("⋑", 1)]),
22308 ("⋑⋑", &[word("⋑⋑", 2)]),
22309 (
22310 "原理,进而",
22311 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22312 ),
22313 (
22314 "hello world",
22315 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22316 ),
22317 (
22318 "hello, world",
22319 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22320 ),
22321 (
22322 " hello world",
22323 &[
22324 whitespace(" ", 2),
22325 word("hello", 5),
22326 whitespace(" ", 1),
22327 word("world", 5),
22328 ],
22329 ),
22330 (
22331 "这是什么 \n 钢笔",
22332 &[
22333 word("这", 1),
22334 word("是", 1),
22335 word("什", 1),
22336 word("么", 1),
22337 whitespace(" ", 1),
22338 newline(),
22339 whitespace(" ", 1),
22340 word("钢", 1),
22341 word("笔", 1),
22342 ],
22343 ),
22344 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22345 ];
22346
22347 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22348 WordBreakToken::Word {
22349 token,
22350 grapheme_len,
22351 }
22352 }
22353
22354 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22355 WordBreakToken::InlineWhitespace {
22356 token,
22357 grapheme_len,
22358 }
22359 }
22360
22361 fn newline() -> WordBreakToken<'static> {
22362 WordBreakToken::Newline
22363 }
22364
22365 for (input, result) in tests {
22366 assert_eq!(
22367 WordBreakingTokenizer::new(input)
22368 .collect::<Vec<_>>()
22369 .as_slice(),
22370 *result,
22371 );
22372 }
22373}
22374
22375fn wrap_with_prefix(
22376 first_line_prefix: String,
22377 subsequent_lines_prefix: String,
22378 unwrapped_text: String,
22379 wrap_column: usize,
22380 tab_size: NonZeroU32,
22381 preserve_existing_whitespace: bool,
22382) -> String {
22383 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22384 let subsequent_lines_prefix_len =
22385 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22386 let mut wrapped_text = String::new();
22387 let mut current_line = first_line_prefix;
22388 let mut is_first_line = true;
22389
22390 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22391 let mut current_line_len = first_line_prefix_len;
22392 let mut in_whitespace = false;
22393 for token in tokenizer {
22394 let have_preceding_whitespace = in_whitespace;
22395 match token {
22396 WordBreakToken::Word {
22397 token,
22398 grapheme_len,
22399 } => {
22400 in_whitespace = false;
22401 let current_prefix_len = if is_first_line {
22402 first_line_prefix_len
22403 } else {
22404 subsequent_lines_prefix_len
22405 };
22406 if current_line_len + grapheme_len > wrap_column
22407 && current_line_len != current_prefix_len
22408 {
22409 wrapped_text.push_str(current_line.trim_end());
22410 wrapped_text.push('\n');
22411 is_first_line = false;
22412 current_line = subsequent_lines_prefix.clone();
22413 current_line_len = subsequent_lines_prefix_len;
22414 }
22415 current_line.push_str(token);
22416 current_line_len += grapheme_len;
22417 }
22418 WordBreakToken::InlineWhitespace {
22419 mut token,
22420 mut grapheme_len,
22421 } => {
22422 in_whitespace = true;
22423 if have_preceding_whitespace && !preserve_existing_whitespace {
22424 continue;
22425 }
22426 if !preserve_existing_whitespace {
22427 // Keep a single whitespace grapheme as-is
22428 if let Some(first) =
22429 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22430 {
22431 token = first;
22432 } else {
22433 token = " ";
22434 }
22435 grapheme_len = 1;
22436 }
22437 let current_prefix_len = if is_first_line {
22438 first_line_prefix_len
22439 } else {
22440 subsequent_lines_prefix_len
22441 };
22442 if current_line_len + grapheme_len > wrap_column {
22443 wrapped_text.push_str(current_line.trim_end());
22444 wrapped_text.push('\n');
22445 is_first_line = false;
22446 current_line = subsequent_lines_prefix.clone();
22447 current_line_len = subsequent_lines_prefix_len;
22448 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22449 current_line.push_str(token);
22450 current_line_len += grapheme_len;
22451 }
22452 }
22453 WordBreakToken::Newline => {
22454 in_whitespace = true;
22455 let current_prefix_len = if is_first_line {
22456 first_line_prefix_len
22457 } else {
22458 subsequent_lines_prefix_len
22459 };
22460 if preserve_existing_whitespace {
22461 wrapped_text.push_str(current_line.trim_end());
22462 wrapped_text.push('\n');
22463 is_first_line = false;
22464 current_line = subsequent_lines_prefix.clone();
22465 current_line_len = subsequent_lines_prefix_len;
22466 } else if have_preceding_whitespace {
22467 continue;
22468 } else if current_line_len + 1 > wrap_column
22469 && current_line_len != current_prefix_len
22470 {
22471 wrapped_text.push_str(current_line.trim_end());
22472 wrapped_text.push('\n');
22473 is_first_line = false;
22474 current_line = subsequent_lines_prefix.clone();
22475 current_line_len = subsequent_lines_prefix_len;
22476 } else if current_line_len != current_prefix_len {
22477 current_line.push(' ');
22478 current_line_len += 1;
22479 }
22480 }
22481 }
22482 }
22483
22484 if !current_line.is_empty() {
22485 wrapped_text.push_str(¤t_line);
22486 }
22487 wrapped_text
22488}
22489
22490#[test]
22491fn test_wrap_with_prefix() {
22492 assert_eq!(
22493 wrap_with_prefix(
22494 "# ".to_string(),
22495 "# ".to_string(),
22496 "abcdefg".to_string(),
22497 4,
22498 NonZeroU32::new(4).unwrap(),
22499 false,
22500 ),
22501 "# abcdefg"
22502 );
22503 assert_eq!(
22504 wrap_with_prefix(
22505 "".to_string(),
22506 "".to_string(),
22507 "\thello world".to_string(),
22508 8,
22509 NonZeroU32::new(4).unwrap(),
22510 false,
22511 ),
22512 "hello\nworld"
22513 );
22514 assert_eq!(
22515 wrap_with_prefix(
22516 "// ".to_string(),
22517 "// ".to_string(),
22518 "xx \nyy zz aa bb cc".to_string(),
22519 12,
22520 NonZeroU32::new(4).unwrap(),
22521 false,
22522 ),
22523 "// xx yy zz\n// aa bb cc"
22524 );
22525 assert_eq!(
22526 wrap_with_prefix(
22527 String::new(),
22528 String::new(),
22529 "这是什么 \n 钢笔".to_string(),
22530 3,
22531 NonZeroU32::new(4).unwrap(),
22532 false,
22533 ),
22534 "这是什\n么 钢\n笔"
22535 );
22536 assert_eq!(
22537 wrap_with_prefix(
22538 String::new(),
22539 String::new(),
22540 format!("foo{}bar", '\u{2009}'), // thin space
22541 80,
22542 NonZeroU32::new(4).unwrap(),
22543 false,
22544 ),
22545 format!("foo{}bar", '\u{2009}')
22546 );
22547}
22548
22549pub trait CollaborationHub {
22550 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22551 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22552 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22553}
22554
22555impl CollaborationHub for Entity<Project> {
22556 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22557 self.read(cx).collaborators()
22558 }
22559
22560 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22561 self.read(cx).user_store().read(cx).participant_indices()
22562 }
22563
22564 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22565 let this = self.read(cx);
22566 let user_ids = this.collaborators().values().map(|c| c.user_id);
22567 this.user_store().read(cx).participant_names(user_ids, cx)
22568 }
22569}
22570
22571pub trait SemanticsProvider {
22572 fn hover(
22573 &self,
22574 buffer: &Entity<Buffer>,
22575 position: text::Anchor,
22576 cx: &mut App,
22577 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22578
22579 fn inline_values(
22580 &self,
22581 buffer_handle: Entity<Buffer>,
22582 range: Range<text::Anchor>,
22583 cx: &mut App,
22584 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22585
22586 fn inlay_hints(
22587 &self,
22588 buffer_handle: Entity<Buffer>,
22589 range: Range<text::Anchor>,
22590 cx: &mut App,
22591 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22592
22593 fn resolve_inlay_hint(
22594 &self,
22595 hint: InlayHint,
22596 buffer_handle: Entity<Buffer>,
22597 server_id: LanguageServerId,
22598 cx: &mut App,
22599 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22600
22601 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22602
22603 fn document_highlights(
22604 &self,
22605 buffer: &Entity<Buffer>,
22606 position: text::Anchor,
22607 cx: &mut App,
22608 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22609
22610 fn definitions(
22611 &self,
22612 buffer: &Entity<Buffer>,
22613 position: text::Anchor,
22614 kind: GotoDefinitionKind,
22615 cx: &mut App,
22616 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22617
22618 fn range_for_rename(
22619 &self,
22620 buffer: &Entity<Buffer>,
22621 position: text::Anchor,
22622 cx: &mut App,
22623 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22624
22625 fn perform_rename(
22626 &self,
22627 buffer: &Entity<Buffer>,
22628 position: text::Anchor,
22629 new_name: String,
22630 cx: &mut App,
22631 ) -> Option<Task<Result<ProjectTransaction>>>;
22632}
22633
22634pub trait CompletionProvider {
22635 fn completions(
22636 &self,
22637 excerpt_id: ExcerptId,
22638 buffer: &Entity<Buffer>,
22639 buffer_position: text::Anchor,
22640 trigger: CompletionContext,
22641 window: &mut Window,
22642 cx: &mut Context<Editor>,
22643 ) -> Task<Result<Vec<CompletionResponse>>>;
22644
22645 fn resolve_completions(
22646 &self,
22647 _buffer: Entity<Buffer>,
22648 _completion_indices: Vec<usize>,
22649 _completions: Rc<RefCell<Box<[Completion]>>>,
22650 _cx: &mut Context<Editor>,
22651 ) -> Task<Result<bool>> {
22652 Task::ready(Ok(false))
22653 }
22654
22655 fn apply_additional_edits_for_completion(
22656 &self,
22657 _buffer: Entity<Buffer>,
22658 _completions: Rc<RefCell<Box<[Completion]>>>,
22659 _completion_index: usize,
22660 _push_to_history: bool,
22661 _cx: &mut Context<Editor>,
22662 ) -> Task<Result<Option<language::Transaction>>> {
22663 Task::ready(Ok(None))
22664 }
22665
22666 fn is_completion_trigger(
22667 &self,
22668 buffer: &Entity<Buffer>,
22669 position: language::Anchor,
22670 text: &str,
22671 trigger_in_words: bool,
22672 menu_is_open: bool,
22673 cx: &mut Context<Editor>,
22674 ) -> bool;
22675
22676 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22677
22678 fn sort_completions(&self) -> bool {
22679 true
22680 }
22681
22682 fn filter_completions(&self) -> bool {
22683 true
22684 }
22685}
22686
22687pub trait CodeActionProvider {
22688 fn id(&self) -> Arc<str>;
22689
22690 fn code_actions(
22691 &self,
22692 buffer: &Entity<Buffer>,
22693 range: Range<text::Anchor>,
22694 window: &mut Window,
22695 cx: &mut App,
22696 ) -> Task<Result<Vec<CodeAction>>>;
22697
22698 fn apply_code_action(
22699 &self,
22700 buffer_handle: Entity<Buffer>,
22701 action: CodeAction,
22702 excerpt_id: ExcerptId,
22703 push_to_history: bool,
22704 window: &mut Window,
22705 cx: &mut App,
22706 ) -> Task<Result<ProjectTransaction>>;
22707}
22708
22709impl CodeActionProvider for Entity<Project> {
22710 fn id(&self) -> Arc<str> {
22711 "project".into()
22712 }
22713
22714 fn code_actions(
22715 &self,
22716 buffer: &Entity<Buffer>,
22717 range: Range<text::Anchor>,
22718 _window: &mut Window,
22719 cx: &mut App,
22720 ) -> Task<Result<Vec<CodeAction>>> {
22721 self.update(cx, |project, cx| {
22722 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22723 let code_actions = project.code_actions(buffer, range, None, cx);
22724 cx.background_spawn(async move {
22725 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22726 Ok(code_lens_actions
22727 .context("code lens fetch")?
22728 .into_iter()
22729 .flatten()
22730 .chain(
22731 code_actions
22732 .context("code action fetch")?
22733 .into_iter()
22734 .flatten(),
22735 )
22736 .collect())
22737 })
22738 })
22739 }
22740
22741 fn apply_code_action(
22742 &self,
22743 buffer_handle: Entity<Buffer>,
22744 action: CodeAction,
22745 _excerpt_id: ExcerptId,
22746 push_to_history: bool,
22747 _window: &mut Window,
22748 cx: &mut App,
22749 ) -> Task<Result<ProjectTransaction>> {
22750 self.update(cx, |project, cx| {
22751 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22752 })
22753 }
22754}
22755
22756fn snippet_completions(
22757 project: &Project,
22758 buffer: &Entity<Buffer>,
22759 buffer_position: text::Anchor,
22760 cx: &mut App,
22761) -> Task<Result<CompletionResponse>> {
22762 let languages = buffer.read(cx).languages_at(buffer_position);
22763 let snippet_store = project.snippets().read(cx);
22764
22765 let scopes: Vec<_> = languages
22766 .iter()
22767 .filter_map(|language| {
22768 let language_name = language.lsp_id();
22769 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22770
22771 if snippets.is_empty() {
22772 None
22773 } else {
22774 Some((language.default_scope(), snippets))
22775 }
22776 })
22777 .collect();
22778
22779 if scopes.is_empty() {
22780 return Task::ready(Ok(CompletionResponse {
22781 completions: vec![],
22782 display_options: CompletionDisplayOptions::default(),
22783 is_incomplete: false,
22784 }));
22785 }
22786
22787 let snapshot = buffer.read(cx).text_snapshot();
22788 let chars: String = snapshot
22789 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22790 .collect();
22791 let executor = cx.background_executor().clone();
22792
22793 cx.background_spawn(async move {
22794 let mut is_incomplete = false;
22795 let mut completions: Vec<Completion> = Vec::new();
22796 for (scope, snippets) in scopes.into_iter() {
22797 let classifier =
22798 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22799 let mut last_word = chars
22800 .chars()
22801 .take_while(|c| classifier.is_word(*c))
22802 .collect::<String>();
22803 last_word = last_word.chars().rev().collect();
22804
22805 if last_word.is_empty() {
22806 return Ok(CompletionResponse {
22807 completions: vec![],
22808 display_options: CompletionDisplayOptions::default(),
22809 is_incomplete: true,
22810 });
22811 }
22812
22813 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22814 let to_lsp = |point: &text::Anchor| {
22815 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22816 point_to_lsp(end)
22817 };
22818 let lsp_end = to_lsp(&buffer_position);
22819
22820 let candidates = snippets
22821 .iter()
22822 .enumerate()
22823 .flat_map(|(ix, snippet)| {
22824 snippet
22825 .prefix
22826 .iter()
22827 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22828 })
22829 .collect::<Vec<StringMatchCandidate>>();
22830
22831 const MAX_RESULTS: usize = 100;
22832 let mut matches = fuzzy::match_strings(
22833 &candidates,
22834 &last_word,
22835 last_word.chars().any(|c| c.is_uppercase()),
22836 true,
22837 MAX_RESULTS,
22838 &Default::default(),
22839 executor.clone(),
22840 )
22841 .await;
22842
22843 if matches.len() >= MAX_RESULTS {
22844 is_incomplete = true;
22845 }
22846
22847 // Remove all candidates where the query's start does not match the start of any word in the candidate
22848 if let Some(query_start) = last_word.chars().next() {
22849 matches.retain(|string_match| {
22850 split_words(&string_match.string).any(|word| {
22851 // Check that the first codepoint of the word as lowercase matches the first
22852 // codepoint of the query as lowercase
22853 word.chars()
22854 .flat_map(|codepoint| codepoint.to_lowercase())
22855 .zip(query_start.to_lowercase())
22856 .all(|(word_cp, query_cp)| word_cp == query_cp)
22857 })
22858 });
22859 }
22860
22861 let matched_strings = matches
22862 .into_iter()
22863 .map(|m| m.string)
22864 .collect::<HashSet<_>>();
22865
22866 completions.extend(snippets.iter().filter_map(|snippet| {
22867 let matching_prefix = snippet
22868 .prefix
22869 .iter()
22870 .find(|prefix| matched_strings.contains(*prefix))?;
22871 let start = as_offset - last_word.len();
22872 let start = snapshot.anchor_before(start);
22873 let range = start..buffer_position;
22874 let lsp_start = to_lsp(&start);
22875 let lsp_range = lsp::Range {
22876 start: lsp_start,
22877 end: lsp_end,
22878 };
22879 Some(Completion {
22880 replace_range: range,
22881 new_text: snippet.body.clone(),
22882 source: CompletionSource::Lsp {
22883 insert_range: None,
22884 server_id: LanguageServerId(usize::MAX),
22885 resolved: true,
22886 lsp_completion: Box::new(lsp::CompletionItem {
22887 label: snippet.prefix.first().unwrap().clone(),
22888 kind: Some(CompletionItemKind::SNIPPET),
22889 label_details: snippet.description.as_ref().map(|description| {
22890 lsp::CompletionItemLabelDetails {
22891 detail: Some(description.clone()),
22892 description: None,
22893 }
22894 }),
22895 insert_text_format: Some(InsertTextFormat::SNIPPET),
22896 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22897 lsp::InsertReplaceEdit {
22898 new_text: snippet.body.clone(),
22899 insert: lsp_range,
22900 replace: lsp_range,
22901 },
22902 )),
22903 filter_text: Some(snippet.body.clone()),
22904 sort_text: Some(char::MAX.to_string()),
22905 ..lsp::CompletionItem::default()
22906 }),
22907 lsp_defaults: None,
22908 },
22909 label: CodeLabel {
22910 text: matching_prefix.clone(),
22911 runs: Vec::new(),
22912 filter_range: 0..matching_prefix.len(),
22913 },
22914 icon_path: None,
22915 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22916 single_line: snippet.name.clone().into(),
22917 plain_text: snippet
22918 .description
22919 .clone()
22920 .map(|description| description.into()),
22921 }),
22922 insert_text_mode: None,
22923 confirm: None,
22924 })
22925 }))
22926 }
22927
22928 Ok(CompletionResponse {
22929 completions,
22930 display_options: CompletionDisplayOptions::default(),
22931 is_incomplete,
22932 })
22933 })
22934}
22935
22936impl CompletionProvider for Entity<Project> {
22937 fn completions(
22938 &self,
22939 _excerpt_id: ExcerptId,
22940 buffer: &Entity<Buffer>,
22941 buffer_position: text::Anchor,
22942 options: CompletionContext,
22943 _window: &mut Window,
22944 cx: &mut Context<Editor>,
22945 ) -> Task<Result<Vec<CompletionResponse>>> {
22946 self.update(cx, |project, cx| {
22947 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22948 let project_completions = project.completions(buffer, buffer_position, options, cx);
22949 cx.background_spawn(async move {
22950 let mut responses = project_completions.await?;
22951 let snippets = snippets.await?;
22952 if !snippets.completions.is_empty() {
22953 responses.push(snippets);
22954 }
22955 Ok(responses)
22956 })
22957 })
22958 }
22959
22960 fn resolve_completions(
22961 &self,
22962 buffer: Entity<Buffer>,
22963 completion_indices: Vec<usize>,
22964 completions: Rc<RefCell<Box<[Completion]>>>,
22965 cx: &mut Context<Editor>,
22966 ) -> Task<Result<bool>> {
22967 self.update(cx, |project, cx| {
22968 project.lsp_store().update(cx, |lsp_store, cx| {
22969 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22970 })
22971 })
22972 }
22973
22974 fn apply_additional_edits_for_completion(
22975 &self,
22976 buffer: Entity<Buffer>,
22977 completions: Rc<RefCell<Box<[Completion]>>>,
22978 completion_index: usize,
22979 push_to_history: bool,
22980 cx: &mut Context<Editor>,
22981 ) -> Task<Result<Option<language::Transaction>>> {
22982 self.update(cx, |project, cx| {
22983 project.lsp_store().update(cx, |lsp_store, cx| {
22984 lsp_store.apply_additional_edits_for_completion(
22985 buffer,
22986 completions,
22987 completion_index,
22988 push_to_history,
22989 cx,
22990 )
22991 })
22992 })
22993 }
22994
22995 fn is_completion_trigger(
22996 &self,
22997 buffer: &Entity<Buffer>,
22998 position: language::Anchor,
22999 text: &str,
23000 trigger_in_words: bool,
23001 menu_is_open: bool,
23002 cx: &mut Context<Editor>,
23003 ) -> bool {
23004 let mut chars = text.chars();
23005 let char = if let Some(char) = chars.next() {
23006 char
23007 } else {
23008 return false;
23009 };
23010 if chars.next().is_some() {
23011 return false;
23012 }
23013
23014 let buffer = buffer.read(cx);
23015 let snapshot = buffer.snapshot();
23016 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23017 return false;
23018 }
23019 let classifier = snapshot
23020 .char_classifier_at(position)
23021 .scope_context(Some(CharScopeContext::Completion));
23022 if trigger_in_words && classifier.is_word(char) {
23023 return true;
23024 }
23025
23026 buffer.completion_triggers().contains(text)
23027 }
23028}
23029
23030impl SemanticsProvider for Entity<Project> {
23031 fn hover(
23032 &self,
23033 buffer: &Entity<Buffer>,
23034 position: text::Anchor,
23035 cx: &mut App,
23036 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23037 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23038 }
23039
23040 fn document_highlights(
23041 &self,
23042 buffer: &Entity<Buffer>,
23043 position: text::Anchor,
23044 cx: &mut App,
23045 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23046 Some(self.update(cx, |project, cx| {
23047 project.document_highlights(buffer, position, cx)
23048 }))
23049 }
23050
23051 fn definitions(
23052 &self,
23053 buffer: &Entity<Buffer>,
23054 position: text::Anchor,
23055 kind: GotoDefinitionKind,
23056 cx: &mut App,
23057 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23058 Some(self.update(cx, |project, cx| match kind {
23059 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23060 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23061 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23062 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23063 }))
23064 }
23065
23066 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23067 self.update(cx, |project, cx| {
23068 if project
23069 .active_debug_session(cx)
23070 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23071 {
23072 return true;
23073 }
23074
23075 buffer.update(cx, |buffer, cx| {
23076 project.any_language_server_supports_inlay_hints(buffer, cx)
23077 })
23078 })
23079 }
23080
23081 fn inline_values(
23082 &self,
23083 buffer_handle: Entity<Buffer>,
23084 range: Range<text::Anchor>,
23085 cx: &mut App,
23086 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23087 self.update(cx, |project, cx| {
23088 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23089
23090 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23091 })
23092 }
23093
23094 fn inlay_hints(
23095 &self,
23096 buffer_handle: Entity<Buffer>,
23097 range: Range<text::Anchor>,
23098 cx: &mut App,
23099 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23100 Some(self.update(cx, |project, cx| {
23101 project.inlay_hints(buffer_handle, range, cx)
23102 }))
23103 }
23104
23105 fn resolve_inlay_hint(
23106 &self,
23107 hint: InlayHint,
23108 buffer_handle: Entity<Buffer>,
23109 server_id: LanguageServerId,
23110 cx: &mut App,
23111 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23112 Some(self.update(cx, |project, cx| {
23113 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23114 }))
23115 }
23116
23117 fn range_for_rename(
23118 &self,
23119 buffer: &Entity<Buffer>,
23120 position: text::Anchor,
23121 cx: &mut App,
23122 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23123 Some(self.update(cx, |project, cx| {
23124 let buffer = buffer.clone();
23125 let task = project.prepare_rename(buffer.clone(), position, cx);
23126 cx.spawn(async move |_, cx| {
23127 Ok(match task.await? {
23128 PrepareRenameResponse::Success(range) => Some(range),
23129 PrepareRenameResponse::InvalidPosition => None,
23130 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23131 // Fallback on using TreeSitter info to determine identifier range
23132 buffer.read_with(cx, |buffer, _| {
23133 let snapshot = buffer.snapshot();
23134 let (range, kind) = snapshot.surrounding_word(position, None);
23135 if kind != Some(CharKind::Word) {
23136 return None;
23137 }
23138 Some(
23139 snapshot.anchor_before(range.start)
23140 ..snapshot.anchor_after(range.end),
23141 )
23142 })?
23143 }
23144 })
23145 })
23146 }))
23147 }
23148
23149 fn perform_rename(
23150 &self,
23151 buffer: &Entity<Buffer>,
23152 position: text::Anchor,
23153 new_name: String,
23154 cx: &mut App,
23155 ) -> Option<Task<Result<ProjectTransaction>>> {
23156 Some(self.update(cx, |project, cx| {
23157 project.perform_rename(buffer.clone(), position, new_name, cx)
23158 }))
23159 }
23160}
23161
23162fn inlay_hint_settings(
23163 location: Anchor,
23164 snapshot: &MultiBufferSnapshot,
23165 cx: &mut Context<Editor>,
23166) -> InlayHintSettings {
23167 let file = snapshot.file_at(location);
23168 let language = snapshot.language_at(location).map(|l| l.name());
23169 language_settings(language, file, cx).inlay_hints
23170}
23171
23172fn consume_contiguous_rows(
23173 contiguous_row_selections: &mut Vec<Selection<Point>>,
23174 selection: &Selection<Point>,
23175 display_map: &DisplaySnapshot,
23176 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23177) -> (MultiBufferRow, MultiBufferRow) {
23178 contiguous_row_selections.push(selection.clone());
23179 let start_row = starting_row(selection, display_map);
23180 let mut end_row = ending_row(selection, display_map);
23181
23182 while let Some(next_selection) = selections.peek() {
23183 if next_selection.start.row <= end_row.0 {
23184 end_row = ending_row(next_selection, display_map);
23185 contiguous_row_selections.push(selections.next().unwrap().clone());
23186 } else {
23187 break;
23188 }
23189 }
23190 (start_row, end_row)
23191}
23192
23193fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23194 if selection.start.column > 0 {
23195 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23196 } else {
23197 MultiBufferRow(selection.start.row)
23198 }
23199}
23200
23201fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23202 if next_selection.end.column > 0 || next_selection.is_empty() {
23203 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23204 } else {
23205 MultiBufferRow(next_selection.end.row)
23206 }
23207}
23208
23209impl EditorSnapshot {
23210 pub fn remote_selections_in_range<'a>(
23211 &'a self,
23212 range: &'a Range<Anchor>,
23213 collaboration_hub: &dyn CollaborationHub,
23214 cx: &'a App,
23215 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23216 let participant_names = collaboration_hub.user_names(cx);
23217 let participant_indices = collaboration_hub.user_participant_indices(cx);
23218 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23219 let collaborators_by_replica_id = collaborators_by_peer_id
23220 .values()
23221 .map(|collaborator| (collaborator.replica_id, collaborator))
23222 .collect::<HashMap<_, _>>();
23223 self.buffer_snapshot
23224 .selections_in_range(range, false)
23225 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23226 if replica_id == AGENT_REPLICA_ID {
23227 Some(RemoteSelection {
23228 replica_id,
23229 selection,
23230 cursor_shape,
23231 line_mode,
23232 collaborator_id: CollaboratorId::Agent,
23233 user_name: Some("Agent".into()),
23234 color: cx.theme().players().agent(),
23235 })
23236 } else {
23237 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23238 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23239 let user_name = participant_names.get(&collaborator.user_id).cloned();
23240 Some(RemoteSelection {
23241 replica_id,
23242 selection,
23243 cursor_shape,
23244 line_mode,
23245 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23246 user_name,
23247 color: if let Some(index) = participant_index {
23248 cx.theme().players().color_for_participant(index.0)
23249 } else {
23250 cx.theme().players().absent()
23251 },
23252 })
23253 }
23254 })
23255 }
23256
23257 pub fn hunks_for_ranges(
23258 &self,
23259 ranges: impl IntoIterator<Item = Range<Point>>,
23260 ) -> Vec<MultiBufferDiffHunk> {
23261 let mut hunks = Vec::new();
23262 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23263 HashMap::default();
23264 for query_range in ranges {
23265 let query_rows =
23266 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23267 for hunk in self.buffer_snapshot.diff_hunks_in_range(
23268 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23269 ) {
23270 // Include deleted hunks that are adjacent to the query range, because
23271 // otherwise they would be missed.
23272 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23273 if hunk.status().is_deleted() {
23274 intersects_range |= hunk.row_range.start == query_rows.end;
23275 intersects_range |= hunk.row_range.end == query_rows.start;
23276 }
23277 if intersects_range {
23278 if !processed_buffer_rows
23279 .entry(hunk.buffer_id)
23280 .or_default()
23281 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23282 {
23283 continue;
23284 }
23285 hunks.push(hunk);
23286 }
23287 }
23288 }
23289
23290 hunks
23291 }
23292
23293 fn display_diff_hunks_for_rows<'a>(
23294 &'a self,
23295 display_rows: Range<DisplayRow>,
23296 folded_buffers: &'a HashSet<BufferId>,
23297 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23298 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23299 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23300
23301 self.buffer_snapshot
23302 .diff_hunks_in_range(buffer_start..buffer_end)
23303 .filter_map(|hunk| {
23304 if folded_buffers.contains(&hunk.buffer_id) {
23305 return None;
23306 }
23307
23308 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23309 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23310
23311 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23312 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23313
23314 let display_hunk = if hunk_display_start.column() != 0 {
23315 DisplayDiffHunk::Folded {
23316 display_row: hunk_display_start.row(),
23317 }
23318 } else {
23319 let mut end_row = hunk_display_end.row();
23320 if hunk_display_end.column() > 0 {
23321 end_row.0 += 1;
23322 }
23323 let is_created_file = hunk.is_created_file();
23324 DisplayDiffHunk::Unfolded {
23325 status: hunk.status(),
23326 diff_base_byte_range: hunk.diff_base_byte_range,
23327 display_row_range: hunk_display_start.row()..end_row,
23328 multi_buffer_range: Anchor::range_in_buffer(
23329 hunk.excerpt_id,
23330 hunk.buffer_id,
23331 hunk.buffer_range,
23332 ),
23333 is_created_file,
23334 }
23335 };
23336
23337 Some(display_hunk)
23338 })
23339 }
23340
23341 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23342 self.display_snapshot.buffer_snapshot.language_at(position)
23343 }
23344
23345 pub fn is_focused(&self) -> bool {
23346 self.is_focused
23347 }
23348
23349 pub fn placeholder_text(&self) -> Option<String> {
23350 self.placeholder_display_snapshot
23351 .as_ref()
23352 .map(|display_map| display_map.text())
23353 }
23354
23355 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23356 self.scroll_anchor.scroll_position(&self.display_snapshot)
23357 }
23358
23359 fn gutter_dimensions(
23360 &self,
23361 font_id: FontId,
23362 font_size: Pixels,
23363 max_line_number_width: Pixels,
23364 cx: &App,
23365 ) -> Option<GutterDimensions> {
23366 if !self.show_gutter {
23367 return None;
23368 }
23369
23370 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23371 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23372
23373 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23374 matches!(
23375 ProjectSettings::get_global(cx).git.git_gutter,
23376 GitGutterSetting::TrackedFiles
23377 )
23378 });
23379 let gutter_settings = EditorSettings::get_global(cx).gutter;
23380 let show_line_numbers = self
23381 .show_line_numbers
23382 .unwrap_or(gutter_settings.line_numbers);
23383 let line_gutter_width = if show_line_numbers {
23384 // Avoid flicker-like gutter resizes when the line number gains another digit by
23385 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23386 let min_width_for_number_on_gutter =
23387 ch_advance * gutter_settings.min_line_number_digits as f32;
23388 max_line_number_width.max(min_width_for_number_on_gutter)
23389 } else {
23390 0.0.into()
23391 };
23392
23393 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23394 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23395
23396 let git_blame_entries_width =
23397 self.git_blame_gutter_max_author_length
23398 .map(|max_author_length| {
23399 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23400 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23401
23402 /// The number of characters to dedicate to gaps and margins.
23403 const SPACING_WIDTH: usize = 4;
23404
23405 let max_char_count = max_author_length.min(renderer.max_author_length())
23406 + ::git::SHORT_SHA_LENGTH
23407 + MAX_RELATIVE_TIMESTAMP.len()
23408 + SPACING_WIDTH;
23409
23410 ch_advance * max_char_count
23411 });
23412
23413 let is_singleton = self.buffer_snapshot.is_singleton();
23414
23415 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23416 left_padding += if !is_singleton {
23417 ch_width * 4.0
23418 } else if show_runnables || show_breakpoints {
23419 ch_width * 3.0
23420 } else if show_git_gutter && show_line_numbers {
23421 ch_width * 2.0
23422 } else if show_git_gutter || show_line_numbers {
23423 ch_width
23424 } else {
23425 px(0.)
23426 };
23427
23428 let shows_folds = is_singleton && gutter_settings.folds;
23429
23430 let right_padding = if shows_folds && show_line_numbers {
23431 ch_width * 4.0
23432 } else if shows_folds || (!is_singleton && show_line_numbers) {
23433 ch_width * 3.0
23434 } else if show_line_numbers {
23435 ch_width
23436 } else {
23437 px(0.)
23438 };
23439
23440 Some(GutterDimensions {
23441 left_padding,
23442 right_padding,
23443 width: line_gutter_width + left_padding + right_padding,
23444 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23445 git_blame_entries_width,
23446 })
23447 }
23448
23449 pub fn render_crease_toggle(
23450 &self,
23451 buffer_row: MultiBufferRow,
23452 row_contains_cursor: bool,
23453 editor: Entity<Editor>,
23454 window: &mut Window,
23455 cx: &mut App,
23456 ) -> Option<AnyElement> {
23457 let folded = self.is_line_folded(buffer_row);
23458 let mut is_foldable = false;
23459
23460 if let Some(crease) = self
23461 .crease_snapshot
23462 .query_row(buffer_row, &self.buffer_snapshot)
23463 {
23464 is_foldable = true;
23465 match crease {
23466 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23467 if let Some(render_toggle) = render_toggle {
23468 let toggle_callback =
23469 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23470 if folded {
23471 editor.update(cx, |editor, cx| {
23472 editor.fold_at(buffer_row, window, cx)
23473 });
23474 } else {
23475 editor.update(cx, |editor, cx| {
23476 editor.unfold_at(buffer_row, window, cx)
23477 });
23478 }
23479 });
23480 return Some((render_toggle)(
23481 buffer_row,
23482 folded,
23483 toggle_callback,
23484 window,
23485 cx,
23486 ));
23487 }
23488 }
23489 }
23490 }
23491
23492 is_foldable |= self.starts_indent(buffer_row);
23493
23494 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23495 Some(
23496 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23497 .toggle_state(folded)
23498 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23499 if folded {
23500 this.unfold_at(buffer_row, window, cx);
23501 } else {
23502 this.fold_at(buffer_row, window, cx);
23503 }
23504 }))
23505 .into_any_element(),
23506 )
23507 } else {
23508 None
23509 }
23510 }
23511
23512 pub fn render_crease_trailer(
23513 &self,
23514 buffer_row: MultiBufferRow,
23515 window: &mut Window,
23516 cx: &mut App,
23517 ) -> Option<AnyElement> {
23518 let folded = self.is_line_folded(buffer_row);
23519 if let Crease::Inline { render_trailer, .. } = self
23520 .crease_snapshot
23521 .query_row(buffer_row, &self.buffer_snapshot)?
23522 {
23523 let render_trailer = render_trailer.as_ref()?;
23524 Some(render_trailer(buffer_row, folded, window, cx))
23525 } else {
23526 None
23527 }
23528 }
23529}
23530
23531impl Deref for EditorSnapshot {
23532 type Target = DisplaySnapshot;
23533
23534 fn deref(&self) -> &Self::Target {
23535 &self.display_snapshot
23536 }
23537}
23538
23539#[derive(Clone, Debug, PartialEq, Eq)]
23540pub enum EditorEvent {
23541 InputIgnored {
23542 text: Arc<str>,
23543 },
23544 InputHandled {
23545 utf16_range_to_replace: Option<Range<isize>>,
23546 text: Arc<str>,
23547 },
23548 ExcerptsAdded {
23549 buffer: Entity<Buffer>,
23550 predecessor: ExcerptId,
23551 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23552 },
23553 ExcerptsRemoved {
23554 ids: Vec<ExcerptId>,
23555 removed_buffer_ids: Vec<BufferId>,
23556 },
23557 BufferFoldToggled {
23558 ids: Vec<ExcerptId>,
23559 folded: bool,
23560 },
23561 ExcerptsEdited {
23562 ids: Vec<ExcerptId>,
23563 },
23564 ExcerptsExpanded {
23565 ids: Vec<ExcerptId>,
23566 },
23567 BufferEdited,
23568 Edited {
23569 transaction_id: clock::Lamport,
23570 },
23571 Reparsed(BufferId),
23572 Focused,
23573 FocusedIn,
23574 Blurred,
23575 DirtyChanged,
23576 Saved,
23577 TitleChanged,
23578 SelectionsChanged {
23579 local: bool,
23580 },
23581 ScrollPositionChanged {
23582 local: bool,
23583 autoscroll: bool,
23584 },
23585 TransactionUndone {
23586 transaction_id: clock::Lamport,
23587 },
23588 TransactionBegun {
23589 transaction_id: clock::Lamport,
23590 },
23591 CursorShapeChanged,
23592 BreadcrumbsChanged,
23593 PushedToNavHistory {
23594 anchor: Anchor,
23595 is_deactivate: bool,
23596 },
23597}
23598
23599impl EventEmitter<EditorEvent> for Editor {}
23600
23601impl Focusable for Editor {
23602 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23603 self.focus_handle.clone()
23604 }
23605}
23606
23607impl Render for Editor {
23608 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23609 let settings = ThemeSettings::get_global(cx);
23610
23611 let mut text_style = match self.mode {
23612 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23613 color: cx.theme().colors().editor_foreground,
23614 font_family: settings.ui_font.family.clone(),
23615 font_features: settings.ui_font.features.clone(),
23616 font_fallbacks: settings.ui_font.fallbacks.clone(),
23617 font_size: rems(0.875).into(),
23618 font_weight: settings.ui_font.weight,
23619 line_height: relative(settings.buffer_line_height.value()),
23620 ..Default::default()
23621 },
23622 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23623 color: cx.theme().colors().editor_foreground,
23624 font_family: settings.buffer_font.family.clone(),
23625 font_features: settings.buffer_font.features.clone(),
23626 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23627 font_size: settings.buffer_font_size(cx).into(),
23628 font_weight: settings.buffer_font.weight,
23629 line_height: relative(settings.buffer_line_height.value()),
23630 ..Default::default()
23631 },
23632 };
23633 if let Some(text_style_refinement) = &self.text_style_refinement {
23634 text_style.refine(text_style_refinement)
23635 }
23636
23637 let background = match self.mode {
23638 EditorMode::SingleLine => cx.theme().system().transparent,
23639 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23640 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23641 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23642 };
23643
23644 EditorElement::new(
23645 &cx.entity(),
23646 EditorStyle {
23647 background,
23648 border: cx.theme().colors().border,
23649 local_player: cx.theme().players().local(),
23650 text: text_style,
23651 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23652 syntax: cx.theme().syntax().clone(),
23653 status: cx.theme().status().clone(),
23654 inlay_hints_style: make_inlay_hints_style(cx),
23655 edit_prediction_styles: make_suggestion_styles(cx),
23656 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23657 show_underlines: self.diagnostics_enabled(),
23658 },
23659 )
23660 }
23661}
23662
23663impl EntityInputHandler for Editor {
23664 fn text_for_range(
23665 &mut self,
23666 range_utf16: Range<usize>,
23667 adjusted_range: &mut Option<Range<usize>>,
23668 _: &mut Window,
23669 cx: &mut Context<Self>,
23670 ) -> Option<String> {
23671 let snapshot = self.buffer.read(cx).read(cx);
23672 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23673 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23674 if (start.0..end.0) != range_utf16 {
23675 adjusted_range.replace(start.0..end.0);
23676 }
23677 Some(snapshot.text_for_range(start..end).collect())
23678 }
23679
23680 fn selected_text_range(
23681 &mut self,
23682 ignore_disabled_input: bool,
23683 _: &mut Window,
23684 cx: &mut Context<Self>,
23685 ) -> Option<UTF16Selection> {
23686 // Prevent the IME menu from appearing when holding down an alphabetic key
23687 // while input is disabled.
23688 if !ignore_disabled_input && !self.input_enabled {
23689 return None;
23690 }
23691
23692 let selection = self.selections.newest::<OffsetUtf16>(cx);
23693 let range = selection.range();
23694
23695 Some(UTF16Selection {
23696 range: range.start.0..range.end.0,
23697 reversed: selection.reversed,
23698 })
23699 }
23700
23701 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23702 let snapshot = self.buffer.read(cx).read(cx);
23703 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23704 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23705 }
23706
23707 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23708 self.clear_highlights::<InputComposition>(cx);
23709 self.ime_transaction.take();
23710 }
23711
23712 fn replace_text_in_range(
23713 &mut self,
23714 range_utf16: Option<Range<usize>>,
23715 text: &str,
23716 window: &mut Window,
23717 cx: &mut Context<Self>,
23718 ) {
23719 if !self.input_enabled {
23720 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23721 return;
23722 }
23723
23724 self.transact(window, cx, |this, window, cx| {
23725 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23726 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23727 Some(this.selection_replacement_ranges(range_utf16, cx))
23728 } else {
23729 this.marked_text_ranges(cx)
23730 };
23731
23732 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23733 let newest_selection_id = this.selections.newest_anchor().id;
23734 this.selections
23735 .all::<OffsetUtf16>(cx)
23736 .iter()
23737 .zip(ranges_to_replace.iter())
23738 .find_map(|(selection, range)| {
23739 if selection.id == newest_selection_id {
23740 Some(
23741 (range.start.0 as isize - selection.head().0 as isize)
23742 ..(range.end.0 as isize - selection.head().0 as isize),
23743 )
23744 } else {
23745 None
23746 }
23747 })
23748 });
23749
23750 cx.emit(EditorEvent::InputHandled {
23751 utf16_range_to_replace: range_to_replace,
23752 text: text.into(),
23753 });
23754
23755 if let Some(new_selected_ranges) = new_selected_ranges {
23756 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23757 selections.select_ranges(new_selected_ranges)
23758 });
23759 this.backspace(&Default::default(), window, cx);
23760 }
23761
23762 this.handle_input(text, window, cx);
23763 });
23764
23765 if let Some(transaction) = self.ime_transaction {
23766 self.buffer.update(cx, |buffer, cx| {
23767 buffer.group_until_transaction(transaction, cx);
23768 });
23769 }
23770
23771 self.unmark_text(window, cx);
23772 }
23773
23774 fn replace_and_mark_text_in_range(
23775 &mut self,
23776 range_utf16: Option<Range<usize>>,
23777 text: &str,
23778 new_selected_range_utf16: Option<Range<usize>>,
23779 window: &mut Window,
23780 cx: &mut Context<Self>,
23781 ) {
23782 if !self.input_enabled {
23783 return;
23784 }
23785
23786 let transaction = self.transact(window, cx, |this, window, cx| {
23787 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23788 let snapshot = this.buffer.read(cx).read(cx);
23789 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23790 for marked_range in &mut marked_ranges {
23791 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23792 marked_range.start.0 += relative_range_utf16.start;
23793 marked_range.start =
23794 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23795 marked_range.end =
23796 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23797 }
23798 }
23799 Some(marked_ranges)
23800 } else if let Some(range_utf16) = range_utf16 {
23801 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23802 Some(this.selection_replacement_ranges(range_utf16, cx))
23803 } else {
23804 None
23805 };
23806
23807 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23808 let newest_selection_id = this.selections.newest_anchor().id;
23809 this.selections
23810 .all::<OffsetUtf16>(cx)
23811 .iter()
23812 .zip(ranges_to_replace.iter())
23813 .find_map(|(selection, range)| {
23814 if selection.id == newest_selection_id {
23815 Some(
23816 (range.start.0 as isize - selection.head().0 as isize)
23817 ..(range.end.0 as isize - selection.head().0 as isize),
23818 )
23819 } else {
23820 None
23821 }
23822 })
23823 });
23824
23825 cx.emit(EditorEvent::InputHandled {
23826 utf16_range_to_replace: range_to_replace,
23827 text: text.into(),
23828 });
23829
23830 if let Some(ranges) = ranges_to_replace {
23831 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23832 s.select_ranges(ranges)
23833 });
23834 }
23835
23836 let marked_ranges = {
23837 let snapshot = this.buffer.read(cx).read(cx);
23838 this.selections
23839 .disjoint_anchors_arc()
23840 .iter()
23841 .map(|selection| {
23842 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23843 })
23844 .collect::<Vec<_>>()
23845 };
23846
23847 if text.is_empty() {
23848 this.unmark_text(window, cx);
23849 } else {
23850 this.highlight_text::<InputComposition>(
23851 marked_ranges.clone(),
23852 HighlightStyle {
23853 underline: Some(UnderlineStyle {
23854 thickness: px(1.),
23855 color: None,
23856 wavy: false,
23857 }),
23858 ..Default::default()
23859 },
23860 cx,
23861 );
23862 }
23863
23864 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23865 let use_autoclose = this.use_autoclose;
23866 let use_auto_surround = this.use_auto_surround;
23867 this.set_use_autoclose(false);
23868 this.set_use_auto_surround(false);
23869 this.handle_input(text, window, cx);
23870 this.set_use_autoclose(use_autoclose);
23871 this.set_use_auto_surround(use_auto_surround);
23872
23873 if let Some(new_selected_range) = new_selected_range_utf16 {
23874 let snapshot = this.buffer.read(cx).read(cx);
23875 let new_selected_ranges = marked_ranges
23876 .into_iter()
23877 .map(|marked_range| {
23878 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23879 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23880 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23881 snapshot.clip_offset_utf16(new_start, Bias::Left)
23882 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23883 })
23884 .collect::<Vec<_>>();
23885
23886 drop(snapshot);
23887 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23888 selections.select_ranges(new_selected_ranges)
23889 });
23890 }
23891 });
23892
23893 self.ime_transaction = self.ime_transaction.or(transaction);
23894 if let Some(transaction) = self.ime_transaction {
23895 self.buffer.update(cx, |buffer, cx| {
23896 buffer.group_until_transaction(transaction, cx);
23897 });
23898 }
23899
23900 if self.text_highlights::<InputComposition>(cx).is_none() {
23901 self.ime_transaction.take();
23902 }
23903 }
23904
23905 fn bounds_for_range(
23906 &mut self,
23907 range_utf16: Range<usize>,
23908 element_bounds: gpui::Bounds<Pixels>,
23909 window: &mut Window,
23910 cx: &mut Context<Self>,
23911 ) -> Option<gpui::Bounds<Pixels>> {
23912 let text_layout_details = self.text_layout_details(window);
23913 let CharacterDimensions {
23914 em_width,
23915 em_advance,
23916 line_height,
23917 } = self.character_dimensions(window);
23918
23919 let snapshot = self.snapshot(window, cx);
23920 let scroll_position = snapshot.scroll_position();
23921 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
23922
23923 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23924 let x = Pixels::from(
23925 ScrollOffset::from(
23926 snapshot.x_for_display_point(start, &text_layout_details)
23927 + self.gutter_dimensions.full_width(),
23928 ) - scroll_left,
23929 );
23930 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
23931
23932 Some(Bounds {
23933 origin: element_bounds.origin + point(x, y),
23934 size: size(em_width, line_height),
23935 })
23936 }
23937
23938 fn character_index_for_point(
23939 &mut self,
23940 point: gpui::Point<Pixels>,
23941 _window: &mut Window,
23942 _cx: &mut Context<Self>,
23943 ) -> Option<usize> {
23944 let position_map = self.last_position_map.as_ref()?;
23945 if !position_map.text_hitbox.contains(&point) {
23946 return None;
23947 }
23948 let display_point = position_map.point_for_position(point).previous_valid;
23949 let anchor = position_map
23950 .snapshot
23951 .display_point_to_anchor(display_point, Bias::Left);
23952 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23953 Some(utf16_offset.0)
23954 }
23955}
23956
23957trait SelectionExt {
23958 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23959 fn spanned_rows(
23960 &self,
23961 include_end_if_at_line_start: bool,
23962 map: &DisplaySnapshot,
23963 ) -> Range<MultiBufferRow>;
23964}
23965
23966impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23967 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23968 let start = self
23969 .start
23970 .to_point(&map.buffer_snapshot)
23971 .to_display_point(map);
23972 let end = self
23973 .end
23974 .to_point(&map.buffer_snapshot)
23975 .to_display_point(map);
23976 if self.reversed {
23977 end..start
23978 } else {
23979 start..end
23980 }
23981 }
23982
23983 fn spanned_rows(
23984 &self,
23985 include_end_if_at_line_start: bool,
23986 map: &DisplaySnapshot,
23987 ) -> Range<MultiBufferRow> {
23988 let start = self.start.to_point(&map.buffer_snapshot);
23989 let mut end = self.end.to_point(&map.buffer_snapshot);
23990 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23991 end.row -= 1;
23992 }
23993
23994 let buffer_start = map.prev_line_boundary(start).0;
23995 let buffer_end = map.next_line_boundary(end).0;
23996 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23997 }
23998}
23999
24000impl<T: InvalidationRegion> InvalidationStack<T> {
24001 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24002 where
24003 S: Clone + ToOffset,
24004 {
24005 while let Some(region) = self.last() {
24006 let all_selections_inside_invalidation_ranges =
24007 if selections.len() == region.ranges().len() {
24008 selections
24009 .iter()
24010 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24011 .all(|(selection, invalidation_range)| {
24012 let head = selection.head().to_offset(buffer);
24013 invalidation_range.start <= head && invalidation_range.end >= head
24014 })
24015 } else {
24016 false
24017 };
24018
24019 if all_selections_inside_invalidation_ranges {
24020 break;
24021 } else {
24022 self.pop();
24023 }
24024 }
24025 }
24026}
24027
24028impl<T> Default for InvalidationStack<T> {
24029 fn default() -> Self {
24030 Self(Default::default())
24031 }
24032}
24033
24034impl<T> Deref for InvalidationStack<T> {
24035 type Target = Vec<T>;
24036
24037 fn deref(&self) -> &Self::Target {
24038 &self.0
24039 }
24040}
24041
24042impl<T> DerefMut for InvalidationStack<T> {
24043 fn deref_mut(&mut self) -> &mut Self::Target {
24044 &mut self.0
24045 }
24046}
24047
24048impl InvalidationRegion for SnippetState {
24049 fn ranges(&self) -> &[Range<Anchor>] {
24050 &self.ranges[self.active_index]
24051 }
24052}
24053
24054fn edit_prediction_edit_text(
24055 current_snapshot: &BufferSnapshot,
24056 edits: &[(Range<Anchor>, String)],
24057 edit_preview: &EditPreview,
24058 include_deletions: bool,
24059 cx: &App,
24060) -> HighlightedText {
24061 let edits = edits
24062 .iter()
24063 .map(|(anchor, text)| {
24064 (
24065 anchor.start.text_anchor..anchor.end.text_anchor,
24066 text.clone(),
24067 )
24068 })
24069 .collect::<Vec<_>>();
24070
24071 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24072}
24073
24074fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24075 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24076 // Just show the raw edit text with basic styling
24077 let mut text = String::new();
24078 let mut highlights = Vec::new();
24079
24080 let insertion_highlight_style = HighlightStyle {
24081 color: Some(cx.theme().colors().text),
24082 ..Default::default()
24083 };
24084
24085 for (_, edit_text) in edits {
24086 let start_offset = text.len();
24087 text.push_str(edit_text);
24088 let end_offset = text.len();
24089
24090 if start_offset < end_offset {
24091 highlights.push((start_offset..end_offset, insertion_highlight_style));
24092 }
24093 }
24094
24095 HighlightedText {
24096 text: text.into(),
24097 highlights,
24098 }
24099}
24100
24101pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24102 match severity {
24103 lsp::DiagnosticSeverity::ERROR => colors.error,
24104 lsp::DiagnosticSeverity::WARNING => colors.warning,
24105 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24106 lsp::DiagnosticSeverity::HINT => colors.info,
24107 _ => colors.ignored,
24108 }
24109}
24110
24111pub fn styled_runs_for_code_label<'a>(
24112 label: &'a CodeLabel,
24113 syntax_theme: &'a theme::SyntaxTheme,
24114) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24115 let fade_out = HighlightStyle {
24116 fade_out: Some(0.35),
24117 ..Default::default()
24118 };
24119
24120 let mut prev_end = label.filter_range.end;
24121 label
24122 .runs
24123 .iter()
24124 .enumerate()
24125 .flat_map(move |(ix, (range, highlight_id))| {
24126 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24127 style
24128 } else {
24129 return Default::default();
24130 };
24131 let muted_style = style.highlight(fade_out);
24132
24133 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24134 if range.start >= label.filter_range.end {
24135 if range.start > prev_end {
24136 runs.push((prev_end..range.start, fade_out));
24137 }
24138 runs.push((range.clone(), muted_style));
24139 } else if range.end <= label.filter_range.end {
24140 runs.push((range.clone(), style));
24141 } else {
24142 runs.push((range.start..label.filter_range.end, style));
24143 runs.push((label.filter_range.end..range.end, muted_style));
24144 }
24145 prev_end = cmp::max(prev_end, range.end);
24146
24147 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24148 runs.push((prev_end..label.text.len(), fade_out));
24149 }
24150
24151 runs
24152 })
24153}
24154
24155pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24156 let mut prev_index = 0;
24157 let mut prev_codepoint: Option<char> = None;
24158 text.char_indices()
24159 .chain([(text.len(), '\0')])
24160 .filter_map(move |(index, codepoint)| {
24161 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24162 let is_boundary = index == text.len()
24163 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24164 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24165 if is_boundary {
24166 let chunk = &text[prev_index..index];
24167 prev_index = index;
24168 Some(chunk)
24169 } else {
24170 None
24171 }
24172 })
24173}
24174
24175pub trait RangeToAnchorExt: Sized {
24176 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24177
24178 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24179 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
24180 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24181 }
24182}
24183
24184impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24185 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24186 let start_offset = self.start.to_offset(snapshot);
24187 let end_offset = self.end.to_offset(snapshot);
24188 if start_offset == end_offset {
24189 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24190 } else {
24191 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24192 }
24193 }
24194}
24195
24196pub trait RowExt {
24197 fn as_f64(&self) -> f64;
24198
24199 fn next_row(&self) -> Self;
24200
24201 fn previous_row(&self) -> Self;
24202
24203 fn minus(&self, other: Self) -> u32;
24204}
24205
24206impl RowExt for DisplayRow {
24207 fn as_f64(&self) -> f64 {
24208 self.0 as _
24209 }
24210
24211 fn next_row(&self) -> Self {
24212 Self(self.0 + 1)
24213 }
24214
24215 fn previous_row(&self) -> Self {
24216 Self(self.0.saturating_sub(1))
24217 }
24218
24219 fn minus(&self, other: Self) -> u32 {
24220 self.0 - other.0
24221 }
24222}
24223
24224impl RowExt for MultiBufferRow {
24225 fn as_f64(&self) -> f64 {
24226 self.0 as _
24227 }
24228
24229 fn next_row(&self) -> Self {
24230 Self(self.0 + 1)
24231 }
24232
24233 fn previous_row(&self) -> Self {
24234 Self(self.0.saturating_sub(1))
24235 }
24236
24237 fn minus(&self, other: Self) -> u32 {
24238 self.0 - other.0
24239 }
24240}
24241
24242trait RowRangeExt {
24243 type Row;
24244
24245 fn len(&self) -> usize;
24246
24247 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24248}
24249
24250impl RowRangeExt for Range<MultiBufferRow> {
24251 type Row = MultiBufferRow;
24252
24253 fn len(&self) -> usize {
24254 (self.end.0 - self.start.0) as usize
24255 }
24256
24257 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24258 (self.start.0..self.end.0).map(MultiBufferRow)
24259 }
24260}
24261
24262impl RowRangeExt for Range<DisplayRow> {
24263 type Row = DisplayRow;
24264
24265 fn len(&self) -> usize {
24266 (self.end.0 - self.start.0) as usize
24267 }
24268
24269 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24270 (self.start.0..self.end.0).map(DisplayRow)
24271 }
24272}
24273
24274/// If select range has more than one line, we
24275/// just point the cursor to range.start.
24276fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24277 if range.start.row == range.end.row {
24278 range
24279 } else {
24280 range.start..range.start
24281 }
24282}
24283pub struct KillRing(ClipboardItem);
24284impl Global for KillRing {}
24285
24286const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24287
24288enum BreakpointPromptEditAction {
24289 Log,
24290 Condition,
24291 HitCondition,
24292}
24293
24294struct BreakpointPromptEditor {
24295 pub(crate) prompt: Entity<Editor>,
24296 editor: WeakEntity<Editor>,
24297 breakpoint_anchor: Anchor,
24298 breakpoint: Breakpoint,
24299 edit_action: BreakpointPromptEditAction,
24300 block_ids: HashSet<CustomBlockId>,
24301 editor_margins: Arc<Mutex<EditorMargins>>,
24302 _subscriptions: Vec<Subscription>,
24303}
24304
24305impl BreakpointPromptEditor {
24306 const MAX_LINES: u8 = 4;
24307
24308 fn new(
24309 editor: WeakEntity<Editor>,
24310 breakpoint_anchor: Anchor,
24311 breakpoint: Breakpoint,
24312 edit_action: BreakpointPromptEditAction,
24313 window: &mut Window,
24314 cx: &mut Context<Self>,
24315 ) -> Self {
24316 let base_text = match edit_action {
24317 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24318 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24319 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24320 }
24321 .map(|msg| msg.to_string())
24322 .unwrap_or_default();
24323
24324 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24325 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24326
24327 let prompt = cx.new(|cx| {
24328 let mut prompt = Editor::new(
24329 EditorMode::AutoHeight {
24330 min_lines: 1,
24331 max_lines: Some(Self::MAX_LINES as usize),
24332 },
24333 buffer,
24334 None,
24335 window,
24336 cx,
24337 );
24338 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24339 prompt.set_show_cursor_when_unfocused(false, cx);
24340 prompt.set_placeholder_text(
24341 match edit_action {
24342 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24343 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24344 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24345 },
24346 window,
24347 cx,
24348 );
24349
24350 prompt
24351 });
24352
24353 Self {
24354 prompt,
24355 editor,
24356 breakpoint_anchor,
24357 breakpoint,
24358 edit_action,
24359 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24360 block_ids: Default::default(),
24361 _subscriptions: vec![],
24362 }
24363 }
24364
24365 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24366 self.block_ids.extend(block_ids)
24367 }
24368
24369 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24370 if let Some(editor) = self.editor.upgrade() {
24371 let message = self
24372 .prompt
24373 .read(cx)
24374 .buffer
24375 .read(cx)
24376 .as_singleton()
24377 .expect("A multi buffer in breakpoint prompt isn't possible")
24378 .read(cx)
24379 .as_rope()
24380 .to_string();
24381
24382 editor.update(cx, |editor, cx| {
24383 editor.edit_breakpoint_at_anchor(
24384 self.breakpoint_anchor,
24385 self.breakpoint.clone(),
24386 match self.edit_action {
24387 BreakpointPromptEditAction::Log => {
24388 BreakpointEditAction::EditLogMessage(message.into())
24389 }
24390 BreakpointPromptEditAction::Condition => {
24391 BreakpointEditAction::EditCondition(message.into())
24392 }
24393 BreakpointPromptEditAction::HitCondition => {
24394 BreakpointEditAction::EditHitCondition(message.into())
24395 }
24396 },
24397 cx,
24398 );
24399
24400 editor.remove_blocks(self.block_ids.clone(), None, cx);
24401 cx.focus_self(window);
24402 });
24403 }
24404 }
24405
24406 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24407 self.editor
24408 .update(cx, |editor, cx| {
24409 editor.remove_blocks(self.block_ids.clone(), None, cx);
24410 window.focus(&editor.focus_handle);
24411 })
24412 .log_err();
24413 }
24414
24415 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24416 let settings = ThemeSettings::get_global(cx);
24417 let text_style = TextStyle {
24418 color: if self.prompt.read(cx).read_only(cx) {
24419 cx.theme().colors().text_disabled
24420 } else {
24421 cx.theme().colors().text
24422 },
24423 font_family: settings.buffer_font.family.clone(),
24424 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24425 font_size: settings.buffer_font_size(cx).into(),
24426 font_weight: settings.buffer_font.weight,
24427 line_height: relative(settings.buffer_line_height.value()),
24428 ..Default::default()
24429 };
24430 EditorElement::new(
24431 &self.prompt,
24432 EditorStyle {
24433 background: cx.theme().colors().editor_background,
24434 local_player: cx.theme().players().local(),
24435 text: text_style,
24436 ..Default::default()
24437 },
24438 )
24439 }
24440}
24441
24442impl Render for BreakpointPromptEditor {
24443 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24444 let editor_margins = *self.editor_margins.lock();
24445 let gutter_dimensions = editor_margins.gutter;
24446 h_flex()
24447 .key_context("Editor")
24448 .bg(cx.theme().colors().editor_background)
24449 .border_y_1()
24450 .border_color(cx.theme().status().info_border)
24451 .size_full()
24452 .py(window.line_height() / 2.5)
24453 .on_action(cx.listener(Self::confirm))
24454 .on_action(cx.listener(Self::cancel))
24455 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24456 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24457 }
24458}
24459
24460impl Focusable for BreakpointPromptEditor {
24461 fn focus_handle(&self, cx: &App) -> FocusHandle {
24462 self.prompt.focus_handle(cx)
24463 }
24464}
24465
24466fn all_edits_insertions_or_deletions(
24467 edits: &Vec<(Range<Anchor>, String)>,
24468 snapshot: &MultiBufferSnapshot,
24469) -> bool {
24470 let mut all_insertions = true;
24471 let mut all_deletions = true;
24472
24473 for (range, new_text) in edits.iter() {
24474 let range_is_empty = range.to_offset(snapshot).is_empty();
24475 let text_is_empty = new_text.is_empty();
24476
24477 if range_is_empty != text_is_empty {
24478 if range_is_empty {
24479 all_deletions = false;
24480 } else {
24481 all_insertions = false;
24482 }
24483 } else {
24484 return false;
24485 }
24486
24487 if !all_insertions && !all_deletions {
24488 return false;
24489 }
24490 }
24491 all_insertions || all_deletions
24492}
24493
24494struct MissingEditPredictionKeybindingTooltip;
24495
24496impl Render for MissingEditPredictionKeybindingTooltip {
24497 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24498 ui::tooltip_container(cx, |container, cx| {
24499 container
24500 .flex_shrink_0()
24501 .max_w_80()
24502 .min_h(rems_from_px(124.))
24503 .justify_between()
24504 .child(
24505 v_flex()
24506 .flex_1()
24507 .text_ui_sm(cx)
24508 .child(Label::new("Conflict with Accept Keybinding"))
24509 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24510 )
24511 .child(
24512 h_flex()
24513 .pb_1()
24514 .gap_1()
24515 .items_end()
24516 .w_full()
24517 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24518 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24519 }))
24520 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24521 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24522 })),
24523 )
24524 })
24525 }
24526}
24527
24528#[derive(Debug, Clone, Copy, PartialEq)]
24529pub struct LineHighlight {
24530 pub background: Background,
24531 pub border: Option<gpui::Hsla>,
24532 pub include_gutter: bool,
24533 pub type_id: Option<TypeId>,
24534}
24535
24536struct LineManipulationResult {
24537 pub new_text: String,
24538 pub line_count_before: usize,
24539 pub line_count_after: usize,
24540}
24541
24542fn render_diff_hunk_controls(
24543 row: u32,
24544 status: &DiffHunkStatus,
24545 hunk_range: Range<Anchor>,
24546 is_created_file: bool,
24547 line_height: Pixels,
24548 editor: &Entity<Editor>,
24549 _window: &mut Window,
24550 cx: &mut App,
24551) -> AnyElement {
24552 h_flex()
24553 .h(line_height)
24554 .mr_1()
24555 .gap_1()
24556 .px_0p5()
24557 .pb_1()
24558 .border_x_1()
24559 .border_b_1()
24560 .border_color(cx.theme().colors().border_variant)
24561 .rounded_b_lg()
24562 .bg(cx.theme().colors().editor_background)
24563 .gap_1()
24564 .block_mouse_except_scroll()
24565 .shadow_md()
24566 .child(if status.has_secondary_hunk() {
24567 Button::new(("stage", row as u64), "Stage")
24568 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24569 .tooltip({
24570 let focus_handle = editor.focus_handle(cx);
24571 move |window, cx| {
24572 Tooltip::for_action_in(
24573 "Stage Hunk",
24574 &::git::ToggleStaged,
24575 &focus_handle,
24576 window,
24577 cx,
24578 )
24579 }
24580 })
24581 .on_click({
24582 let editor = editor.clone();
24583 move |_event, _window, cx| {
24584 editor.update(cx, |editor, cx| {
24585 editor.stage_or_unstage_diff_hunks(
24586 true,
24587 vec![hunk_range.start..hunk_range.start],
24588 cx,
24589 );
24590 });
24591 }
24592 })
24593 } else {
24594 Button::new(("unstage", row as u64), "Unstage")
24595 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24596 .tooltip({
24597 let focus_handle = editor.focus_handle(cx);
24598 move |window, cx| {
24599 Tooltip::for_action_in(
24600 "Unstage Hunk",
24601 &::git::ToggleStaged,
24602 &focus_handle,
24603 window,
24604 cx,
24605 )
24606 }
24607 })
24608 .on_click({
24609 let editor = editor.clone();
24610 move |_event, _window, cx| {
24611 editor.update(cx, |editor, cx| {
24612 editor.stage_or_unstage_diff_hunks(
24613 false,
24614 vec![hunk_range.start..hunk_range.start],
24615 cx,
24616 );
24617 });
24618 }
24619 })
24620 })
24621 .child(
24622 Button::new(("restore", row as u64), "Restore")
24623 .tooltip({
24624 let focus_handle = editor.focus_handle(cx);
24625 move |window, cx| {
24626 Tooltip::for_action_in(
24627 "Restore Hunk",
24628 &::git::Restore,
24629 &focus_handle,
24630 window,
24631 cx,
24632 )
24633 }
24634 })
24635 .on_click({
24636 let editor = editor.clone();
24637 move |_event, window, cx| {
24638 editor.update(cx, |editor, cx| {
24639 let snapshot = editor.snapshot(window, cx);
24640 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24641 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24642 });
24643 }
24644 })
24645 .disabled(is_created_file),
24646 )
24647 .when(
24648 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24649 |el| {
24650 el.child(
24651 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24652 .shape(IconButtonShape::Square)
24653 .icon_size(IconSize::Small)
24654 // .disabled(!has_multiple_hunks)
24655 .tooltip({
24656 let focus_handle = editor.focus_handle(cx);
24657 move |window, cx| {
24658 Tooltip::for_action_in(
24659 "Next Hunk",
24660 &GoToHunk,
24661 &focus_handle,
24662 window,
24663 cx,
24664 )
24665 }
24666 })
24667 .on_click({
24668 let editor = editor.clone();
24669 move |_event, window, cx| {
24670 editor.update(cx, |editor, cx| {
24671 let snapshot = editor.snapshot(window, cx);
24672 let position =
24673 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24674 editor.go_to_hunk_before_or_after_position(
24675 &snapshot,
24676 position,
24677 Direction::Next,
24678 window,
24679 cx,
24680 );
24681 editor.expand_selected_diff_hunks(cx);
24682 });
24683 }
24684 }),
24685 )
24686 .child(
24687 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24688 .shape(IconButtonShape::Square)
24689 .icon_size(IconSize::Small)
24690 // .disabled(!has_multiple_hunks)
24691 .tooltip({
24692 let focus_handle = editor.focus_handle(cx);
24693 move |window, cx| {
24694 Tooltip::for_action_in(
24695 "Previous Hunk",
24696 &GoToPreviousHunk,
24697 &focus_handle,
24698 window,
24699 cx,
24700 )
24701 }
24702 })
24703 .on_click({
24704 let editor = editor.clone();
24705 move |_event, window, cx| {
24706 editor.update(cx, |editor, cx| {
24707 let snapshot = editor.snapshot(window, cx);
24708 let point =
24709 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24710 editor.go_to_hunk_before_or_after_position(
24711 &snapshot,
24712 point,
24713 Direction::Prev,
24714 window,
24715 cx,
24716 );
24717 editor.expand_selected_diff_hunks(cx);
24718 });
24719 }
24720 }),
24721 )
24722 },
24723 )
24724 .into_any_element()
24725}
24726
24727pub fn multibuffer_context_lines(cx: &App) -> u32 {
24728 EditorSettings::try_get(cx)
24729 .map(|settings| settings.excerpt_context_lines)
24730 .unwrap_or(2)
24731 .min(32)
24732}