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 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18130 while start_row < end_row {
18131 match self
18132 .snapshot(window, cx)
18133 .crease_for_buffer_row(MultiBufferRow(start_row))
18134 {
18135 Some(crease) => {
18136 let nested_start_row = crease.range().start.row + 1;
18137 let nested_end_row = crease.range().end.row;
18138
18139 if current_level < fold_at_level {
18140 stack.push((nested_start_row, nested_end_row, current_level + 1));
18141 } else if current_level == fold_at_level {
18142 to_fold.push(crease);
18143 }
18144
18145 start_row = nested_end_row + 1;
18146 }
18147 None => start_row += 1,
18148 }
18149 }
18150 }
18151
18152 self.fold_creases(to_fold, true, window, cx);
18153 }
18154
18155 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18156 if self.buffer.read(cx).is_singleton() {
18157 let mut fold_ranges = Vec::new();
18158 let snapshot = self.buffer.read(cx).snapshot(cx);
18159
18160 for row in 0..snapshot.max_row().0 {
18161 if let Some(foldable_range) = self
18162 .snapshot(window, cx)
18163 .crease_for_buffer_row(MultiBufferRow(row))
18164 {
18165 fold_ranges.push(foldable_range);
18166 }
18167 }
18168
18169 self.fold_creases(fold_ranges, true, window, cx);
18170 } else {
18171 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18172 editor
18173 .update_in(cx, |editor, _, cx| {
18174 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18175 editor.fold_buffer(buffer_id, cx);
18176 }
18177 })
18178 .ok();
18179 });
18180 }
18181 }
18182
18183 pub fn fold_function_bodies(
18184 &mut self,
18185 _: &actions::FoldFunctionBodies,
18186 window: &mut Window,
18187 cx: &mut Context<Self>,
18188 ) {
18189 let snapshot = self.buffer.read(cx).snapshot(cx);
18190
18191 let ranges = snapshot
18192 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18193 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18194 .collect::<Vec<_>>();
18195
18196 let creases = ranges
18197 .into_iter()
18198 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18199 .collect();
18200
18201 self.fold_creases(creases, true, window, cx);
18202 }
18203
18204 pub fn fold_recursive(
18205 &mut self,
18206 _: &actions::FoldRecursive,
18207 window: &mut Window,
18208 cx: &mut Context<Self>,
18209 ) {
18210 let mut to_fold = Vec::new();
18211 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18212 let selections = self.selections.all_adjusted(cx);
18213
18214 for selection in selections {
18215 let range = selection.range().sorted();
18216 let buffer_start_row = range.start.row;
18217
18218 if range.start.row != range.end.row {
18219 let mut found = false;
18220 for row in range.start.row..=range.end.row {
18221 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18222 found = true;
18223 to_fold.push(crease);
18224 }
18225 }
18226 if found {
18227 continue;
18228 }
18229 }
18230
18231 for row in (0..=range.start.row).rev() {
18232 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18233 if crease.range().end.row >= buffer_start_row {
18234 to_fold.push(crease);
18235 } else {
18236 break;
18237 }
18238 }
18239 }
18240 }
18241
18242 self.fold_creases(to_fold, true, window, cx);
18243 }
18244
18245 pub fn fold_at(
18246 &mut self,
18247 buffer_row: MultiBufferRow,
18248 window: &mut Window,
18249 cx: &mut Context<Self>,
18250 ) {
18251 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18252
18253 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18254 let autoscroll = self
18255 .selections
18256 .all::<Point>(cx)
18257 .iter()
18258 .any(|selection| crease.range().overlaps(&selection.range()));
18259
18260 self.fold_creases(vec![crease], autoscroll, window, cx);
18261 }
18262 }
18263
18264 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18265 if self.is_singleton(cx) {
18266 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18267 let buffer = &display_map.buffer_snapshot;
18268 let selections = self.selections.all::<Point>(cx);
18269 let ranges = selections
18270 .iter()
18271 .map(|s| {
18272 let range = s.display_range(&display_map).sorted();
18273 let mut start = range.start.to_point(&display_map);
18274 let mut end = range.end.to_point(&display_map);
18275 start.column = 0;
18276 end.column = buffer.line_len(MultiBufferRow(end.row));
18277 start..end
18278 })
18279 .collect::<Vec<_>>();
18280
18281 self.unfold_ranges(&ranges, true, true, cx);
18282 } else {
18283 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18284 let buffer_ids = self
18285 .selections
18286 .disjoint_anchor_ranges()
18287 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18288 .collect::<HashSet<_>>();
18289 for buffer_id in buffer_ids {
18290 self.unfold_buffer(buffer_id, cx);
18291 }
18292 }
18293 }
18294
18295 pub fn unfold_recursive(
18296 &mut self,
18297 _: &UnfoldRecursive,
18298 _window: &mut Window,
18299 cx: &mut Context<Self>,
18300 ) {
18301 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18302 let selections = self.selections.all::<Point>(cx);
18303 let ranges = selections
18304 .iter()
18305 .map(|s| {
18306 let mut range = s.display_range(&display_map).sorted();
18307 *range.start.column_mut() = 0;
18308 *range.end.column_mut() = display_map.line_len(range.end.row());
18309 let start = range.start.to_point(&display_map);
18310 let end = range.end.to_point(&display_map);
18311 start..end
18312 })
18313 .collect::<Vec<_>>();
18314
18315 self.unfold_ranges(&ranges, true, true, cx);
18316 }
18317
18318 pub fn unfold_at(
18319 &mut self,
18320 buffer_row: MultiBufferRow,
18321 _window: &mut Window,
18322 cx: &mut Context<Self>,
18323 ) {
18324 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18325
18326 let intersection_range = Point::new(buffer_row.0, 0)
18327 ..Point::new(
18328 buffer_row.0,
18329 display_map.buffer_snapshot.line_len(buffer_row),
18330 );
18331
18332 let autoscroll = self
18333 .selections
18334 .all::<Point>(cx)
18335 .iter()
18336 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18337
18338 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18339 }
18340
18341 pub fn unfold_all(
18342 &mut self,
18343 _: &actions::UnfoldAll,
18344 _window: &mut Window,
18345 cx: &mut Context<Self>,
18346 ) {
18347 if self.buffer.read(cx).is_singleton() {
18348 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18349 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18350 } else {
18351 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18352 editor
18353 .update(cx, |editor, cx| {
18354 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18355 editor.unfold_buffer(buffer_id, cx);
18356 }
18357 })
18358 .ok();
18359 });
18360 }
18361 }
18362
18363 pub fn fold_selected_ranges(
18364 &mut self,
18365 _: &FoldSelectedRanges,
18366 window: &mut Window,
18367 cx: &mut Context<Self>,
18368 ) {
18369 let selections = self.selections.all_adjusted(cx);
18370 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18371 let ranges = selections
18372 .into_iter()
18373 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18374 .collect::<Vec<_>>();
18375 self.fold_creases(ranges, true, window, cx);
18376 }
18377
18378 pub fn fold_ranges<T: ToOffset + Clone>(
18379 &mut self,
18380 ranges: Vec<Range<T>>,
18381 auto_scroll: bool,
18382 window: &mut Window,
18383 cx: &mut Context<Self>,
18384 ) {
18385 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18386 let ranges = ranges
18387 .into_iter()
18388 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18389 .collect::<Vec<_>>();
18390 self.fold_creases(ranges, auto_scroll, window, cx);
18391 }
18392
18393 pub fn fold_creases<T: ToOffset + Clone>(
18394 &mut self,
18395 creases: Vec<Crease<T>>,
18396 auto_scroll: bool,
18397 _window: &mut Window,
18398 cx: &mut Context<Self>,
18399 ) {
18400 if creases.is_empty() {
18401 return;
18402 }
18403
18404 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18405
18406 if auto_scroll {
18407 self.request_autoscroll(Autoscroll::fit(), cx);
18408 }
18409
18410 cx.notify();
18411
18412 self.scrollbar_marker_state.dirty = true;
18413 self.folds_did_change(cx);
18414 }
18415
18416 /// Removes any folds whose ranges intersect any of the given ranges.
18417 pub fn unfold_ranges<T: ToOffset + Clone>(
18418 &mut self,
18419 ranges: &[Range<T>],
18420 inclusive: bool,
18421 auto_scroll: bool,
18422 cx: &mut Context<Self>,
18423 ) {
18424 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18425 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18426 });
18427 self.folds_did_change(cx);
18428 }
18429
18430 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18431 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18432 return;
18433 }
18434 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18435 self.display_map.update(cx, |display_map, cx| {
18436 display_map.fold_buffers([buffer_id], cx)
18437 });
18438 cx.emit(EditorEvent::BufferFoldToggled {
18439 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18440 folded: true,
18441 });
18442 cx.notify();
18443 }
18444
18445 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18446 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18447 return;
18448 }
18449 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18450 self.display_map.update(cx, |display_map, cx| {
18451 display_map.unfold_buffers([buffer_id], cx);
18452 });
18453 cx.emit(EditorEvent::BufferFoldToggled {
18454 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18455 folded: false,
18456 });
18457 cx.notify();
18458 }
18459
18460 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18461 self.display_map.read(cx).is_buffer_folded(buffer)
18462 }
18463
18464 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18465 self.display_map.read(cx).folded_buffers()
18466 }
18467
18468 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18469 self.display_map.update(cx, |display_map, cx| {
18470 display_map.disable_header_for_buffer(buffer_id, cx);
18471 });
18472 cx.notify();
18473 }
18474
18475 /// Removes any folds with the given ranges.
18476 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18477 &mut self,
18478 ranges: &[Range<T>],
18479 type_id: TypeId,
18480 auto_scroll: bool,
18481 cx: &mut Context<Self>,
18482 ) {
18483 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18484 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18485 });
18486 self.folds_did_change(cx);
18487 }
18488
18489 fn remove_folds_with<T: ToOffset + Clone>(
18490 &mut self,
18491 ranges: &[Range<T>],
18492 auto_scroll: bool,
18493 cx: &mut Context<Self>,
18494 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18495 ) {
18496 if ranges.is_empty() {
18497 return;
18498 }
18499
18500 let mut buffers_affected = HashSet::default();
18501 let multi_buffer = self.buffer().read(cx);
18502 for range in ranges {
18503 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18504 buffers_affected.insert(buffer.read(cx).remote_id());
18505 };
18506 }
18507
18508 self.display_map.update(cx, update);
18509
18510 if auto_scroll {
18511 self.request_autoscroll(Autoscroll::fit(), cx);
18512 }
18513
18514 cx.notify();
18515 self.scrollbar_marker_state.dirty = true;
18516 self.active_indent_guides_state.dirty = true;
18517 }
18518
18519 pub fn update_renderer_widths(
18520 &mut self,
18521 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18522 cx: &mut Context<Self>,
18523 ) -> bool {
18524 self.display_map
18525 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18526 }
18527
18528 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18529 self.display_map.read(cx).fold_placeholder.clone()
18530 }
18531
18532 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18533 self.buffer.update(cx, |buffer, cx| {
18534 buffer.set_all_diff_hunks_expanded(cx);
18535 });
18536 }
18537
18538 pub fn expand_all_diff_hunks(
18539 &mut self,
18540 _: &ExpandAllDiffHunks,
18541 _window: &mut Window,
18542 cx: &mut Context<Self>,
18543 ) {
18544 self.buffer.update(cx, |buffer, cx| {
18545 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18546 });
18547 }
18548
18549 pub fn toggle_selected_diff_hunks(
18550 &mut self,
18551 _: &ToggleSelectedDiffHunks,
18552 _window: &mut Window,
18553 cx: &mut Context<Self>,
18554 ) {
18555 let ranges: Vec<_> = self
18556 .selections
18557 .disjoint_anchors()
18558 .iter()
18559 .map(|s| s.range())
18560 .collect();
18561 self.toggle_diff_hunks_in_ranges(ranges, cx);
18562 }
18563
18564 pub fn diff_hunks_in_ranges<'a>(
18565 &'a self,
18566 ranges: &'a [Range<Anchor>],
18567 buffer: &'a MultiBufferSnapshot,
18568 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18569 ranges.iter().flat_map(move |range| {
18570 let end_excerpt_id = range.end.excerpt_id;
18571 let range = range.to_point(buffer);
18572 let mut peek_end = range.end;
18573 if range.end.row < buffer.max_row().0 {
18574 peek_end = Point::new(range.end.row + 1, 0);
18575 }
18576 buffer
18577 .diff_hunks_in_range(range.start..peek_end)
18578 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18579 })
18580 }
18581
18582 pub fn has_stageable_diff_hunks_in_ranges(
18583 &self,
18584 ranges: &[Range<Anchor>],
18585 snapshot: &MultiBufferSnapshot,
18586 ) -> bool {
18587 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18588 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18589 }
18590
18591 pub fn toggle_staged_selected_diff_hunks(
18592 &mut self,
18593 _: &::git::ToggleStaged,
18594 _: &mut Window,
18595 cx: &mut Context<Self>,
18596 ) {
18597 let snapshot = self.buffer.read(cx).snapshot(cx);
18598 let ranges: Vec<_> = self
18599 .selections
18600 .disjoint_anchors()
18601 .iter()
18602 .map(|s| s.range())
18603 .collect();
18604 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18605 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18606 }
18607
18608 pub fn set_render_diff_hunk_controls(
18609 &mut self,
18610 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18611 cx: &mut Context<Self>,
18612 ) {
18613 self.render_diff_hunk_controls = render_diff_hunk_controls;
18614 cx.notify();
18615 }
18616
18617 pub fn stage_and_next(
18618 &mut self,
18619 _: &::git::StageAndNext,
18620 window: &mut Window,
18621 cx: &mut Context<Self>,
18622 ) {
18623 self.do_stage_or_unstage_and_next(true, window, cx);
18624 }
18625
18626 pub fn unstage_and_next(
18627 &mut self,
18628 _: &::git::UnstageAndNext,
18629 window: &mut Window,
18630 cx: &mut Context<Self>,
18631 ) {
18632 self.do_stage_or_unstage_and_next(false, window, cx);
18633 }
18634
18635 pub fn stage_or_unstage_diff_hunks(
18636 &mut self,
18637 stage: bool,
18638 ranges: Vec<Range<Anchor>>,
18639 cx: &mut Context<Self>,
18640 ) {
18641 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18642 cx.spawn(async move |this, cx| {
18643 task.await?;
18644 this.update(cx, |this, cx| {
18645 let snapshot = this.buffer.read(cx).snapshot(cx);
18646 let chunk_by = this
18647 .diff_hunks_in_ranges(&ranges, &snapshot)
18648 .chunk_by(|hunk| hunk.buffer_id);
18649 for (buffer_id, hunks) in &chunk_by {
18650 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18651 }
18652 })
18653 })
18654 .detach_and_log_err(cx);
18655 }
18656
18657 fn save_buffers_for_ranges_if_needed(
18658 &mut self,
18659 ranges: &[Range<Anchor>],
18660 cx: &mut Context<Editor>,
18661 ) -> Task<Result<()>> {
18662 let multibuffer = self.buffer.read(cx);
18663 let snapshot = multibuffer.read(cx);
18664 let buffer_ids: HashSet<_> = ranges
18665 .iter()
18666 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18667 .collect();
18668 drop(snapshot);
18669
18670 let mut buffers = HashSet::default();
18671 for buffer_id in buffer_ids {
18672 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18673 let buffer = buffer_entity.read(cx);
18674 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18675 {
18676 buffers.insert(buffer_entity);
18677 }
18678 }
18679 }
18680
18681 if let Some(project) = &self.project {
18682 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18683 } else {
18684 Task::ready(Ok(()))
18685 }
18686 }
18687
18688 fn do_stage_or_unstage_and_next(
18689 &mut self,
18690 stage: bool,
18691 window: &mut Window,
18692 cx: &mut Context<Self>,
18693 ) {
18694 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18695
18696 if ranges.iter().any(|range| range.start != range.end) {
18697 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18698 return;
18699 }
18700
18701 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18702 let snapshot = self.snapshot(window, cx);
18703 let position = self.selections.newest::<Point>(cx).head();
18704 let mut row = snapshot
18705 .buffer_snapshot
18706 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18707 .find(|hunk| hunk.row_range.start.0 > position.row)
18708 .map(|hunk| hunk.row_range.start);
18709
18710 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18711 // Outside of the project diff editor, wrap around to the beginning.
18712 if !all_diff_hunks_expanded {
18713 row = row.or_else(|| {
18714 snapshot
18715 .buffer_snapshot
18716 .diff_hunks_in_range(Point::zero()..position)
18717 .find(|hunk| hunk.row_range.end.0 < position.row)
18718 .map(|hunk| hunk.row_range.start)
18719 });
18720 }
18721
18722 if let Some(row) = row {
18723 let destination = Point::new(row.0, 0);
18724 let autoscroll = Autoscroll::center();
18725
18726 self.unfold_ranges(&[destination..destination], false, false, cx);
18727 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18728 s.select_ranges([destination..destination]);
18729 });
18730 }
18731 }
18732
18733 fn do_stage_or_unstage(
18734 &self,
18735 stage: bool,
18736 buffer_id: BufferId,
18737 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18738 cx: &mut App,
18739 ) -> Option<()> {
18740 let project = self.project()?;
18741 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18742 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18743 let buffer_snapshot = buffer.read(cx).snapshot();
18744 let file_exists = buffer_snapshot
18745 .file()
18746 .is_some_and(|file| file.disk_state().exists());
18747 diff.update(cx, |diff, cx| {
18748 diff.stage_or_unstage_hunks(
18749 stage,
18750 &hunks
18751 .map(|hunk| buffer_diff::DiffHunk {
18752 buffer_range: hunk.buffer_range,
18753 diff_base_byte_range: hunk.diff_base_byte_range,
18754 secondary_status: hunk.secondary_status,
18755 range: Point::zero()..Point::zero(), // unused
18756 })
18757 .collect::<Vec<_>>(),
18758 &buffer_snapshot,
18759 file_exists,
18760 cx,
18761 )
18762 });
18763 None
18764 }
18765
18766 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18767 let ranges: Vec<_> = self
18768 .selections
18769 .disjoint_anchors()
18770 .iter()
18771 .map(|s| s.range())
18772 .collect();
18773 self.buffer
18774 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18775 }
18776
18777 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18778 self.buffer.update(cx, |buffer, cx| {
18779 let ranges = vec![Anchor::min()..Anchor::max()];
18780 if !buffer.all_diff_hunks_expanded()
18781 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18782 {
18783 buffer.collapse_diff_hunks(ranges, cx);
18784 true
18785 } else {
18786 false
18787 }
18788 })
18789 }
18790
18791 fn toggle_diff_hunks_in_ranges(
18792 &mut self,
18793 ranges: Vec<Range<Anchor>>,
18794 cx: &mut Context<Editor>,
18795 ) {
18796 self.buffer.update(cx, |buffer, cx| {
18797 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18798 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18799 })
18800 }
18801
18802 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18803 self.buffer.update(cx, |buffer, cx| {
18804 let snapshot = buffer.snapshot(cx);
18805 let excerpt_id = range.end.excerpt_id;
18806 let point_range = range.to_point(&snapshot);
18807 let expand = !buffer.single_hunk_is_expanded(range, cx);
18808 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18809 })
18810 }
18811
18812 pub(crate) fn apply_all_diff_hunks(
18813 &mut self,
18814 _: &ApplyAllDiffHunks,
18815 window: &mut Window,
18816 cx: &mut Context<Self>,
18817 ) {
18818 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18819
18820 let buffers = self.buffer.read(cx).all_buffers();
18821 for branch_buffer in buffers {
18822 branch_buffer.update(cx, |branch_buffer, cx| {
18823 branch_buffer.merge_into_base(Vec::new(), cx);
18824 });
18825 }
18826
18827 if let Some(project) = self.project.clone() {
18828 self.save(
18829 SaveOptions {
18830 format: true,
18831 autosave: false,
18832 },
18833 project,
18834 window,
18835 cx,
18836 )
18837 .detach_and_log_err(cx);
18838 }
18839 }
18840
18841 pub(crate) fn apply_selected_diff_hunks(
18842 &mut self,
18843 _: &ApplyDiffHunk,
18844 window: &mut Window,
18845 cx: &mut Context<Self>,
18846 ) {
18847 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18848 let snapshot = self.snapshot(window, cx);
18849 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18850 let mut ranges_by_buffer = HashMap::default();
18851 self.transact(window, cx, |editor, _window, cx| {
18852 for hunk in hunks {
18853 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18854 ranges_by_buffer
18855 .entry(buffer.clone())
18856 .or_insert_with(Vec::new)
18857 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18858 }
18859 }
18860
18861 for (buffer, ranges) in ranges_by_buffer {
18862 buffer.update(cx, |buffer, cx| {
18863 buffer.merge_into_base(ranges, cx);
18864 });
18865 }
18866 });
18867
18868 if let Some(project) = self.project.clone() {
18869 self.save(
18870 SaveOptions {
18871 format: true,
18872 autosave: false,
18873 },
18874 project,
18875 window,
18876 cx,
18877 )
18878 .detach_and_log_err(cx);
18879 }
18880 }
18881
18882 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18883 if hovered != self.gutter_hovered {
18884 self.gutter_hovered = hovered;
18885 cx.notify();
18886 }
18887 }
18888
18889 pub fn insert_blocks(
18890 &mut self,
18891 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18892 autoscroll: Option<Autoscroll>,
18893 cx: &mut Context<Self>,
18894 ) -> Vec<CustomBlockId> {
18895 let blocks = self
18896 .display_map
18897 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18898 if let Some(autoscroll) = autoscroll {
18899 self.request_autoscroll(autoscroll, cx);
18900 }
18901 cx.notify();
18902 blocks
18903 }
18904
18905 pub fn resize_blocks(
18906 &mut self,
18907 heights: HashMap<CustomBlockId, u32>,
18908 autoscroll: Option<Autoscroll>,
18909 cx: &mut Context<Self>,
18910 ) {
18911 self.display_map
18912 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18913 if let Some(autoscroll) = autoscroll {
18914 self.request_autoscroll(autoscroll, cx);
18915 }
18916 cx.notify();
18917 }
18918
18919 pub fn replace_blocks(
18920 &mut self,
18921 renderers: HashMap<CustomBlockId, RenderBlock>,
18922 autoscroll: Option<Autoscroll>,
18923 cx: &mut Context<Self>,
18924 ) {
18925 self.display_map
18926 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18927 if let Some(autoscroll) = autoscroll {
18928 self.request_autoscroll(autoscroll, cx);
18929 }
18930 cx.notify();
18931 }
18932
18933 pub fn remove_blocks(
18934 &mut self,
18935 block_ids: HashSet<CustomBlockId>,
18936 autoscroll: Option<Autoscroll>,
18937 cx: &mut Context<Self>,
18938 ) {
18939 self.display_map.update(cx, |display_map, cx| {
18940 display_map.remove_blocks(block_ids, cx)
18941 });
18942 if let Some(autoscroll) = autoscroll {
18943 self.request_autoscroll(autoscroll, cx);
18944 }
18945 cx.notify();
18946 }
18947
18948 pub fn row_for_block(
18949 &self,
18950 block_id: CustomBlockId,
18951 cx: &mut Context<Self>,
18952 ) -> Option<DisplayRow> {
18953 self.display_map
18954 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18955 }
18956
18957 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18958 self.focused_block = Some(focused_block);
18959 }
18960
18961 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18962 self.focused_block.take()
18963 }
18964
18965 pub fn insert_creases(
18966 &mut self,
18967 creases: impl IntoIterator<Item = Crease<Anchor>>,
18968 cx: &mut Context<Self>,
18969 ) -> Vec<CreaseId> {
18970 self.display_map
18971 .update(cx, |map, cx| map.insert_creases(creases, cx))
18972 }
18973
18974 pub fn remove_creases(
18975 &mut self,
18976 ids: impl IntoIterator<Item = CreaseId>,
18977 cx: &mut Context<Self>,
18978 ) -> Vec<(CreaseId, Range<Anchor>)> {
18979 self.display_map
18980 .update(cx, |map, cx| map.remove_creases(ids, cx))
18981 }
18982
18983 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18984 self.display_map
18985 .update(cx, |map, cx| map.snapshot(cx))
18986 .longest_row()
18987 }
18988
18989 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18990 self.display_map
18991 .update(cx, |map, cx| map.snapshot(cx))
18992 .max_point()
18993 }
18994
18995 pub fn text(&self, cx: &App) -> String {
18996 self.buffer.read(cx).read(cx).text()
18997 }
18998
18999 pub fn is_empty(&self, cx: &App) -> bool {
19000 self.buffer.read(cx).read(cx).is_empty()
19001 }
19002
19003 pub fn text_option(&self, cx: &App) -> Option<String> {
19004 let text = self.text(cx);
19005 let text = text.trim();
19006
19007 if text.is_empty() {
19008 return None;
19009 }
19010
19011 Some(text.to_string())
19012 }
19013
19014 pub fn set_text(
19015 &mut self,
19016 text: impl Into<Arc<str>>,
19017 window: &mut Window,
19018 cx: &mut Context<Self>,
19019 ) {
19020 self.transact(window, cx, |this, _, cx| {
19021 this.buffer
19022 .read(cx)
19023 .as_singleton()
19024 .expect("you can only call set_text on editors for singleton buffers")
19025 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19026 });
19027 }
19028
19029 pub fn display_text(&self, cx: &mut App) -> String {
19030 self.display_map
19031 .update(cx, |map, cx| map.snapshot(cx))
19032 .text()
19033 }
19034
19035 fn create_minimap(
19036 &self,
19037 minimap_settings: MinimapSettings,
19038 window: &mut Window,
19039 cx: &mut Context<Self>,
19040 ) -> Option<Entity<Self>> {
19041 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
19042 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19043 }
19044
19045 fn initialize_new_minimap(
19046 &self,
19047 minimap_settings: MinimapSettings,
19048 window: &mut Window,
19049 cx: &mut Context<Self>,
19050 ) -> Entity<Self> {
19051 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19052
19053 let mut minimap = Editor::new_internal(
19054 EditorMode::Minimap {
19055 parent: cx.weak_entity(),
19056 },
19057 self.buffer.clone(),
19058 None,
19059 Some(self.display_map.clone()),
19060 window,
19061 cx,
19062 );
19063 minimap.scroll_manager.clone_state(&self.scroll_manager);
19064 minimap.set_text_style_refinement(TextStyleRefinement {
19065 font_size: Some(MINIMAP_FONT_SIZE),
19066 font_weight: Some(MINIMAP_FONT_WEIGHT),
19067 ..Default::default()
19068 });
19069 minimap.update_minimap_configuration(minimap_settings, cx);
19070 cx.new(|_| minimap)
19071 }
19072
19073 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19074 let current_line_highlight = minimap_settings
19075 .current_line_highlight
19076 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19077 self.set_current_line_highlight(Some(current_line_highlight));
19078 }
19079
19080 pub fn minimap(&self) -> Option<&Entity<Self>> {
19081 self.minimap
19082 .as_ref()
19083 .filter(|_| self.minimap_visibility.visible())
19084 }
19085
19086 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19087 let mut wrap_guides = smallvec![];
19088
19089 if self.show_wrap_guides == Some(false) {
19090 return wrap_guides;
19091 }
19092
19093 let settings = self.buffer.read(cx).language_settings(cx);
19094 if settings.show_wrap_guides {
19095 match self.soft_wrap_mode(cx) {
19096 SoftWrap::Column(soft_wrap) => {
19097 wrap_guides.push((soft_wrap as usize, true));
19098 }
19099 SoftWrap::Bounded(soft_wrap) => {
19100 wrap_guides.push((soft_wrap as usize, true));
19101 }
19102 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19103 }
19104 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19105 }
19106
19107 wrap_guides
19108 }
19109
19110 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19111 let settings = self.buffer.read(cx).language_settings(cx);
19112 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19113 match mode {
19114 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19115 SoftWrap::None
19116 }
19117 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19118 language_settings::SoftWrap::PreferredLineLength => {
19119 SoftWrap::Column(settings.preferred_line_length)
19120 }
19121 language_settings::SoftWrap::Bounded => {
19122 SoftWrap::Bounded(settings.preferred_line_length)
19123 }
19124 }
19125 }
19126
19127 pub fn set_soft_wrap_mode(
19128 &mut self,
19129 mode: language_settings::SoftWrap,
19130
19131 cx: &mut Context<Self>,
19132 ) {
19133 self.soft_wrap_mode_override = Some(mode);
19134 cx.notify();
19135 }
19136
19137 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19138 self.hard_wrap = hard_wrap;
19139 cx.notify();
19140 }
19141
19142 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19143 self.text_style_refinement = Some(style);
19144 }
19145
19146 /// called by the Element so we know what style we were most recently rendered with.
19147 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19148 // We intentionally do not inform the display map about the minimap style
19149 // so that wrapping is not recalculated and stays consistent for the editor
19150 // and its linked minimap.
19151 if !self.mode.is_minimap() {
19152 let font = style.text.font();
19153 let font_size = style.text.font_size.to_pixels(window.rem_size());
19154 let display_map = self
19155 .placeholder_display_map
19156 .as_ref()
19157 .filter(|_| self.is_empty(cx))
19158 .unwrap_or(&self.display_map);
19159
19160 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19161 }
19162 self.style = Some(style);
19163 }
19164
19165 pub fn style(&self) -> Option<&EditorStyle> {
19166 self.style.as_ref()
19167 }
19168
19169 // Called by the element. This method is not designed to be called outside of the editor
19170 // element's layout code because it does not notify when rewrapping is computed synchronously.
19171 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19172 if self.is_empty(cx) {
19173 self.placeholder_display_map
19174 .as_ref()
19175 .map_or(false, |display_map| {
19176 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19177 })
19178 } else {
19179 self.display_map
19180 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19181 }
19182 }
19183
19184 pub fn set_soft_wrap(&mut self) {
19185 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19186 }
19187
19188 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19189 if self.soft_wrap_mode_override.is_some() {
19190 self.soft_wrap_mode_override.take();
19191 } else {
19192 let soft_wrap = match self.soft_wrap_mode(cx) {
19193 SoftWrap::GitDiff => return,
19194 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19195 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19196 language_settings::SoftWrap::None
19197 }
19198 };
19199 self.soft_wrap_mode_override = Some(soft_wrap);
19200 }
19201 cx.notify();
19202 }
19203
19204 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19205 let Some(workspace) = self.workspace() else {
19206 return;
19207 };
19208 let fs = workspace.read(cx).app_state().fs.clone();
19209 let current_show = TabBarSettings::get_global(cx).show;
19210 update_settings_file(fs, cx, move |setting, _| {
19211 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19212 });
19213 }
19214
19215 pub fn toggle_indent_guides(
19216 &mut self,
19217 _: &ToggleIndentGuides,
19218 _: &mut Window,
19219 cx: &mut Context<Self>,
19220 ) {
19221 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19222 self.buffer
19223 .read(cx)
19224 .language_settings(cx)
19225 .indent_guides
19226 .enabled
19227 });
19228 self.show_indent_guides = Some(!currently_enabled);
19229 cx.notify();
19230 }
19231
19232 fn should_show_indent_guides(&self) -> Option<bool> {
19233 self.show_indent_guides
19234 }
19235
19236 pub fn toggle_line_numbers(
19237 &mut self,
19238 _: &ToggleLineNumbers,
19239 _: &mut Window,
19240 cx: &mut Context<Self>,
19241 ) {
19242 let mut editor_settings = EditorSettings::get_global(cx).clone();
19243 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19244 EditorSettings::override_global(editor_settings, cx);
19245 }
19246
19247 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19248 if let Some(show_line_numbers) = self.show_line_numbers {
19249 return show_line_numbers;
19250 }
19251 EditorSettings::get_global(cx).gutter.line_numbers
19252 }
19253
19254 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19255 self.use_relative_line_numbers
19256 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19257 }
19258
19259 pub fn toggle_relative_line_numbers(
19260 &mut self,
19261 _: &ToggleRelativeLineNumbers,
19262 _: &mut Window,
19263 cx: &mut Context<Self>,
19264 ) {
19265 let is_relative = self.should_use_relative_line_numbers(cx);
19266 self.set_relative_line_number(Some(!is_relative), cx)
19267 }
19268
19269 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19270 self.use_relative_line_numbers = is_relative;
19271 cx.notify();
19272 }
19273
19274 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19275 self.show_gutter = show_gutter;
19276 cx.notify();
19277 }
19278
19279 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19280 self.show_scrollbars = ScrollbarAxes {
19281 horizontal: show,
19282 vertical: show,
19283 };
19284 cx.notify();
19285 }
19286
19287 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19288 self.show_scrollbars.vertical = show;
19289 cx.notify();
19290 }
19291
19292 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19293 self.show_scrollbars.horizontal = show;
19294 cx.notify();
19295 }
19296
19297 pub fn set_minimap_visibility(
19298 &mut self,
19299 minimap_visibility: MinimapVisibility,
19300 window: &mut Window,
19301 cx: &mut Context<Self>,
19302 ) {
19303 if self.minimap_visibility != minimap_visibility {
19304 if minimap_visibility.visible() && self.minimap.is_none() {
19305 let minimap_settings = EditorSettings::get_global(cx).minimap;
19306 self.minimap =
19307 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19308 }
19309 self.minimap_visibility = minimap_visibility;
19310 cx.notify();
19311 }
19312 }
19313
19314 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19315 self.set_show_scrollbars(false, cx);
19316 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19317 }
19318
19319 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19320 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19321 }
19322
19323 /// Normally the text in full mode and auto height editors is padded on the
19324 /// left side by roughly half a character width for improved hit testing.
19325 ///
19326 /// Use this method to disable this for cases where this is not wanted (e.g.
19327 /// if you want to align the editor text with some other text above or below)
19328 /// or if you want to add this padding to single-line editors.
19329 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19330 self.offset_content = offset_content;
19331 cx.notify();
19332 }
19333
19334 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19335 self.show_line_numbers = Some(show_line_numbers);
19336 cx.notify();
19337 }
19338
19339 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19340 self.disable_expand_excerpt_buttons = true;
19341 cx.notify();
19342 }
19343
19344 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19345 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19346 cx.notify();
19347 }
19348
19349 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19350 self.show_code_actions = Some(show_code_actions);
19351 cx.notify();
19352 }
19353
19354 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19355 self.show_runnables = Some(show_runnables);
19356 cx.notify();
19357 }
19358
19359 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19360 self.show_breakpoints = Some(show_breakpoints);
19361 cx.notify();
19362 }
19363
19364 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19365 if self.display_map.read(cx).masked != masked {
19366 self.display_map.update(cx, |map, _| map.masked = masked);
19367 }
19368 cx.notify()
19369 }
19370
19371 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19372 self.show_wrap_guides = Some(show_wrap_guides);
19373 cx.notify();
19374 }
19375
19376 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19377 self.show_indent_guides = Some(show_indent_guides);
19378 cx.notify();
19379 }
19380
19381 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19382 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19383 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19384 && let Some(dir) = file.abs_path(cx).parent()
19385 {
19386 return Some(dir.to_owned());
19387 }
19388 }
19389
19390 None
19391 }
19392
19393 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19394 self.active_excerpt(cx)?
19395 .1
19396 .read(cx)
19397 .file()
19398 .and_then(|f| f.as_local())
19399 }
19400
19401 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19402 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19403 let buffer = buffer.read(cx);
19404 if let Some(project_path) = buffer.project_path(cx) {
19405 let project = self.project()?.read(cx);
19406 project.absolute_path(&project_path, cx)
19407 } else {
19408 buffer
19409 .file()
19410 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19411 }
19412 })
19413 }
19414
19415 pub fn reveal_in_finder(
19416 &mut self,
19417 _: &RevealInFileManager,
19418 _window: &mut Window,
19419 cx: &mut Context<Self>,
19420 ) {
19421 if let Some(target) = self.target_file(cx) {
19422 cx.reveal_path(&target.abs_path(cx));
19423 }
19424 }
19425
19426 pub fn copy_path(
19427 &mut self,
19428 _: &zed_actions::workspace::CopyPath,
19429 _window: &mut Window,
19430 cx: &mut Context<Self>,
19431 ) {
19432 if let Some(path) = self.target_file_abs_path(cx)
19433 && let Some(path) = path.to_str()
19434 {
19435 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19436 } else {
19437 cx.propagate();
19438 }
19439 }
19440
19441 pub fn copy_relative_path(
19442 &mut self,
19443 _: &zed_actions::workspace::CopyRelativePath,
19444 _window: &mut Window,
19445 cx: &mut Context<Self>,
19446 ) {
19447 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19448 let project = self.project()?.read(cx);
19449 let path = buffer.read(cx).file()?.path();
19450 let path = path.display(project.path_style(cx));
19451 Some(path)
19452 }) {
19453 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19454 } else {
19455 cx.propagate();
19456 }
19457 }
19458
19459 /// Returns the project path for the editor's buffer, if any buffer is
19460 /// opened in the editor.
19461 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19462 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19463 buffer.read(cx).project_path(cx)
19464 } else {
19465 None
19466 }
19467 }
19468
19469 // Returns true if the editor handled a go-to-line request
19470 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19471 maybe!({
19472 let breakpoint_store = self.breakpoint_store.as_ref()?;
19473
19474 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19475 else {
19476 self.clear_row_highlights::<ActiveDebugLine>();
19477 return None;
19478 };
19479
19480 let position = active_stack_frame.position;
19481 let buffer_id = position.buffer_id?;
19482 let snapshot = self
19483 .project
19484 .as_ref()?
19485 .read(cx)
19486 .buffer_for_id(buffer_id, cx)?
19487 .read(cx)
19488 .snapshot();
19489
19490 let mut handled = false;
19491 for (id, ExcerptRange { context, .. }) in
19492 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19493 {
19494 if context.start.cmp(&position, &snapshot).is_ge()
19495 || context.end.cmp(&position, &snapshot).is_lt()
19496 {
19497 continue;
19498 }
19499 let snapshot = self.buffer.read(cx).snapshot(cx);
19500 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19501
19502 handled = true;
19503 self.clear_row_highlights::<ActiveDebugLine>();
19504
19505 self.go_to_line::<ActiveDebugLine>(
19506 multibuffer_anchor,
19507 Some(cx.theme().colors().editor_debugger_active_line_background),
19508 window,
19509 cx,
19510 );
19511
19512 cx.notify();
19513 }
19514
19515 handled.then_some(())
19516 })
19517 .is_some()
19518 }
19519
19520 pub fn copy_file_name_without_extension(
19521 &mut self,
19522 _: &CopyFileNameWithoutExtension,
19523 _: &mut Window,
19524 cx: &mut Context<Self>,
19525 ) {
19526 if let Some(file) = self.target_file(cx)
19527 && let Some(file_stem) = file.path().file_stem()
19528 {
19529 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19530 }
19531 }
19532
19533 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19534 if let Some(file) = self.target_file(cx)
19535 && let Some(name) = file.path().file_name()
19536 {
19537 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19538 }
19539 }
19540
19541 pub fn toggle_git_blame(
19542 &mut self,
19543 _: &::git::Blame,
19544 window: &mut Window,
19545 cx: &mut Context<Self>,
19546 ) {
19547 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19548
19549 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19550 self.start_git_blame(true, window, cx);
19551 }
19552
19553 cx.notify();
19554 }
19555
19556 pub fn toggle_git_blame_inline(
19557 &mut self,
19558 _: &ToggleGitBlameInline,
19559 window: &mut Window,
19560 cx: &mut Context<Self>,
19561 ) {
19562 self.toggle_git_blame_inline_internal(true, window, cx);
19563 cx.notify();
19564 }
19565
19566 pub fn open_git_blame_commit(
19567 &mut self,
19568 _: &OpenGitBlameCommit,
19569 window: &mut Window,
19570 cx: &mut Context<Self>,
19571 ) {
19572 self.open_git_blame_commit_internal(window, cx);
19573 }
19574
19575 fn open_git_blame_commit_internal(
19576 &mut self,
19577 window: &mut Window,
19578 cx: &mut Context<Self>,
19579 ) -> Option<()> {
19580 let blame = self.blame.as_ref()?;
19581 let snapshot = self.snapshot(window, cx);
19582 let cursor = self.selections.newest::<Point>(cx).head();
19583 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19584 let (_, blame_entry) = blame
19585 .update(cx, |blame, cx| {
19586 blame
19587 .blame_for_rows(
19588 &[RowInfo {
19589 buffer_id: Some(buffer.remote_id()),
19590 buffer_row: Some(point.row),
19591 ..Default::default()
19592 }],
19593 cx,
19594 )
19595 .next()
19596 })
19597 .flatten()?;
19598 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19599 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19600 let workspace = self.workspace()?.downgrade();
19601 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19602 None
19603 }
19604
19605 pub fn git_blame_inline_enabled(&self) -> bool {
19606 self.git_blame_inline_enabled
19607 }
19608
19609 pub fn toggle_selection_menu(
19610 &mut self,
19611 _: &ToggleSelectionMenu,
19612 _: &mut Window,
19613 cx: &mut Context<Self>,
19614 ) {
19615 self.show_selection_menu = self
19616 .show_selection_menu
19617 .map(|show_selections_menu| !show_selections_menu)
19618 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19619
19620 cx.notify();
19621 }
19622
19623 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19624 self.show_selection_menu
19625 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19626 }
19627
19628 fn start_git_blame(
19629 &mut self,
19630 user_triggered: bool,
19631 window: &mut Window,
19632 cx: &mut Context<Self>,
19633 ) {
19634 if let Some(project) = self.project() {
19635 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19636 && buffer.read(cx).file().is_none()
19637 {
19638 return;
19639 }
19640
19641 let focused = self.focus_handle(cx).contains_focused(window, cx);
19642
19643 let project = project.clone();
19644 let blame = cx
19645 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19646 self.blame_subscription =
19647 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19648 self.blame = Some(blame);
19649 }
19650 }
19651
19652 fn toggle_git_blame_inline_internal(
19653 &mut self,
19654 user_triggered: bool,
19655 window: &mut Window,
19656 cx: &mut Context<Self>,
19657 ) {
19658 if self.git_blame_inline_enabled {
19659 self.git_blame_inline_enabled = false;
19660 self.show_git_blame_inline = false;
19661 self.show_git_blame_inline_delay_task.take();
19662 } else {
19663 self.git_blame_inline_enabled = true;
19664 self.start_git_blame_inline(user_triggered, window, cx);
19665 }
19666
19667 cx.notify();
19668 }
19669
19670 fn start_git_blame_inline(
19671 &mut self,
19672 user_triggered: bool,
19673 window: &mut Window,
19674 cx: &mut Context<Self>,
19675 ) {
19676 self.start_git_blame(user_triggered, window, cx);
19677
19678 if ProjectSettings::get_global(cx)
19679 .git
19680 .inline_blame_delay()
19681 .is_some()
19682 {
19683 self.start_inline_blame_timer(window, cx);
19684 } else {
19685 self.show_git_blame_inline = true
19686 }
19687 }
19688
19689 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19690 self.blame.as_ref()
19691 }
19692
19693 pub fn show_git_blame_gutter(&self) -> bool {
19694 self.show_git_blame_gutter
19695 }
19696
19697 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19698 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19699 }
19700
19701 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19702 self.show_git_blame_inline
19703 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19704 && !self.newest_selection_head_on_empty_line(cx)
19705 && self.has_blame_entries(cx)
19706 }
19707
19708 fn has_blame_entries(&self, cx: &App) -> bool {
19709 self.blame()
19710 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19711 }
19712
19713 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19714 let cursor_anchor = self.selections.newest_anchor().head();
19715
19716 let snapshot = self.buffer.read(cx).snapshot(cx);
19717 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19718
19719 snapshot.line_len(buffer_row) == 0
19720 }
19721
19722 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19723 let buffer_and_selection = maybe!({
19724 let selection = self.selections.newest::<Point>(cx);
19725 let selection_range = selection.range();
19726
19727 let multi_buffer = self.buffer().read(cx);
19728 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19729 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19730
19731 let (buffer, range, _) = if selection.reversed {
19732 buffer_ranges.first()
19733 } else {
19734 buffer_ranges.last()
19735 }?;
19736
19737 let selection = text::ToPoint::to_point(&range.start, buffer).row
19738 ..text::ToPoint::to_point(&range.end, buffer).row;
19739 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19740 });
19741
19742 let Some((buffer, selection)) = buffer_and_selection else {
19743 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19744 };
19745
19746 let Some(project) = self.project() else {
19747 return Task::ready(Err(anyhow!("editor does not have project")));
19748 };
19749
19750 project.update(cx, |project, cx| {
19751 project.get_permalink_to_line(&buffer, selection, cx)
19752 })
19753 }
19754
19755 pub fn copy_permalink_to_line(
19756 &mut self,
19757 _: &CopyPermalinkToLine,
19758 window: &mut Window,
19759 cx: &mut Context<Self>,
19760 ) {
19761 let permalink_task = self.get_permalink_to_line(cx);
19762 let workspace = self.workspace();
19763
19764 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19765 Ok(permalink) => {
19766 cx.update(|_, cx| {
19767 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19768 })
19769 .ok();
19770 }
19771 Err(err) => {
19772 let message = format!("Failed to copy permalink: {err}");
19773
19774 anyhow::Result::<()>::Err(err).log_err();
19775
19776 if let Some(workspace) = workspace {
19777 workspace
19778 .update_in(cx, |workspace, _, cx| {
19779 struct CopyPermalinkToLine;
19780
19781 workspace.show_toast(
19782 Toast::new(
19783 NotificationId::unique::<CopyPermalinkToLine>(),
19784 message,
19785 ),
19786 cx,
19787 )
19788 })
19789 .ok();
19790 }
19791 }
19792 })
19793 .detach();
19794 }
19795
19796 pub fn copy_file_location(
19797 &mut self,
19798 _: &CopyFileLocation,
19799 _: &mut Window,
19800 cx: &mut Context<Self>,
19801 ) {
19802 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19803 if let Some(file) = self.target_file(cx) {
19804 let path = file.path().display(file.path_style(cx));
19805 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19806 }
19807 }
19808
19809 pub fn open_permalink_to_line(
19810 &mut self,
19811 _: &OpenPermalinkToLine,
19812 window: &mut Window,
19813 cx: &mut Context<Self>,
19814 ) {
19815 let permalink_task = self.get_permalink_to_line(cx);
19816 let workspace = self.workspace();
19817
19818 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19819 Ok(permalink) => {
19820 cx.update(|_, cx| {
19821 cx.open_url(permalink.as_ref());
19822 })
19823 .ok();
19824 }
19825 Err(err) => {
19826 let message = format!("Failed to open permalink: {err}");
19827
19828 anyhow::Result::<()>::Err(err).log_err();
19829
19830 if let Some(workspace) = workspace {
19831 workspace
19832 .update(cx, |workspace, cx| {
19833 struct OpenPermalinkToLine;
19834
19835 workspace.show_toast(
19836 Toast::new(
19837 NotificationId::unique::<OpenPermalinkToLine>(),
19838 message,
19839 ),
19840 cx,
19841 )
19842 })
19843 .ok();
19844 }
19845 }
19846 })
19847 .detach();
19848 }
19849
19850 pub fn insert_uuid_v4(
19851 &mut self,
19852 _: &InsertUuidV4,
19853 window: &mut Window,
19854 cx: &mut Context<Self>,
19855 ) {
19856 self.insert_uuid(UuidVersion::V4, window, cx);
19857 }
19858
19859 pub fn insert_uuid_v7(
19860 &mut self,
19861 _: &InsertUuidV7,
19862 window: &mut Window,
19863 cx: &mut Context<Self>,
19864 ) {
19865 self.insert_uuid(UuidVersion::V7, window, cx);
19866 }
19867
19868 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19869 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19870 self.transact(window, cx, |this, window, cx| {
19871 let edits = this
19872 .selections
19873 .all::<Point>(cx)
19874 .into_iter()
19875 .map(|selection| {
19876 let uuid = match version {
19877 UuidVersion::V4 => uuid::Uuid::new_v4(),
19878 UuidVersion::V7 => uuid::Uuid::now_v7(),
19879 };
19880
19881 (selection.range(), uuid.to_string())
19882 });
19883 this.edit(edits, cx);
19884 this.refresh_edit_prediction(true, false, window, cx);
19885 });
19886 }
19887
19888 pub fn open_selections_in_multibuffer(
19889 &mut self,
19890 _: &OpenSelectionsInMultibuffer,
19891 window: &mut Window,
19892 cx: &mut Context<Self>,
19893 ) {
19894 let multibuffer = self.buffer.read(cx);
19895
19896 let Some(buffer) = multibuffer.as_singleton() else {
19897 return;
19898 };
19899
19900 let Some(workspace) = self.workspace() else {
19901 return;
19902 };
19903
19904 let title = multibuffer.title(cx).to_string();
19905
19906 let locations = self
19907 .selections
19908 .all_anchors(cx)
19909 .iter()
19910 .map(|selection| {
19911 (
19912 buffer.clone(),
19913 (selection.start.text_anchor..selection.end.text_anchor)
19914 .to_point(buffer.read(cx)),
19915 )
19916 })
19917 .into_group_map();
19918
19919 cx.spawn_in(window, async move |_, cx| {
19920 workspace.update_in(cx, |workspace, window, cx| {
19921 Self::open_locations_in_multibuffer(
19922 workspace,
19923 locations,
19924 format!("Selections for '{title}'"),
19925 false,
19926 MultibufferSelectionMode::All,
19927 window,
19928 cx,
19929 );
19930 })
19931 })
19932 .detach();
19933 }
19934
19935 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19936 /// last highlight added will be used.
19937 ///
19938 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19939 pub fn highlight_rows<T: 'static>(
19940 &mut self,
19941 range: Range<Anchor>,
19942 color: Hsla,
19943 options: RowHighlightOptions,
19944 cx: &mut Context<Self>,
19945 ) {
19946 let snapshot = self.buffer().read(cx).snapshot(cx);
19947 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19948 let ix = row_highlights.binary_search_by(|highlight| {
19949 Ordering::Equal
19950 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19951 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19952 });
19953
19954 if let Err(mut ix) = ix {
19955 let index = post_inc(&mut self.highlight_order);
19956
19957 // If this range intersects with the preceding highlight, then merge it with
19958 // the preceding highlight. Otherwise insert a new highlight.
19959 let mut merged = false;
19960 if ix > 0 {
19961 let prev_highlight = &mut row_highlights[ix - 1];
19962 if prev_highlight
19963 .range
19964 .end
19965 .cmp(&range.start, &snapshot)
19966 .is_ge()
19967 {
19968 ix -= 1;
19969 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19970 prev_highlight.range.end = range.end;
19971 }
19972 merged = true;
19973 prev_highlight.index = index;
19974 prev_highlight.color = color;
19975 prev_highlight.options = options;
19976 }
19977 }
19978
19979 if !merged {
19980 row_highlights.insert(
19981 ix,
19982 RowHighlight {
19983 range,
19984 index,
19985 color,
19986 options,
19987 type_id: TypeId::of::<T>(),
19988 },
19989 );
19990 }
19991
19992 // If any of the following highlights intersect with this one, merge them.
19993 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19994 let highlight = &row_highlights[ix];
19995 if next_highlight
19996 .range
19997 .start
19998 .cmp(&highlight.range.end, &snapshot)
19999 .is_le()
20000 {
20001 if next_highlight
20002 .range
20003 .end
20004 .cmp(&highlight.range.end, &snapshot)
20005 .is_gt()
20006 {
20007 row_highlights[ix].range.end = next_highlight.range.end;
20008 }
20009 row_highlights.remove(ix + 1);
20010 } else {
20011 break;
20012 }
20013 }
20014 }
20015 }
20016
20017 /// Remove any highlighted row ranges of the given type that intersect the
20018 /// given ranges.
20019 pub fn remove_highlighted_rows<T: 'static>(
20020 &mut self,
20021 ranges_to_remove: Vec<Range<Anchor>>,
20022 cx: &mut Context<Self>,
20023 ) {
20024 let snapshot = self.buffer().read(cx).snapshot(cx);
20025 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20026 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20027 row_highlights.retain(|highlight| {
20028 while let Some(range_to_remove) = ranges_to_remove.peek() {
20029 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20030 Ordering::Less | Ordering::Equal => {
20031 ranges_to_remove.next();
20032 }
20033 Ordering::Greater => {
20034 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20035 Ordering::Less | Ordering::Equal => {
20036 return false;
20037 }
20038 Ordering::Greater => break,
20039 }
20040 }
20041 }
20042 }
20043
20044 true
20045 })
20046 }
20047
20048 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20049 pub fn clear_row_highlights<T: 'static>(&mut self) {
20050 self.highlighted_rows.remove(&TypeId::of::<T>());
20051 }
20052
20053 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20054 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20055 self.highlighted_rows
20056 .get(&TypeId::of::<T>())
20057 .map_or(&[] as &[_], |vec| vec.as_slice())
20058 .iter()
20059 .map(|highlight| (highlight.range.clone(), highlight.color))
20060 }
20061
20062 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20063 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20064 /// Allows to ignore certain kinds of highlights.
20065 pub fn highlighted_display_rows(
20066 &self,
20067 window: &mut Window,
20068 cx: &mut App,
20069 ) -> BTreeMap<DisplayRow, LineHighlight> {
20070 let snapshot = self.snapshot(window, cx);
20071 let mut used_highlight_orders = HashMap::default();
20072 self.highlighted_rows
20073 .iter()
20074 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20075 .fold(
20076 BTreeMap::<DisplayRow, LineHighlight>::new(),
20077 |mut unique_rows, highlight| {
20078 let start = highlight.range.start.to_display_point(&snapshot);
20079 let end = highlight.range.end.to_display_point(&snapshot);
20080 let start_row = start.row().0;
20081 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20082 && end.column() == 0
20083 {
20084 end.row().0.saturating_sub(1)
20085 } else {
20086 end.row().0
20087 };
20088 for row in start_row..=end_row {
20089 let used_index =
20090 used_highlight_orders.entry(row).or_insert(highlight.index);
20091 if highlight.index >= *used_index {
20092 *used_index = highlight.index;
20093 unique_rows.insert(
20094 DisplayRow(row),
20095 LineHighlight {
20096 include_gutter: highlight.options.include_gutter,
20097 border: None,
20098 background: highlight.color.into(),
20099 type_id: Some(highlight.type_id),
20100 },
20101 );
20102 }
20103 }
20104 unique_rows
20105 },
20106 )
20107 }
20108
20109 pub fn highlighted_display_row_for_autoscroll(
20110 &self,
20111 snapshot: &DisplaySnapshot,
20112 ) -> Option<DisplayRow> {
20113 self.highlighted_rows
20114 .values()
20115 .flat_map(|highlighted_rows| highlighted_rows.iter())
20116 .filter_map(|highlight| {
20117 if highlight.options.autoscroll {
20118 Some(highlight.range.start.to_display_point(snapshot).row())
20119 } else {
20120 None
20121 }
20122 })
20123 .min()
20124 }
20125
20126 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20127 self.highlight_background::<SearchWithinRange>(
20128 ranges,
20129 |colors| colors.colors().editor_document_highlight_read_background,
20130 cx,
20131 )
20132 }
20133
20134 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20135 self.breadcrumb_header = Some(new_header);
20136 }
20137
20138 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20139 self.clear_background_highlights::<SearchWithinRange>(cx);
20140 }
20141
20142 pub fn highlight_background<T: 'static>(
20143 &mut self,
20144 ranges: &[Range<Anchor>],
20145 color_fetcher: fn(&Theme) -> Hsla,
20146 cx: &mut Context<Self>,
20147 ) {
20148 self.background_highlights.insert(
20149 HighlightKey::Type(TypeId::of::<T>()),
20150 (color_fetcher, Arc::from(ranges)),
20151 );
20152 self.scrollbar_marker_state.dirty = true;
20153 cx.notify();
20154 }
20155
20156 pub fn highlight_background_key<T: 'static>(
20157 &mut self,
20158 key: usize,
20159 ranges: &[Range<Anchor>],
20160 color_fetcher: fn(&Theme) -> Hsla,
20161 cx: &mut Context<Self>,
20162 ) {
20163 self.background_highlights.insert(
20164 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20165 (color_fetcher, Arc::from(ranges)),
20166 );
20167 self.scrollbar_marker_state.dirty = true;
20168 cx.notify();
20169 }
20170
20171 pub fn clear_background_highlights<T: 'static>(
20172 &mut self,
20173 cx: &mut Context<Self>,
20174 ) -> Option<BackgroundHighlight> {
20175 let text_highlights = self
20176 .background_highlights
20177 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20178 if !text_highlights.1.is_empty() {
20179 self.scrollbar_marker_state.dirty = true;
20180 cx.notify();
20181 }
20182 Some(text_highlights)
20183 }
20184
20185 pub fn highlight_gutter<T: 'static>(
20186 &mut self,
20187 ranges: impl Into<Vec<Range<Anchor>>>,
20188 color_fetcher: fn(&App) -> Hsla,
20189 cx: &mut Context<Self>,
20190 ) {
20191 self.gutter_highlights
20192 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20193 cx.notify();
20194 }
20195
20196 pub fn clear_gutter_highlights<T: 'static>(
20197 &mut self,
20198 cx: &mut Context<Self>,
20199 ) -> Option<GutterHighlight> {
20200 cx.notify();
20201 self.gutter_highlights.remove(&TypeId::of::<T>())
20202 }
20203
20204 pub fn insert_gutter_highlight<T: 'static>(
20205 &mut self,
20206 range: Range<Anchor>,
20207 color_fetcher: fn(&App) -> Hsla,
20208 cx: &mut Context<Self>,
20209 ) {
20210 let snapshot = self.buffer().read(cx).snapshot(cx);
20211 let mut highlights = self
20212 .gutter_highlights
20213 .remove(&TypeId::of::<T>())
20214 .map(|(_, highlights)| highlights)
20215 .unwrap_or_default();
20216 let ix = highlights.binary_search_by(|highlight| {
20217 Ordering::Equal
20218 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20219 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20220 });
20221 if let Err(ix) = ix {
20222 highlights.insert(ix, range);
20223 }
20224 self.gutter_highlights
20225 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20226 }
20227
20228 pub fn remove_gutter_highlights<T: 'static>(
20229 &mut self,
20230 ranges_to_remove: Vec<Range<Anchor>>,
20231 cx: &mut Context<Self>,
20232 ) {
20233 let snapshot = self.buffer().read(cx).snapshot(cx);
20234 let Some((color_fetcher, mut gutter_highlights)) =
20235 self.gutter_highlights.remove(&TypeId::of::<T>())
20236 else {
20237 return;
20238 };
20239 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20240 gutter_highlights.retain(|highlight| {
20241 while let Some(range_to_remove) = ranges_to_remove.peek() {
20242 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20243 Ordering::Less | Ordering::Equal => {
20244 ranges_to_remove.next();
20245 }
20246 Ordering::Greater => {
20247 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20248 Ordering::Less | Ordering::Equal => {
20249 return false;
20250 }
20251 Ordering::Greater => break,
20252 }
20253 }
20254 }
20255 }
20256
20257 true
20258 });
20259 self.gutter_highlights
20260 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20261 }
20262
20263 #[cfg(feature = "test-support")]
20264 pub fn all_text_highlights(
20265 &self,
20266 window: &mut Window,
20267 cx: &mut Context<Self>,
20268 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20269 let snapshot = self.snapshot(window, cx);
20270 self.display_map.update(cx, |display_map, _| {
20271 display_map
20272 .all_text_highlights()
20273 .map(|highlight| {
20274 let (style, ranges) = highlight.as_ref();
20275 (
20276 *style,
20277 ranges
20278 .iter()
20279 .map(|range| range.clone().to_display_points(&snapshot))
20280 .collect(),
20281 )
20282 })
20283 .collect()
20284 })
20285 }
20286
20287 #[cfg(feature = "test-support")]
20288 pub fn all_text_background_highlights(
20289 &self,
20290 window: &mut Window,
20291 cx: &mut Context<Self>,
20292 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20293 let snapshot = self.snapshot(window, cx);
20294 let buffer = &snapshot.buffer_snapshot;
20295 let start = buffer.anchor_before(0);
20296 let end = buffer.anchor_after(buffer.len());
20297 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20298 }
20299
20300 #[cfg(any(test, feature = "test-support"))]
20301 pub fn sorted_background_highlights_in_range(
20302 &self,
20303 search_range: Range<Anchor>,
20304 display_snapshot: &DisplaySnapshot,
20305 theme: &Theme,
20306 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20307 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20308 res.sort_by(|a, b| {
20309 a.0.start
20310 .cmp(&b.0.start)
20311 .then_with(|| a.0.end.cmp(&b.0.end))
20312 .then_with(|| a.1.cmp(&b.1))
20313 });
20314 res
20315 }
20316
20317 #[cfg(feature = "test-support")]
20318 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20319 let snapshot = self.buffer().read(cx).snapshot(cx);
20320
20321 let highlights = self
20322 .background_highlights
20323 .get(&HighlightKey::Type(TypeId::of::<
20324 items::BufferSearchHighlights,
20325 >()));
20326
20327 if let Some((_color, ranges)) = highlights {
20328 ranges
20329 .iter()
20330 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20331 .collect_vec()
20332 } else {
20333 vec![]
20334 }
20335 }
20336
20337 fn document_highlights_for_position<'a>(
20338 &'a self,
20339 position: Anchor,
20340 buffer: &'a MultiBufferSnapshot,
20341 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20342 let read_highlights = self
20343 .background_highlights
20344 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20345 .map(|h| &h.1);
20346 let write_highlights = self
20347 .background_highlights
20348 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20349 .map(|h| &h.1);
20350 let left_position = position.bias_left(buffer);
20351 let right_position = position.bias_right(buffer);
20352 read_highlights
20353 .into_iter()
20354 .chain(write_highlights)
20355 .flat_map(move |ranges| {
20356 let start_ix = match ranges.binary_search_by(|probe| {
20357 let cmp = probe.end.cmp(&left_position, buffer);
20358 if cmp.is_ge() {
20359 Ordering::Greater
20360 } else {
20361 Ordering::Less
20362 }
20363 }) {
20364 Ok(i) | Err(i) => i,
20365 };
20366
20367 ranges[start_ix..]
20368 .iter()
20369 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20370 })
20371 }
20372
20373 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20374 self.background_highlights
20375 .get(&HighlightKey::Type(TypeId::of::<T>()))
20376 .is_some_and(|(_, highlights)| !highlights.is_empty())
20377 }
20378
20379 /// Returns all background highlights for a given range.
20380 ///
20381 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20382 pub fn background_highlights_in_range(
20383 &self,
20384 search_range: Range<Anchor>,
20385 display_snapshot: &DisplaySnapshot,
20386 theme: &Theme,
20387 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20388 let mut results = Vec::new();
20389 for (color_fetcher, ranges) in self.background_highlights.values() {
20390 let color = color_fetcher(theme);
20391 let start_ix = match ranges.binary_search_by(|probe| {
20392 let cmp = probe
20393 .end
20394 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20395 if cmp.is_gt() {
20396 Ordering::Greater
20397 } else {
20398 Ordering::Less
20399 }
20400 }) {
20401 Ok(i) | Err(i) => i,
20402 };
20403 for range in &ranges[start_ix..] {
20404 if range
20405 .start
20406 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20407 .is_ge()
20408 {
20409 break;
20410 }
20411
20412 let start = range.start.to_display_point(display_snapshot);
20413 let end = range.end.to_display_point(display_snapshot);
20414 results.push((start..end, color))
20415 }
20416 }
20417 results
20418 }
20419
20420 pub fn gutter_highlights_in_range(
20421 &self,
20422 search_range: Range<Anchor>,
20423 display_snapshot: &DisplaySnapshot,
20424 cx: &App,
20425 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20426 let mut results = Vec::new();
20427 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20428 let color = color_fetcher(cx);
20429 let start_ix = match ranges.binary_search_by(|probe| {
20430 let cmp = probe
20431 .end
20432 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20433 if cmp.is_gt() {
20434 Ordering::Greater
20435 } else {
20436 Ordering::Less
20437 }
20438 }) {
20439 Ok(i) | Err(i) => i,
20440 };
20441 for range in &ranges[start_ix..] {
20442 if range
20443 .start
20444 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20445 .is_ge()
20446 {
20447 break;
20448 }
20449
20450 let start = range.start.to_display_point(display_snapshot);
20451 let end = range.end.to_display_point(display_snapshot);
20452 results.push((start..end, color))
20453 }
20454 }
20455 results
20456 }
20457
20458 /// Get the text ranges corresponding to the redaction query
20459 pub fn redacted_ranges(
20460 &self,
20461 search_range: Range<Anchor>,
20462 display_snapshot: &DisplaySnapshot,
20463 cx: &App,
20464 ) -> Vec<Range<DisplayPoint>> {
20465 display_snapshot
20466 .buffer_snapshot
20467 .redacted_ranges(search_range, |file| {
20468 if let Some(file) = file {
20469 file.is_private()
20470 && EditorSettings::get(
20471 Some(SettingsLocation {
20472 worktree_id: file.worktree_id(cx),
20473 path: file.path().as_ref(),
20474 }),
20475 cx,
20476 )
20477 .redact_private_values
20478 } else {
20479 false
20480 }
20481 })
20482 .map(|range| {
20483 range.start.to_display_point(display_snapshot)
20484 ..range.end.to_display_point(display_snapshot)
20485 })
20486 .collect()
20487 }
20488
20489 pub fn highlight_text_key<T: 'static>(
20490 &mut self,
20491 key: usize,
20492 ranges: Vec<Range<Anchor>>,
20493 style: HighlightStyle,
20494 cx: &mut Context<Self>,
20495 ) {
20496 self.display_map.update(cx, |map, _| {
20497 map.highlight_text(
20498 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20499 ranges,
20500 style,
20501 );
20502 });
20503 cx.notify();
20504 }
20505
20506 pub fn highlight_text<T: 'static>(
20507 &mut self,
20508 ranges: Vec<Range<Anchor>>,
20509 style: HighlightStyle,
20510 cx: &mut Context<Self>,
20511 ) {
20512 self.display_map.update(cx, |map, _| {
20513 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20514 });
20515 cx.notify();
20516 }
20517
20518 pub(crate) fn highlight_inlays<T: 'static>(
20519 &mut self,
20520 highlights: Vec<InlayHighlight>,
20521 style: HighlightStyle,
20522 cx: &mut Context<Self>,
20523 ) {
20524 self.display_map.update(cx, |map, _| {
20525 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20526 });
20527 cx.notify();
20528 }
20529
20530 pub fn text_highlights<'a, T: 'static>(
20531 &'a self,
20532 cx: &'a App,
20533 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20534 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20535 }
20536
20537 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20538 let cleared = self
20539 .display_map
20540 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20541 if cleared {
20542 cx.notify();
20543 }
20544 }
20545
20546 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20547 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20548 && self.focus_handle.is_focused(window)
20549 }
20550
20551 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20552 self.show_cursor_when_unfocused = is_enabled;
20553 cx.notify();
20554 }
20555
20556 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20557 cx.notify();
20558 }
20559
20560 fn on_debug_session_event(
20561 &mut self,
20562 _session: Entity<Session>,
20563 event: &SessionEvent,
20564 cx: &mut Context<Self>,
20565 ) {
20566 if let SessionEvent::InvalidateInlineValue = event {
20567 self.refresh_inline_values(cx);
20568 }
20569 }
20570
20571 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20572 let Some(project) = self.project.clone() else {
20573 return;
20574 };
20575
20576 if !self.inline_value_cache.enabled {
20577 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20578 self.splice_inlays(&inlays, Vec::new(), cx);
20579 return;
20580 }
20581
20582 let current_execution_position = self
20583 .highlighted_rows
20584 .get(&TypeId::of::<ActiveDebugLine>())
20585 .and_then(|lines| lines.last().map(|line| line.range.end));
20586
20587 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20588 let inline_values = editor
20589 .update(cx, |editor, cx| {
20590 let Some(current_execution_position) = current_execution_position else {
20591 return Some(Task::ready(Ok(Vec::new())));
20592 };
20593
20594 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20595 let snapshot = buffer.snapshot(cx);
20596
20597 let excerpt = snapshot.excerpt_containing(
20598 current_execution_position..current_execution_position,
20599 )?;
20600
20601 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20602 })?;
20603
20604 let range =
20605 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20606
20607 project.inline_values(buffer, range, cx)
20608 })
20609 .ok()
20610 .flatten()?
20611 .await
20612 .context("refreshing debugger inlays")
20613 .log_err()?;
20614
20615 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20616
20617 for (buffer_id, inline_value) in inline_values
20618 .into_iter()
20619 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20620 {
20621 buffer_inline_values
20622 .entry(buffer_id)
20623 .or_default()
20624 .push(inline_value);
20625 }
20626
20627 editor
20628 .update(cx, |editor, cx| {
20629 let snapshot = editor.buffer.read(cx).snapshot(cx);
20630 let mut new_inlays = Vec::default();
20631
20632 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20633 let buffer_id = buffer_snapshot.remote_id();
20634 buffer_inline_values
20635 .get(&buffer_id)
20636 .into_iter()
20637 .flatten()
20638 .for_each(|hint| {
20639 let inlay = Inlay::debugger(
20640 post_inc(&mut editor.next_inlay_id),
20641 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20642 hint.text(),
20643 );
20644 if !inlay.text().chars().contains(&'\n') {
20645 new_inlays.push(inlay);
20646 }
20647 });
20648 }
20649
20650 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20651 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20652
20653 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20654 })
20655 .ok()?;
20656 Some(())
20657 });
20658 }
20659
20660 fn on_buffer_event(
20661 &mut self,
20662 multibuffer: &Entity<MultiBuffer>,
20663 event: &multi_buffer::Event,
20664 window: &mut Window,
20665 cx: &mut Context<Self>,
20666 ) {
20667 match event {
20668 multi_buffer::Event::Edited {
20669 singleton_buffer_edited,
20670 edited_buffer,
20671 } => {
20672 self.scrollbar_marker_state.dirty = true;
20673 self.active_indent_guides_state.dirty = true;
20674 self.refresh_active_diagnostics(cx);
20675 self.refresh_code_actions(window, cx);
20676 self.refresh_selected_text_highlights(true, window, cx);
20677 self.refresh_single_line_folds(window, cx);
20678 refresh_matching_bracket_highlights(self, window, cx);
20679 if self.has_active_edit_prediction() {
20680 self.update_visible_edit_prediction(window, cx);
20681 }
20682 if let Some(project) = self.project.as_ref()
20683 && let Some(edited_buffer) = edited_buffer
20684 {
20685 project.update(cx, |project, cx| {
20686 self.registered_buffers
20687 .entry(edited_buffer.read(cx).remote_id())
20688 .or_insert_with(|| {
20689 project.register_buffer_with_language_servers(edited_buffer, cx)
20690 });
20691 });
20692 }
20693 cx.emit(EditorEvent::BufferEdited);
20694 cx.emit(SearchEvent::MatchesInvalidated);
20695
20696 if let Some(buffer) = edited_buffer {
20697 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20698 }
20699
20700 if *singleton_buffer_edited {
20701 if let Some(buffer) = edited_buffer
20702 && buffer.read(cx).file().is_none()
20703 {
20704 cx.emit(EditorEvent::TitleChanged);
20705 }
20706 if let Some(project) = &self.project {
20707 #[allow(clippy::mutable_key_type)]
20708 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20709 multibuffer
20710 .all_buffers()
20711 .into_iter()
20712 .filter_map(|buffer| {
20713 buffer.update(cx, |buffer, cx| {
20714 let language = buffer.language()?;
20715 let should_discard = project.update(cx, |project, cx| {
20716 project.is_local()
20717 && !project.has_language_servers_for(buffer, cx)
20718 });
20719 should_discard.not().then_some(language.clone())
20720 })
20721 })
20722 .collect::<HashSet<_>>()
20723 });
20724 if !languages_affected.is_empty() {
20725 self.refresh_inlay_hints(
20726 InlayHintRefreshReason::BufferEdited(languages_affected),
20727 cx,
20728 );
20729 }
20730 }
20731 }
20732
20733 let Some(project) = &self.project else { return };
20734 let (telemetry, is_via_ssh) = {
20735 let project = project.read(cx);
20736 let telemetry = project.client().telemetry().clone();
20737 let is_via_ssh = project.is_via_remote_server();
20738 (telemetry, is_via_ssh)
20739 };
20740 refresh_linked_ranges(self, window, cx);
20741 telemetry.log_edit_event("editor", is_via_ssh);
20742 }
20743 multi_buffer::Event::ExcerptsAdded {
20744 buffer,
20745 predecessor,
20746 excerpts,
20747 } => {
20748 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20749 let buffer_id = buffer.read(cx).remote_id();
20750 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20751 && let Some(project) = &self.project
20752 {
20753 update_uncommitted_diff_for_buffer(
20754 cx.entity(),
20755 project,
20756 [buffer.clone()],
20757 self.buffer.clone(),
20758 cx,
20759 )
20760 .detach();
20761 }
20762 if self.active_diagnostics != ActiveDiagnostic::All {
20763 self.update_lsp_data(false, Some(buffer_id), window, cx);
20764 }
20765 cx.emit(EditorEvent::ExcerptsAdded {
20766 buffer: buffer.clone(),
20767 predecessor: *predecessor,
20768 excerpts: excerpts.clone(),
20769 });
20770 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20771 }
20772 multi_buffer::Event::ExcerptsRemoved {
20773 ids,
20774 removed_buffer_ids,
20775 } => {
20776 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20777 let buffer = self.buffer.read(cx);
20778 self.registered_buffers
20779 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20780 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20781 cx.emit(EditorEvent::ExcerptsRemoved {
20782 ids: ids.clone(),
20783 removed_buffer_ids: removed_buffer_ids.clone(),
20784 });
20785 }
20786 multi_buffer::Event::ExcerptsEdited {
20787 excerpt_ids,
20788 buffer_ids,
20789 } => {
20790 self.display_map.update(cx, |map, cx| {
20791 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20792 });
20793 cx.emit(EditorEvent::ExcerptsEdited {
20794 ids: excerpt_ids.clone(),
20795 });
20796 }
20797 multi_buffer::Event::ExcerptsExpanded { ids } => {
20798 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20799 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20800 }
20801 multi_buffer::Event::Reparsed(buffer_id) => {
20802 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20803 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20804
20805 cx.emit(EditorEvent::Reparsed(*buffer_id));
20806 }
20807 multi_buffer::Event::DiffHunksToggled => {
20808 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20809 }
20810 multi_buffer::Event::LanguageChanged(buffer_id) => {
20811 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20812 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20813 cx.emit(EditorEvent::Reparsed(*buffer_id));
20814 cx.notify();
20815 }
20816 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20817 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20818 multi_buffer::Event::FileHandleChanged
20819 | multi_buffer::Event::Reloaded
20820 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20821 multi_buffer::Event::DiagnosticsUpdated => {
20822 self.update_diagnostics_state(window, cx);
20823 }
20824 _ => {}
20825 };
20826 }
20827
20828 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20829 if !self.diagnostics_enabled() {
20830 return;
20831 }
20832 self.refresh_active_diagnostics(cx);
20833 self.refresh_inline_diagnostics(true, window, cx);
20834 self.scrollbar_marker_state.dirty = true;
20835 cx.notify();
20836 }
20837
20838 pub fn start_temporary_diff_override(&mut self) {
20839 self.load_diff_task.take();
20840 self.temporary_diff_override = true;
20841 }
20842
20843 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20844 self.temporary_diff_override = false;
20845 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20846 self.buffer.update(cx, |buffer, cx| {
20847 buffer.set_all_diff_hunks_collapsed(cx);
20848 });
20849
20850 if let Some(project) = self.project.clone() {
20851 self.load_diff_task = Some(
20852 update_uncommitted_diff_for_buffer(
20853 cx.entity(),
20854 &project,
20855 self.buffer.read(cx).all_buffers(),
20856 self.buffer.clone(),
20857 cx,
20858 )
20859 .shared(),
20860 );
20861 }
20862 }
20863
20864 fn on_display_map_changed(
20865 &mut self,
20866 _: Entity<DisplayMap>,
20867 _: &mut Window,
20868 cx: &mut Context<Self>,
20869 ) {
20870 cx.notify();
20871 }
20872
20873 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20874 if self.diagnostics_enabled() {
20875 let new_severity = EditorSettings::get_global(cx)
20876 .diagnostics_max_severity
20877 .unwrap_or(DiagnosticSeverity::Hint);
20878 self.set_max_diagnostics_severity(new_severity, cx);
20879 }
20880 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20881 self.update_edit_prediction_settings(cx);
20882 self.refresh_edit_prediction(true, false, window, cx);
20883 self.refresh_inline_values(cx);
20884 self.refresh_inlay_hints(
20885 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20886 self.selections.newest_anchor().head(),
20887 &self.buffer.read(cx).snapshot(cx),
20888 cx,
20889 )),
20890 cx,
20891 );
20892
20893 let old_cursor_shape = self.cursor_shape;
20894 let old_show_breadcrumbs = self.show_breadcrumbs;
20895
20896 {
20897 let editor_settings = EditorSettings::get_global(cx);
20898 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20899 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20900 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20901 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20902 }
20903
20904 if old_cursor_shape != self.cursor_shape {
20905 cx.emit(EditorEvent::CursorShapeChanged);
20906 }
20907
20908 if old_show_breadcrumbs != self.show_breadcrumbs {
20909 cx.emit(EditorEvent::BreadcrumbsChanged);
20910 }
20911
20912 let project_settings = ProjectSettings::get_global(cx);
20913 self.serialize_dirty_buffers =
20914 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20915
20916 if self.mode.is_full() {
20917 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20918 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20919 if self.show_inline_diagnostics != show_inline_diagnostics {
20920 self.show_inline_diagnostics = show_inline_diagnostics;
20921 self.refresh_inline_diagnostics(false, window, cx);
20922 }
20923
20924 if self.git_blame_inline_enabled != inline_blame_enabled {
20925 self.toggle_git_blame_inline_internal(false, window, cx);
20926 }
20927
20928 let minimap_settings = EditorSettings::get_global(cx).minimap;
20929 if self.minimap_visibility != MinimapVisibility::Disabled {
20930 if self.minimap_visibility.settings_visibility()
20931 != minimap_settings.minimap_enabled()
20932 {
20933 self.set_minimap_visibility(
20934 MinimapVisibility::for_mode(self.mode(), cx),
20935 window,
20936 cx,
20937 );
20938 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20939 minimap_entity.update(cx, |minimap_editor, cx| {
20940 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20941 })
20942 }
20943 }
20944 }
20945
20946 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20947 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20948 }) {
20949 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20950 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20951 }
20952 self.refresh_colors(false, None, window, cx);
20953 }
20954
20955 cx.notify();
20956 }
20957
20958 pub fn set_searchable(&mut self, searchable: bool) {
20959 self.searchable = searchable;
20960 }
20961
20962 pub fn searchable(&self) -> bool {
20963 self.searchable
20964 }
20965
20966 fn open_proposed_changes_editor(
20967 &mut self,
20968 _: &OpenProposedChangesEditor,
20969 window: &mut Window,
20970 cx: &mut Context<Self>,
20971 ) {
20972 let Some(workspace) = self.workspace() else {
20973 cx.propagate();
20974 return;
20975 };
20976
20977 let selections = self.selections.all::<usize>(cx);
20978 let multi_buffer = self.buffer.read(cx);
20979 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20980 let mut new_selections_by_buffer = HashMap::default();
20981 for selection in selections {
20982 for (buffer, range, _) in
20983 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20984 {
20985 let mut range = range.to_point(buffer);
20986 range.start.column = 0;
20987 range.end.column = buffer.line_len(range.end.row);
20988 new_selections_by_buffer
20989 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20990 .or_insert(Vec::new())
20991 .push(range)
20992 }
20993 }
20994
20995 let proposed_changes_buffers = new_selections_by_buffer
20996 .into_iter()
20997 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20998 .collect::<Vec<_>>();
20999 let proposed_changes_editor = cx.new(|cx| {
21000 ProposedChangesEditor::new(
21001 "Proposed changes",
21002 proposed_changes_buffers,
21003 self.project.clone(),
21004 window,
21005 cx,
21006 )
21007 });
21008
21009 window.defer(cx, move |window, cx| {
21010 workspace.update(cx, |workspace, cx| {
21011 workspace.active_pane().update(cx, |pane, cx| {
21012 pane.add_item(
21013 Box::new(proposed_changes_editor),
21014 true,
21015 true,
21016 None,
21017 window,
21018 cx,
21019 );
21020 });
21021 });
21022 });
21023 }
21024
21025 pub fn open_excerpts_in_split(
21026 &mut self,
21027 _: &OpenExcerptsSplit,
21028 window: &mut Window,
21029 cx: &mut Context<Self>,
21030 ) {
21031 self.open_excerpts_common(None, true, window, cx)
21032 }
21033
21034 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21035 self.open_excerpts_common(None, false, window, cx)
21036 }
21037
21038 fn open_excerpts_common(
21039 &mut self,
21040 jump_data: Option<JumpData>,
21041 split: bool,
21042 window: &mut Window,
21043 cx: &mut Context<Self>,
21044 ) {
21045 let Some(workspace) = self.workspace() else {
21046 cx.propagate();
21047 return;
21048 };
21049
21050 if self.buffer.read(cx).is_singleton() {
21051 cx.propagate();
21052 return;
21053 }
21054
21055 let mut new_selections_by_buffer = HashMap::default();
21056 match &jump_data {
21057 Some(JumpData::MultiBufferPoint {
21058 excerpt_id,
21059 position,
21060 anchor,
21061 line_offset_from_top,
21062 }) => {
21063 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21064 if let Some(buffer) = multi_buffer_snapshot
21065 .buffer_id_for_excerpt(*excerpt_id)
21066 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21067 {
21068 let buffer_snapshot = buffer.read(cx).snapshot();
21069 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21070 language::ToPoint::to_point(anchor, &buffer_snapshot)
21071 } else {
21072 buffer_snapshot.clip_point(*position, Bias::Left)
21073 };
21074 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21075 new_selections_by_buffer.insert(
21076 buffer,
21077 (
21078 vec![jump_to_offset..jump_to_offset],
21079 Some(*line_offset_from_top),
21080 ),
21081 );
21082 }
21083 }
21084 Some(JumpData::MultiBufferRow {
21085 row,
21086 line_offset_from_top,
21087 }) => {
21088 let point = MultiBufferPoint::new(row.0, 0);
21089 if let Some((buffer, buffer_point, _)) =
21090 self.buffer.read(cx).point_to_buffer_point(point, cx)
21091 {
21092 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21093 new_selections_by_buffer
21094 .entry(buffer)
21095 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21096 .0
21097 .push(buffer_offset..buffer_offset)
21098 }
21099 }
21100 None => {
21101 let selections = self.selections.all::<usize>(cx);
21102 let multi_buffer = self.buffer.read(cx);
21103 for selection in selections {
21104 for (snapshot, range, _, anchor) in multi_buffer
21105 .snapshot(cx)
21106 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21107 {
21108 if let Some(anchor) = anchor {
21109 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21110 else {
21111 continue;
21112 };
21113 let offset = text::ToOffset::to_offset(
21114 &anchor.text_anchor,
21115 &buffer_handle.read(cx).snapshot(),
21116 );
21117 let range = offset..offset;
21118 new_selections_by_buffer
21119 .entry(buffer_handle)
21120 .or_insert((Vec::new(), None))
21121 .0
21122 .push(range)
21123 } else {
21124 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21125 else {
21126 continue;
21127 };
21128 new_selections_by_buffer
21129 .entry(buffer_handle)
21130 .or_insert((Vec::new(), None))
21131 .0
21132 .push(range)
21133 }
21134 }
21135 }
21136 }
21137 }
21138
21139 new_selections_by_buffer
21140 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21141
21142 if new_selections_by_buffer.is_empty() {
21143 return;
21144 }
21145
21146 // We defer the pane interaction because we ourselves are a workspace item
21147 // and activating a new item causes the pane to call a method on us reentrantly,
21148 // which panics if we're on the stack.
21149 window.defer(cx, move |window, cx| {
21150 workspace.update(cx, |workspace, cx| {
21151 let pane = if split {
21152 workspace.adjacent_pane(window, cx)
21153 } else {
21154 workspace.active_pane().clone()
21155 };
21156
21157 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21158 let editor = buffer
21159 .read(cx)
21160 .file()
21161 .is_none()
21162 .then(|| {
21163 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21164 // so `workspace.open_project_item` will never find them, always opening a new editor.
21165 // Instead, we try to activate the existing editor in the pane first.
21166 let (editor, pane_item_index) =
21167 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21168 let editor = item.downcast::<Editor>()?;
21169 let singleton_buffer =
21170 editor.read(cx).buffer().read(cx).as_singleton()?;
21171 if singleton_buffer == buffer {
21172 Some((editor, i))
21173 } else {
21174 None
21175 }
21176 })?;
21177 pane.update(cx, |pane, cx| {
21178 pane.activate_item(pane_item_index, true, true, window, cx)
21179 });
21180 Some(editor)
21181 })
21182 .flatten()
21183 .unwrap_or_else(|| {
21184 workspace.open_project_item::<Self>(
21185 pane.clone(),
21186 buffer,
21187 true,
21188 true,
21189 window,
21190 cx,
21191 )
21192 });
21193
21194 editor.update(cx, |editor, cx| {
21195 let autoscroll = match scroll_offset {
21196 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21197 None => Autoscroll::newest(),
21198 };
21199 let nav_history = editor.nav_history.take();
21200 editor.change_selections(
21201 SelectionEffects::scroll(autoscroll),
21202 window,
21203 cx,
21204 |s| {
21205 s.select_ranges(ranges);
21206 },
21207 );
21208 editor.nav_history = nav_history;
21209 });
21210 }
21211 })
21212 });
21213 }
21214
21215 // For now, don't allow opening excerpts in buffers that aren't backed by
21216 // regular project files.
21217 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21218 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21219 }
21220
21221 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21222 let snapshot = self.buffer.read(cx).read(cx);
21223 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21224 Some(
21225 ranges
21226 .iter()
21227 .map(move |range| {
21228 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21229 })
21230 .collect(),
21231 )
21232 }
21233
21234 fn selection_replacement_ranges(
21235 &self,
21236 range: Range<OffsetUtf16>,
21237 cx: &mut App,
21238 ) -> Vec<Range<OffsetUtf16>> {
21239 let selections = self.selections.all::<OffsetUtf16>(cx);
21240 let newest_selection = selections
21241 .iter()
21242 .max_by_key(|selection| selection.id)
21243 .unwrap();
21244 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21245 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21246 let snapshot = self.buffer.read(cx).read(cx);
21247 selections
21248 .into_iter()
21249 .map(|mut selection| {
21250 selection.start.0 =
21251 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21252 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21253 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21254 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21255 })
21256 .collect()
21257 }
21258
21259 fn report_editor_event(
21260 &self,
21261 reported_event: ReportEditorEvent,
21262 file_extension: Option<String>,
21263 cx: &App,
21264 ) {
21265 if cfg!(any(test, feature = "test-support")) {
21266 return;
21267 }
21268
21269 let Some(project) = &self.project else { return };
21270
21271 // If None, we are in a file without an extension
21272 let file = self
21273 .buffer
21274 .read(cx)
21275 .as_singleton()
21276 .and_then(|b| b.read(cx).file());
21277 let file_extension = file_extension.or(file
21278 .as_ref()
21279 .and_then(|file| Path::new(file.file_name(cx)).extension())
21280 .and_then(|e| e.to_str())
21281 .map(|a| a.to_string()));
21282
21283 let vim_mode = vim_enabled(cx);
21284
21285 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21286 let copilot_enabled = edit_predictions_provider
21287 == language::language_settings::EditPredictionProvider::Copilot;
21288 let copilot_enabled_for_language = self
21289 .buffer
21290 .read(cx)
21291 .language_settings(cx)
21292 .show_edit_predictions;
21293
21294 let project = project.read(cx);
21295 let event_type = reported_event.event_type();
21296
21297 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21298 telemetry::event!(
21299 event_type,
21300 type = if auto_saved {"autosave"} else {"manual"},
21301 file_extension,
21302 vim_mode,
21303 copilot_enabled,
21304 copilot_enabled_for_language,
21305 edit_predictions_provider,
21306 is_via_ssh = project.is_via_remote_server(),
21307 );
21308 } else {
21309 telemetry::event!(
21310 event_type,
21311 file_extension,
21312 vim_mode,
21313 copilot_enabled,
21314 copilot_enabled_for_language,
21315 edit_predictions_provider,
21316 is_via_ssh = project.is_via_remote_server(),
21317 );
21318 };
21319 }
21320
21321 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21322 /// with each line being an array of {text, highlight} objects.
21323 fn copy_highlight_json(
21324 &mut self,
21325 _: &CopyHighlightJson,
21326 window: &mut Window,
21327 cx: &mut Context<Self>,
21328 ) {
21329 #[derive(Serialize)]
21330 struct Chunk<'a> {
21331 text: String,
21332 highlight: Option<&'a str>,
21333 }
21334
21335 let snapshot = self.buffer.read(cx).snapshot(cx);
21336 let range = self
21337 .selected_text_range(false, window, cx)
21338 .and_then(|selection| {
21339 if selection.range.is_empty() {
21340 None
21341 } else {
21342 Some(selection.range)
21343 }
21344 })
21345 .unwrap_or_else(|| 0..snapshot.len());
21346
21347 let chunks = snapshot.chunks(range, true);
21348 let mut lines = Vec::new();
21349 let mut line: VecDeque<Chunk> = VecDeque::new();
21350
21351 let Some(style) = self.style.as_ref() else {
21352 return;
21353 };
21354
21355 for chunk in chunks {
21356 let highlight = chunk
21357 .syntax_highlight_id
21358 .and_then(|id| id.name(&style.syntax));
21359 let mut chunk_lines = chunk.text.split('\n').peekable();
21360 while let Some(text) = chunk_lines.next() {
21361 let mut merged_with_last_token = false;
21362 if let Some(last_token) = line.back_mut()
21363 && last_token.highlight == highlight
21364 {
21365 last_token.text.push_str(text);
21366 merged_with_last_token = true;
21367 }
21368
21369 if !merged_with_last_token {
21370 line.push_back(Chunk {
21371 text: text.into(),
21372 highlight,
21373 });
21374 }
21375
21376 if chunk_lines.peek().is_some() {
21377 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21378 line.pop_front();
21379 }
21380 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21381 line.pop_back();
21382 }
21383
21384 lines.push(mem::take(&mut line));
21385 }
21386 }
21387 }
21388
21389 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21390 return;
21391 };
21392 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21393 }
21394
21395 pub fn open_context_menu(
21396 &mut self,
21397 _: &OpenContextMenu,
21398 window: &mut Window,
21399 cx: &mut Context<Self>,
21400 ) {
21401 self.request_autoscroll(Autoscroll::newest(), cx);
21402 let position = self.selections.newest_display(cx).start;
21403 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21404 }
21405
21406 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21407 &self.inlay_hint_cache
21408 }
21409
21410 pub fn replay_insert_event(
21411 &mut self,
21412 text: &str,
21413 relative_utf16_range: Option<Range<isize>>,
21414 window: &mut Window,
21415 cx: &mut Context<Self>,
21416 ) {
21417 if !self.input_enabled {
21418 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21419 return;
21420 }
21421 if let Some(relative_utf16_range) = relative_utf16_range {
21422 let selections = self.selections.all::<OffsetUtf16>(cx);
21423 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21424 let new_ranges = selections.into_iter().map(|range| {
21425 let start = OffsetUtf16(
21426 range
21427 .head()
21428 .0
21429 .saturating_add_signed(relative_utf16_range.start),
21430 );
21431 let end = OffsetUtf16(
21432 range
21433 .head()
21434 .0
21435 .saturating_add_signed(relative_utf16_range.end),
21436 );
21437 start..end
21438 });
21439 s.select_ranges(new_ranges);
21440 });
21441 }
21442
21443 self.handle_input(text, window, cx);
21444 }
21445
21446 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21447 let Some(provider) = self.semantics_provider.as_ref() else {
21448 return false;
21449 };
21450
21451 let mut supports = false;
21452 self.buffer().update(cx, |this, cx| {
21453 this.for_each_buffer(|buffer| {
21454 supports |= provider.supports_inlay_hints(buffer, cx);
21455 });
21456 });
21457
21458 supports
21459 }
21460
21461 pub fn is_focused(&self, window: &Window) -> bool {
21462 self.focus_handle.is_focused(window)
21463 }
21464
21465 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21466 cx.emit(EditorEvent::Focused);
21467
21468 if let Some(descendant) = self
21469 .last_focused_descendant
21470 .take()
21471 .and_then(|descendant| descendant.upgrade())
21472 {
21473 window.focus(&descendant);
21474 } else {
21475 if let Some(blame) = self.blame.as_ref() {
21476 blame.update(cx, GitBlame::focus)
21477 }
21478
21479 self.blink_manager.update(cx, BlinkManager::enable);
21480 self.show_cursor_names(window, cx);
21481 self.buffer.update(cx, |buffer, cx| {
21482 buffer.finalize_last_transaction(cx);
21483 if self.leader_id.is_none() {
21484 buffer.set_active_selections(
21485 &self.selections.disjoint_anchors_arc(),
21486 self.selections.line_mode(),
21487 self.cursor_shape,
21488 cx,
21489 );
21490 }
21491 });
21492 }
21493 }
21494
21495 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21496 cx.emit(EditorEvent::FocusedIn)
21497 }
21498
21499 fn handle_focus_out(
21500 &mut self,
21501 event: FocusOutEvent,
21502 _window: &mut Window,
21503 cx: &mut Context<Self>,
21504 ) {
21505 if event.blurred != self.focus_handle {
21506 self.last_focused_descendant = Some(event.blurred);
21507 }
21508 self.selection_drag_state = SelectionDragState::None;
21509 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21510 }
21511
21512 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21513 self.blink_manager.update(cx, BlinkManager::disable);
21514 self.buffer
21515 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21516
21517 if let Some(blame) = self.blame.as_ref() {
21518 blame.update(cx, GitBlame::blur)
21519 }
21520 if !self.hover_state.focused(window, cx) {
21521 hide_hover(self, cx);
21522 }
21523 if !self
21524 .context_menu
21525 .borrow()
21526 .as_ref()
21527 .is_some_and(|context_menu| context_menu.focused(window, cx))
21528 {
21529 self.hide_context_menu(window, cx);
21530 }
21531 self.take_active_edit_prediction(cx);
21532 cx.emit(EditorEvent::Blurred);
21533 cx.notify();
21534 }
21535
21536 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21537 let mut pending: String = window
21538 .pending_input_keystrokes()
21539 .into_iter()
21540 .flatten()
21541 .filter_map(|keystroke| {
21542 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21543 keystroke.key_char.clone()
21544 } else {
21545 None
21546 }
21547 })
21548 .collect();
21549
21550 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21551 pending = "".to_string();
21552 }
21553
21554 let existing_pending = self
21555 .text_highlights::<PendingInput>(cx)
21556 .map(|(_, ranges)| ranges.to_vec());
21557 if existing_pending.is_none() && pending.is_empty() {
21558 return;
21559 }
21560 let transaction =
21561 self.transact(window, cx, |this, window, cx| {
21562 let selections = this.selections.all::<usize>(cx);
21563 let edits = selections
21564 .iter()
21565 .map(|selection| (selection.end..selection.end, pending.clone()));
21566 this.edit(edits, cx);
21567 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21568 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21569 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21570 }));
21571 });
21572 if let Some(existing_ranges) = existing_pending {
21573 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21574 this.edit(edits, cx);
21575 }
21576 });
21577
21578 let snapshot = self.snapshot(window, cx);
21579 let ranges = self
21580 .selections
21581 .all::<usize>(cx)
21582 .into_iter()
21583 .map(|selection| {
21584 snapshot.buffer_snapshot.anchor_after(selection.end)
21585 ..snapshot
21586 .buffer_snapshot
21587 .anchor_before(selection.end + pending.len())
21588 })
21589 .collect();
21590
21591 if pending.is_empty() {
21592 self.clear_highlights::<PendingInput>(cx);
21593 } else {
21594 self.highlight_text::<PendingInput>(
21595 ranges,
21596 HighlightStyle {
21597 underline: Some(UnderlineStyle {
21598 thickness: px(1.),
21599 color: None,
21600 wavy: false,
21601 }),
21602 ..Default::default()
21603 },
21604 cx,
21605 );
21606 }
21607
21608 self.ime_transaction = self.ime_transaction.or(transaction);
21609 if let Some(transaction) = self.ime_transaction {
21610 self.buffer.update(cx, |buffer, cx| {
21611 buffer.group_until_transaction(transaction, cx);
21612 });
21613 }
21614
21615 if self.text_highlights::<PendingInput>(cx).is_none() {
21616 self.ime_transaction.take();
21617 }
21618 }
21619
21620 pub fn register_action_renderer(
21621 &mut self,
21622 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21623 ) -> Subscription {
21624 let id = self.next_editor_action_id.post_inc();
21625 self.editor_actions
21626 .borrow_mut()
21627 .insert(id, Box::new(listener));
21628
21629 let editor_actions = self.editor_actions.clone();
21630 Subscription::new(move || {
21631 editor_actions.borrow_mut().remove(&id);
21632 })
21633 }
21634
21635 pub fn register_action<A: Action>(
21636 &mut self,
21637 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21638 ) -> Subscription {
21639 let id = self.next_editor_action_id.post_inc();
21640 let listener = Arc::new(listener);
21641 self.editor_actions.borrow_mut().insert(
21642 id,
21643 Box::new(move |_, window, _| {
21644 let listener = listener.clone();
21645 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21646 let action = action.downcast_ref().unwrap();
21647 if phase == DispatchPhase::Bubble {
21648 listener(action, window, cx)
21649 }
21650 })
21651 }),
21652 );
21653
21654 let editor_actions = self.editor_actions.clone();
21655 Subscription::new(move || {
21656 editor_actions.borrow_mut().remove(&id);
21657 })
21658 }
21659
21660 pub fn file_header_size(&self) -> u32 {
21661 FILE_HEADER_HEIGHT
21662 }
21663
21664 pub fn restore(
21665 &mut self,
21666 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21667 window: &mut Window,
21668 cx: &mut Context<Self>,
21669 ) {
21670 let workspace = self.workspace();
21671 let project = self.project();
21672 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21673 let mut tasks = Vec::new();
21674 for (buffer_id, changes) in revert_changes {
21675 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21676 buffer.update(cx, |buffer, cx| {
21677 buffer.edit(
21678 changes
21679 .into_iter()
21680 .map(|(range, text)| (range, text.to_string())),
21681 None,
21682 cx,
21683 );
21684 });
21685
21686 if let Some(project) =
21687 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21688 {
21689 project.update(cx, |project, cx| {
21690 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21691 })
21692 }
21693 }
21694 }
21695 tasks
21696 });
21697 cx.spawn_in(window, async move |_, cx| {
21698 for (buffer, task) in save_tasks {
21699 let result = task.await;
21700 if result.is_err() {
21701 let Some(path) = buffer
21702 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21703 .ok()
21704 else {
21705 continue;
21706 };
21707 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21708 let Some(task) = cx
21709 .update_window_entity(workspace, |workspace, window, cx| {
21710 workspace
21711 .open_path_preview(path, None, false, false, false, window, cx)
21712 })
21713 .ok()
21714 else {
21715 continue;
21716 };
21717 task.await.log_err();
21718 }
21719 }
21720 }
21721 })
21722 .detach();
21723 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21724 selections.refresh()
21725 });
21726 }
21727
21728 pub fn to_pixel_point(
21729 &self,
21730 source: multi_buffer::Anchor,
21731 editor_snapshot: &EditorSnapshot,
21732 window: &mut Window,
21733 ) -> Option<gpui::Point<Pixels>> {
21734 let source_point = source.to_display_point(editor_snapshot);
21735 self.display_to_pixel_point(source_point, editor_snapshot, window)
21736 }
21737
21738 pub fn display_to_pixel_point(
21739 &self,
21740 source: DisplayPoint,
21741 editor_snapshot: &EditorSnapshot,
21742 window: &mut Window,
21743 ) -> Option<gpui::Point<Pixels>> {
21744 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21745 let text_layout_details = self.text_layout_details(window);
21746 let scroll_top = text_layout_details
21747 .scroll_anchor
21748 .scroll_position(editor_snapshot)
21749 .y;
21750
21751 if source.row().as_f64() < scroll_top.floor() {
21752 return None;
21753 }
21754 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21755 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21756 Some(gpui::Point::new(source_x, source_y))
21757 }
21758
21759 pub fn has_visible_completions_menu(&self) -> bool {
21760 !self.edit_prediction_preview_is_active()
21761 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21762 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21763 })
21764 }
21765
21766 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21767 if self.mode.is_minimap() {
21768 return;
21769 }
21770 self.addons
21771 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21772 }
21773
21774 pub fn unregister_addon<T: Addon>(&mut self) {
21775 self.addons.remove(&std::any::TypeId::of::<T>());
21776 }
21777
21778 pub fn addon<T: Addon>(&self) -> Option<&T> {
21779 let type_id = std::any::TypeId::of::<T>();
21780 self.addons
21781 .get(&type_id)
21782 .and_then(|item| item.to_any().downcast_ref::<T>())
21783 }
21784
21785 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21786 let type_id = std::any::TypeId::of::<T>();
21787 self.addons
21788 .get_mut(&type_id)
21789 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21790 }
21791
21792 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21793 let text_layout_details = self.text_layout_details(window);
21794 let style = &text_layout_details.editor_style;
21795 let font_id = window.text_system().resolve_font(&style.text.font());
21796 let font_size = style.text.font_size.to_pixels(window.rem_size());
21797 let line_height = style.text.line_height_in_pixels(window.rem_size());
21798 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21799 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21800
21801 CharacterDimensions {
21802 em_width,
21803 em_advance,
21804 line_height,
21805 }
21806 }
21807
21808 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21809 self.load_diff_task.clone()
21810 }
21811
21812 fn read_metadata_from_db(
21813 &mut self,
21814 item_id: u64,
21815 workspace_id: WorkspaceId,
21816 window: &mut Window,
21817 cx: &mut Context<Editor>,
21818 ) {
21819 if self.is_singleton(cx)
21820 && !self.mode.is_minimap()
21821 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21822 {
21823 let buffer_snapshot = OnceCell::new();
21824
21825 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21826 && !folds.is_empty()
21827 {
21828 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21829 self.fold_ranges(
21830 folds
21831 .into_iter()
21832 .map(|(start, end)| {
21833 snapshot.clip_offset(start, Bias::Left)
21834 ..snapshot.clip_offset(end, Bias::Right)
21835 })
21836 .collect(),
21837 false,
21838 window,
21839 cx,
21840 );
21841 }
21842
21843 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21844 && !selections.is_empty()
21845 {
21846 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21847 // skip adding the initial selection to selection history
21848 self.selection_history.mode = SelectionHistoryMode::Skipping;
21849 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21850 s.select_ranges(selections.into_iter().map(|(start, end)| {
21851 snapshot.clip_offset(start, Bias::Left)
21852 ..snapshot.clip_offset(end, Bias::Right)
21853 }));
21854 });
21855 self.selection_history.mode = SelectionHistoryMode::Normal;
21856 };
21857 }
21858
21859 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21860 }
21861
21862 fn update_lsp_data(
21863 &mut self,
21864 ignore_cache: bool,
21865 for_buffer: Option<BufferId>,
21866 window: &mut Window,
21867 cx: &mut Context<'_, Self>,
21868 ) {
21869 self.pull_diagnostics(for_buffer, window, cx);
21870 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21871 }
21872}
21873
21874fn edit_for_markdown_paste<'a>(
21875 buffer: &MultiBufferSnapshot,
21876 range: Range<usize>,
21877 to_insert: &'a str,
21878 url: Option<url::Url>,
21879) -> (Range<usize>, Cow<'a, str>) {
21880 if url.is_none() {
21881 return (range, Cow::Borrowed(to_insert));
21882 };
21883
21884 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
21885
21886 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
21887 Cow::Borrowed(to_insert)
21888 } else {
21889 Cow::Owned(format!("[{old_text}]({to_insert})"))
21890 };
21891 (range, new_text)
21892}
21893
21894fn vim_enabled(cx: &App) -> bool {
21895 vim_mode_setting::VimModeSetting::try_get(cx)
21896 .map(|vim_mode| vim_mode.0)
21897 .unwrap_or(false)
21898}
21899
21900fn process_completion_for_edit(
21901 completion: &Completion,
21902 intent: CompletionIntent,
21903 buffer: &Entity<Buffer>,
21904 cursor_position: &text::Anchor,
21905 cx: &mut Context<Editor>,
21906) -> CompletionEdit {
21907 let buffer = buffer.read(cx);
21908 let buffer_snapshot = buffer.snapshot();
21909 let (snippet, new_text) = if completion.is_snippet() {
21910 // Workaround for typescript language server issues so that methods don't expand within
21911 // strings and functions with type expressions. The previous point is used because the query
21912 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21913 let mut snippet_source = completion.new_text.clone();
21914 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21915 previous_point.column = previous_point.column.saturating_sub(1);
21916 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21917 && scope.prefers_label_for_snippet_in_completion()
21918 && let Some(label) = completion.label()
21919 && matches!(
21920 completion.kind(),
21921 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21922 )
21923 {
21924 snippet_source = label;
21925 }
21926 match Snippet::parse(&snippet_source).log_err() {
21927 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21928 None => (None, completion.new_text.clone()),
21929 }
21930 } else {
21931 (None, completion.new_text.clone())
21932 };
21933
21934 let mut range_to_replace = {
21935 let replace_range = &completion.replace_range;
21936 if let CompletionSource::Lsp {
21937 insert_range: Some(insert_range),
21938 ..
21939 } = &completion.source
21940 {
21941 debug_assert_eq!(
21942 insert_range.start, replace_range.start,
21943 "insert_range and replace_range should start at the same position"
21944 );
21945 debug_assert!(
21946 insert_range
21947 .start
21948 .cmp(cursor_position, &buffer_snapshot)
21949 .is_le(),
21950 "insert_range should start before or at cursor position"
21951 );
21952 debug_assert!(
21953 replace_range
21954 .start
21955 .cmp(cursor_position, &buffer_snapshot)
21956 .is_le(),
21957 "replace_range should start before or at cursor position"
21958 );
21959
21960 let should_replace = match intent {
21961 CompletionIntent::CompleteWithInsert => false,
21962 CompletionIntent::CompleteWithReplace => true,
21963 CompletionIntent::Complete | CompletionIntent::Compose => {
21964 let insert_mode =
21965 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21966 .completions
21967 .lsp_insert_mode;
21968 match insert_mode {
21969 LspInsertMode::Insert => false,
21970 LspInsertMode::Replace => true,
21971 LspInsertMode::ReplaceSubsequence => {
21972 let mut text_to_replace = buffer.chars_for_range(
21973 buffer.anchor_before(replace_range.start)
21974 ..buffer.anchor_after(replace_range.end),
21975 );
21976 let mut current_needle = text_to_replace.next();
21977 for haystack_ch in completion.label.text.chars() {
21978 if let Some(needle_ch) = current_needle
21979 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21980 {
21981 current_needle = text_to_replace.next();
21982 }
21983 }
21984 current_needle.is_none()
21985 }
21986 LspInsertMode::ReplaceSuffix => {
21987 if replace_range
21988 .end
21989 .cmp(cursor_position, &buffer_snapshot)
21990 .is_gt()
21991 {
21992 let range_after_cursor = *cursor_position..replace_range.end;
21993 let text_after_cursor = buffer
21994 .text_for_range(
21995 buffer.anchor_before(range_after_cursor.start)
21996 ..buffer.anchor_after(range_after_cursor.end),
21997 )
21998 .collect::<String>()
21999 .to_ascii_lowercase();
22000 completion
22001 .label
22002 .text
22003 .to_ascii_lowercase()
22004 .ends_with(&text_after_cursor)
22005 } else {
22006 true
22007 }
22008 }
22009 }
22010 }
22011 };
22012
22013 if should_replace {
22014 replace_range.clone()
22015 } else {
22016 insert_range.clone()
22017 }
22018 } else {
22019 replace_range.clone()
22020 }
22021 };
22022
22023 if range_to_replace
22024 .end
22025 .cmp(cursor_position, &buffer_snapshot)
22026 .is_lt()
22027 {
22028 range_to_replace.end = *cursor_position;
22029 }
22030
22031 CompletionEdit {
22032 new_text,
22033 replace_range: range_to_replace.to_offset(buffer),
22034 snippet,
22035 }
22036}
22037
22038struct CompletionEdit {
22039 new_text: String,
22040 replace_range: Range<usize>,
22041 snippet: Option<Snippet>,
22042}
22043
22044fn insert_extra_newline_brackets(
22045 buffer: &MultiBufferSnapshot,
22046 range: Range<usize>,
22047 language: &language::LanguageScope,
22048) -> bool {
22049 let leading_whitespace_len = buffer
22050 .reversed_chars_at(range.start)
22051 .take_while(|c| c.is_whitespace() && *c != '\n')
22052 .map(|c| c.len_utf8())
22053 .sum::<usize>();
22054 let trailing_whitespace_len = buffer
22055 .chars_at(range.end)
22056 .take_while(|c| c.is_whitespace() && *c != '\n')
22057 .map(|c| c.len_utf8())
22058 .sum::<usize>();
22059 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22060
22061 language.brackets().any(|(pair, enabled)| {
22062 let pair_start = pair.start.trim_end();
22063 let pair_end = pair.end.trim_start();
22064
22065 enabled
22066 && pair.newline
22067 && buffer.contains_str_at(range.end, pair_end)
22068 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22069 })
22070}
22071
22072fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22073 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22074 [(buffer, range, _)] => (*buffer, range.clone()),
22075 _ => return false,
22076 };
22077 let pair = {
22078 let mut result: Option<BracketMatch> = None;
22079
22080 for pair in buffer
22081 .all_bracket_ranges(range.clone())
22082 .filter(move |pair| {
22083 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22084 })
22085 {
22086 let len = pair.close_range.end - pair.open_range.start;
22087
22088 if let Some(existing) = &result {
22089 let existing_len = existing.close_range.end - existing.open_range.start;
22090 if len > existing_len {
22091 continue;
22092 }
22093 }
22094
22095 result = Some(pair);
22096 }
22097
22098 result
22099 };
22100 let Some(pair) = pair else {
22101 return false;
22102 };
22103 pair.newline_only
22104 && buffer
22105 .chars_for_range(pair.open_range.end..range.start)
22106 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22107 .all(|c| c.is_whitespace() && c != '\n')
22108}
22109
22110fn update_uncommitted_diff_for_buffer(
22111 editor: Entity<Editor>,
22112 project: &Entity<Project>,
22113 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22114 buffer: Entity<MultiBuffer>,
22115 cx: &mut App,
22116) -> Task<()> {
22117 let mut tasks = Vec::new();
22118 project.update(cx, |project, cx| {
22119 for buffer in buffers {
22120 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22121 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22122 }
22123 }
22124 });
22125 cx.spawn(async move |cx| {
22126 let diffs = future::join_all(tasks).await;
22127 if editor
22128 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22129 .unwrap_or(false)
22130 {
22131 return;
22132 }
22133
22134 buffer
22135 .update(cx, |buffer, cx| {
22136 for diff in diffs.into_iter().flatten() {
22137 buffer.add_diff(diff, cx);
22138 }
22139 })
22140 .ok();
22141 })
22142}
22143
22144fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22145 let tab_size = tab_size.get() as usize;
22146 let mut width = offset;
22147
22148 for ch in text.chars() {
22149 width += if ch == '\t' {
22150 tab_size - (width % tab_size)
22151 } else {
22152 1
22153 };
22154 }
22155
22156 width - offset
22157}
22158
22159#[cfg(test)]
22160mod tests {
22161 use super::*;
22162
22163 #[test]
22164 fn test_string_size_with_expanded_tabs() {
22165 let nz = |val| NonZeroU32::new(val).unwrap();
22166 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22167 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22168 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22169 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22170 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22171 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22172 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22173 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22174 }
22175}
22176
22177/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22178struct WordBreakingTokenizer<'a> {
22179 input: &'a str,
22180}
22181
22182impl<'a> WordBreakingTokenizer<'a> {
22183 fn new(input: &'a str) -> Self {
22184 Self { input }
22185 }
22186}
22187
22188fn is_char_ideographic(ch: char) -> bool {
22189 use unicode_script::Script::*;
22190 use unicode_script::UnicodeScript;
22191 matches!(ch.script(), Han | Tangut | Yi)
22192}
22193
22194fn is_grapheme_ideographic(text: &str) -> bool {
22195 text.chars().any(is_char_ideographic)
22196}
22197
22198fn is_grapheme_whitespace(text: &str) -> bool {
22199 text.chars().any(|x| x.is_whitespace())
22200}
22201
22202fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22203 text.chars()
22204 .next()
22205 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22206}
22207
22208#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22209enum WordBreakToken<'a> {
22210 Word { token: &'a str, grapheme_len: usize },
22211 InlineWhitespace { token: &'a str, grapheme_len: usize },
22212 Newline,
22213}
22214
22215impl<'a> Iterator for WordBreakingTokenizer<'a> {
22216 /// Yields a span, the count of graphemes in the token, and whether it was
22217 /// whitespace. Note that it also breaks at word boundaries.
22218 type Item = WordBreakToken<'a>;
22219
22220 fn next(&mut self) -> Option<Self::Item> {
22221 use unicode_segmentation::UnicodeSegmentation;
22222 if self.input.is_empty() {
22223 return None;
22224 }
22225
22226 let mut iter = self.input.graphemes(true).peekable();
22227 let mut offset = 0;
22228 let mut grapheme_len = 0;
22229 if let Some(first_grapheme) = iter.next() {
22230 let is_newline = first_grapheme == "\n";
22231 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22232 offset += first_grapheme.len();
22233 grapheme_len += 1;
22234 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22235 if let Some(grapheme) = iter.peek().copied()
22236 && should_stay_with_preceding_ideograph(grapheme)
22237 {
22238 offset += grapheme.len();
22239 grapheme_len += 1;
22240 }
22241 } else {
22242 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22243 let mut next_word_bound = words.peek().copied();
22244 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22245 next_word_bound = words.next();
22246 }
22247 while let Some(grapheme) = iter.peek().copied() {
22248 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22249 break;
22250 };
22251 if is_grapheme_whitespace(grapheme) != is_whitespace
22252 || (grapheme == "\n") != is_newline
22253 {
22254 break;
22255 };
22256 offset += grapheme.len();
22257 grapheme_len += 1;
22258 iter.next();
22259 }
22260 }
22261 let token = &self.input[..offset];
22262 self.input = &self.input[offset..];
22263 if token == "\n" {
22264 Some(WordBreakToken::Newline)
22265 } else if is_whitespace {
22266 Some(WordBreakToken::InlineWhitespace {
22267 token,
22268 grapheme_len,
22269 })
22270 } else {
22271 Some(WordBreakToken::Word {
22272 token,
22273 grapheme_len,
22274 })
22275 }
22276 } else {
22277 None
22278 }
22279 }
22280}
22281
22282#[test]
22283fn test_word_breaking_tokenizer() {
22284 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22285 ("", &[]),
22286 (" ", &[whitespace(" ", 2)]),
22287 ("Ʒ", &[word("Ʒ", 1)]),
22288 ("Ǽ", &[word("Ǽ", 1)]),
22289 ("⋑", &[word("⋑", 1)]),
22290 ("⋑⋑", &[word("⋑⋑", 2)]),
22291 (
22292 "原理,进而",
22293 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22294 ),
22295 (
22296 "hello world",
22297 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22298 ),
22299 (
22300 "hello, world",
22301 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22302 ),
22303 (
22304 " hello world",
22305 &[
22306 whitespace(" ", 2),
22307 word("hello", 5),
22308 whitespace(" ", 1),
22309 word("world", 5),
22310 ],
22311 ),
22312 (
22313 "这是什么 \n 钢笔",
22314 &[
22315 word("这", 1),
22316 word("是", 1),
22317 word("什", 1),
22318 word("么", 1),
22319 whitespace(" ", 1),
22320 newline(),
22321 whitespace(" ", 1),
22322 word("钢", 1),
22323 word("笔", 1),
22324 ],
22325 ),
22326 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22327 ];
22328
22329 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22330 WordBreakToken::Word {
22331 token,
22332 grapheme_len,
22333 }
22334 }
22335
22336 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22337 WordBreakToken::InlineWhitespace {
22338 token,
22339 grapheme_len,
22340 }
22341 }
22342
22343 fn newline() -> WordBreakToken<'static> {
22344 WordBreakToken::Newline
22345 }
22346
22347 for (input, result) in tests {
22348 assert_eq!(
22349 WordBreakingTokenizer::new(input)
22350 .collect::<Vec<_>>()
22351 .as_slice(),
22352 *result,
22353 );
22354 }
22355}
22356
22357fn wrap_with_prefix(
22358 first_line_prefix: String,
22359 subsequent_lines_prefix: String,
22360 unwrapped_text: String,
22361 wrap_column: usize,
22362 tab_size: NonZeroU32,
22363 preserve_existing_whitespace: bool,
22364) -> String {
22365 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22366 let subsequent_lines_prefix_len =
22367 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22368 let mut wrapped_text = String::new();
22369 let mut current_line = first_line_prefix;
22370 let mut is_first_line = true;
22371
22372 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22373 let mut current_line_len = first_line_prefix_len;
22374 let mut in_whitespace = false;
22375 for token in tokenizer {
22376 let have_preceding_whitespace = in_whitespace;
22377 match token {
22378 WordBreakToken::Word {
22379 token,
22380 grapheme_len,
22381 } => {
22382 in_whitespace = false;
22383 let current_prefix_len = if is_first_line {
22384 first_line_prefix_len
22385 } else {
22386 subsequent_lines_prefix_len
22387 };
22388 if current_line_len + grapheme_len > wrap_column
22389 && current_line_len != current_prefix_len
22390 {
22391 wrapped_text.push_str(current_line.trim_end());
22392 wrapped_text.push('\n');
22393 is_first_line = false;
22394 current_line = subsequent_lines_prefix.clone();
22395 current_line_len = subsequent_lines_prefix_len;
22396 }
22397 current_line.push_str(token);
22398 current_line_len += grapheme_len;
22399 }
22400 WordBreakToken::InlineWhitespace {
22401 mut token,
22402 mut grapheme_len,
22403 } => {
22404 in_whitespace = true;
22405 if have_preceding_whitespace && !preserve_existing_whitespace {
22406 continue;
22407 }
22408 if !preserve_existing_whitespace {
22409 // Keep a single whitespace grapheme as-is
22410 if let Some(first) =
22411 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22412 {
22413 token = first;
22414 } else {
22415 token = " ";
22416 }
22417 grapheme_len = 1;
22418 }
22419 let current_prefix_len = if is_first_line {
22420 first_line_prefix_len
22421 } else {
22422 subsequent_lines_prefix_len
22423 };
22424 if current_line_len + grapheme_len > wrap_column {
22425 wrapped_text.push_str(current_line.trim_end());
22426 wrapped_text.push('\n');
22427 is_first_line = false;
22428 current_line = subsequent_lines_prefix.clone();
22429 current_line_len = subsequent_lines_prefix_len;
22430 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22431 current_line.push_str(token);
22432 current_line_len += grapheme_len;
22433 }
22434 }
22435 WordBreakToken::Newline => {
22436 in_whitespace = true;
22437 let current_prefix_len = if is_first_line {
22438 first_line_prefix_len
22439 } else {
22440 subsequent_lines_prefix_len
22441 };
22442 if preserve_existing_whitespace {
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 have_preceding_whitespace {
22449 continue;
22450 } else if current_line_len + 1 > wrap_column
22451 && current_line_len != current_prefix_len
22452 {
22453 wrapped_text.push_str(current_line.trim_end());
22454 wrapped_text.push('\n');
22455 is_first_line = false;
22456 current_line = subsequent_lines_prefix.clone();
22457 current_line_len = subsequent_lines_prefix_len;
22458 } else if current_line_len != current_prefix_len {
22459 current_line.push(' ');
22460 current_line_len += 1;
22461 }
22462 }
22463 }
22464 }
22465
22466 if !current_line.is_empty() {
22467 wrapped_text.push_str(¤t_line);
22468 }
22469 wrapped_text
22470}
22471
22472#[test]
22473fn test_wrap_with_prefix() {
22474 assert_eq!(
22475 wrap_with_prefix(
22476 "# ".to_string(),
22477 "# ".to_string(),
22478 "abcdefg".to_string(),
22479 4,
22480 NonZeroU32::new(4).unwrap(),
22481 false,
22482 ),
22483 "# abcdefg"
22484 );
22485 assert_eq!(
22486 wrap_with_prefix(
22487 "".to_string(),
22488 "".to_string(),
22489 "\thello world".to_string(),
22490 8,
22491 NonZeroU32::new(4).unwrap(),
22492 false,
22493 ),
22494 "hello\nworld"
22495 );
22496 assert_eq!(
22497 wrap_with_prefix(
22498 "// ".to_string(),
22499 "// ".to_string(),
22500 "xx \nyy zz aa bb cc".to_string(),
22501 12,
22502 NonZeroU32::new(4).unwrap(),
22503 false,
22504 ),
22505 "// xx yy zz\n// aa bb cc"
22506 );
22507 assert_eq!(
22508 wrap_with_prefix(
22509 String::new(),
22510 String::new(),
22511 "这是什么 \n 钢笔".to_string(),
22512 3,
22513 NonZeroU32::new(4).unwrap(),
22514 false,
22515 ),
22516 "这是什\n么 钢\n笔"
22517 );
22518 assert_eq!(
22519 wrap_with_prefix(
22520 String::new(),
22521 String::new(),
22522 format!("foo{}bar", '\u{2009}'), // thin space
22523 80,
22524 NonZeroU32::new(4).unwrap(),
22525 false,
22526 ),
22527 format!("foo{}bar", '\u{2009}')
22528 );
22529}
22530
22531pub trait CollaborationHub {
22532 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22533 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22534 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22535}
22536
22537impl CollaborationHub for Entity<Project> {
22538 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22539 self.read(cx).collaborators()
22540 }
22541
22542 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22543 self.read(cx).user_store().read(cx).participant_indices()
22544 }
22545
22546 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22547 let this = self.read(cx);
22548 let user_ids = this.collaborators().values().map(|c| c.user_id);
22549 this.user_store().read(cx).participant_names(user_ids, cx)
22550 }
22551}
22552
22553pub trait SemanticsProvider {
22554 fn hover(
22555 &self,
22556 buffer: &Entity<Buffer>,
22557 position: text::Anchor,
22558 cx: &mut App,
22559 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22560
22561 fn inline_values(
22562 &self,
22563 buffer_handle: Entity<Buffer>,
22564 range: Range<text::Anchor>,
22565 cx: &mut App,
22566 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22567
22568 fn inlay_hints(
22569 &self,
22570 buffer_handle: Entity<Buffer>,
22571 range: Range<text::Anchor>,
22572 cx: &mut App,
22573 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22574
22575 fn resolve_inlay_hint(
22576 &self,
22577 hint: InlayHint,
22578 buffer_handle: Entity<Buffer>,
22579 server_id: LanguageServerId,
22580 cx: &mut App,
22581 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22582
22583 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22584
22585 fn document_highlights(
22586 &self,
22587 buffer: &Entity<Buffer>,
22588 position: text::Anchor,
22589 cx: &mut App,
22590 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22591
22592 fn definitions(
22593 &self,
22594 buffer: &Entity<Buffer>,
22595 position: text::Anchor,
22596 kind: GotoDefinitionKind,
22597 cx: &mut App,
22598 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22599
22600 fn range_for_rename(
22601 &self,
22602 buffer: &Entity<Buffer>,
22603 position: text::Anchor,
22604 cx: &mut App,
22605 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22606
22607 fn perform_rename(
22608 &self,
22609 buffer: &Entity<Buffer>,
22610 position: text::Anchor,
22611 new_name: String,
22612 cx: &mut App,
22613 ) -> Option<Task<Result<ProjectTransaction>>>;
22614}
22615
22616pub trait CompletionProvider {
22617 fn completions(
22618 &self,
22619 excerpt_id: ExcerptId,
22620 buffer: &Entity<Buffer>,
22621 buffer_position: text::Anchor,
22622 trigger: CompletionContext,
22623 window: &mut Window,
22624 cx: &mut Context<Editor>,
22625 ) -> Task<Result<Vec<CompletionResponse>>>;
22626
22627 fn resolve_completions(
22628 &self,
22629 _buffer: Entity<Buffer>,
22630 _completion_indices: Vec<usize>,
22631 _completions: Rc<RefCell<Box<[Completion]>>>,
22632 _cx: &mut Context<Editor>,
22633 ) -> Task<Result<bool>> {
22634 Task::ready(Ok(false))
22635 }
22636
22637 fn apply_additional_edits_for_completion(
22638 &self,
22639 _buffer: Entity<Buffer>,
22640 _completions: Rc<RefCell<Box<[Completion]>>>,
22641 _completion_index: usize,
22642 _push_to_history: bool,
22643 _cx: &mut Context<Editor>,
22644 ) -> Task<Result<Option<language::Transaction>>> {
22645 Task::ready(Ok(None))
22646 }
22647
22648 fn is_completion_trigger(
22649 &self,
22650 buffer: &Entity<Buffer>,
22651 position: language::Anchor,
22652 text: &str,
22653 trigger_in_words: bool,
22654 menu_is_open: bool,
22655 cx: &mut Context<Editor>,
22656 ) -> bool;
22657
22658 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22659
22660 fn sort_completions(&self) -> bool {
22661 true
22662 }
22663
22664 fn filter_completions(&self) -> bool {
22665 true
22666 }
22667}
22668
22669pub trait CodeActionProvider {
22670 fn id(&self) -> Arc<str>;
22671
22672 fn code_actions(
22673 &self,
22674 buffer: &Entity<Buffer>,
22675 range: Range<text::Anchor>,
22676 window: &mut Window,
22677 cx: &mut App,
22678 ) -> Task<Result<Vec<CodeAction>>>;
22679
22680 fn apply_code_action(
22681 &self,
22682 buffer_handle: Entity<Buffer>,
22683 action: CodeAction,
22684 excerpt_id: ExcerptId,
22685 push_to_history: bool,
22686 window: &mut Window,
22687 cx: &mut App,
22688 ) -> Task<Result<ProjectTransaction>>;
22689}
22690
22691impl CodeActionProvider for Entity<Project> {
22692 fn id(&self) -> Arc<str> {
22693 "project".into()
22694 }
22695
22696 fn code_actions(
22697 &self,
22698 buffer: &Entity<Buffer>,
22699 range: Range<text::Anchor>,
22700 _window: &mut Window,
22701 cx: &mut App,
22702 ) -> Task<Result<Vec<CodeAction>>> {
22703 self.update(cx, |project, cx| {
22704 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22705 let code_actions = project.code_actions(buffer, range, None, cx);
22706 cx.background_spawn(async move {
22707 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22708 Ok(code_lens_actions
22709 .context("code lens fetch")?
22710 .into_iter()
22711 .flatten()
22712 .chain(
22713 code_actions
22714 .context("code action fetch")?
22715 .into_iter()
22716 .flatten(),
22717 )
22718 .collect())
22719 })
22720 })
22721 }
22722
22723 fn apply_code_action(
22724 &self,
22725 buffer_handle: Entity<Buffer>,
22726 action: CodeAction,
22727 _excerpt_id: ExcerptId,
22728 push_to_history: bool,
22729 _window: &mut Window,
22730 cx: &mut App,
22731 ) -> Task<Result<ProjectTransaction>> {
22732 self.update(cx, |project, cx| {
22733 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22734 })
22735 }
22736}
22737
22738fn snippet_completions(
22739 project: &Project,
22740 buffer: &Entity<Buffer>,
22741 buffer_position: text::Anchor,
22742 cx: &mut App,
22743) -> Task<Result<CompletionResponse>> {
22744 let languages = buffer.read(cx).languages_at(buffer_position);
22745 let snippet_store = project.snippets().read(cx);
22746
22747 let scopes: Vec<_> = languages
22748 .iter()
22749 .filter_map(|language| {
22750 let language_name = language.lsp_id();
22751 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22752
22753 if snippets.is_empty() {
22754 None
22755 } else {
22756 Some((language.default_scope(), snippets))
22757 }
22758 })
22759 .collect();
22760
22761 if scopes.is_empty() {
22762 return Task::ready(Ok(CompletionResponse {
22763 completions: vec![],
22764 display_options: CompletionDisplayOptions::default(),
22765 is_incomplete: false,
22766 }));
22767 }
22768
22769 let snapshot = buffer.read(cx).text_snapshot();
22770 let chars: String = snapshot
22771 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22772 .collect();
22773 let executor = cx.background_executor().clone();
22774
22775 cx.background_spawn(async move {
22776 let mut is_incomplete = false;
22777 let mut completions: Vec<Completion> = Vec::new();
22778 for (scope, snippets) in scopes.into_iter() {
22779 let classifier =
22780 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22781 let mut last_word = chars
22782 .chars()
22783 .take_while(|c| classifier.is_word(*c))
22784 .collect::<String>();
22785 last_word = last_word.chars().rev().collect();
22786
22787 if last_word.is_empty() {
22788 return Ok(CompletionResponse {
22789 completions: vec![],
22790 display_options: CompletionDisplayOptions::default(),
22791 is_incomplete: true,
22792 });
22793 }
22794
22795 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22796 let to_lsp = |point: &text::Anchor| {
22797 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22798 point_to_lsp(end)
22799 };
22800 let lsp_end = to_lsp(&buffer_position);
22801
22802 let candidates = snippets
22803 .iter()
22804 .enumerate()
22805 .flat_map(|(ix, snippet)| {
22806 snippet
22807 .prefix
22808 .iter()
22809 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22810 })
22811 .collect::<Vec<StringMatchCandidate>>();
22812
22813 const MAX_RESULTS: usize = 100;
22814 let mut matches = fuzzy::match_strings(
22815 &candidates,
22816 &last_word,
22817 last_word.chars().any(|c| c.is_uppercase()),
22818 true,
22819 MAX_RESULTS,
22820 &Default::default(),
22821 executor.clone(),
22822 )
22823 .await;
22824
22825 if matches.len() >= MAX_RESULTS {
22826 is_incomplete = true;
22827 }
22828
22829 // Remove all candidates where the query's start does not match the start of any word in the candidate
22830 if let Some(query_start) = last_word.chars().next() {
22831 matches.retain(|string_match| {
22832 split_words(&string_match.string).any(|word| {
22833 // Check that the first codepoint of the word as lowercase matches the first
22834 // codepoint of the query as lowercase
22835 word.chars()
22836 .flat_map(|codepoint| codepoint.to_lowercase())
22837 .zip(query_start.to_lowercase())
22838 .all(|(word_cp, query_cp)| word_cp == query_cp)
22839 })
22840 });
22841 }
22842
22843 let matched_strings = matches
22844 .into_iter()
22845 .map(|m| m.string)
22846 .collect::<HashSet<_>>();
22847
22848 completions.extend(snippets.iter().filter_map(|snippet| {
22849 let matching_prefix = snippet
22850 .prefix
22851 .iter()
22852 .find(|prefix| matched_strings.contains(*prefix))?;
22853 let start = as_offset - last_word.len();
22854 let start = snapshot.anchor_before(start);
22855 let range = start..buffer_position;
22856 let lsp_start = to_lsp(&start);
22857 let lsp_range = lsp::Range {
22858 start: lsp_start,
22859 end: lsp_end,
22860 };
22861 Some(Completion {
22862 replace_range: range,
22863 new_text: snippet.body.clone(),
22864 source: CompletionSource::Lsp {
22865 insert_range: None,
22866 server_id: LanguageServerId(usize::MAX),
22867 resolved: true,
22868 lsp_completion: Box::new(lsp::CompletionItem {
22869 label: snippet.prefix.first().unwrap().clone(),
22870 kind: Some(CompletionItemKind::SNIPPET),
22871 label_details: snippet.description.as_ref().map(|description| {
22872 lsp::CompletionItemLabelDetails {
22873 detail: Some(description.clone()),
22874 description: None,
22875 }
22876 }),
22877 insert_text_format: Some(InsertTextFormat::SNIPPET),
22878 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22879 lsp::InsertReplaceEdit {
22880 new_text: snippet.body.clone(),
22881 insert: lsp_range,
22882 replace: lsp_range,
22883 },
22884 )),
22885 filter_text: Some(snippet.body.clone()),
22886 sort_text: Some(char::MAX.to_string()),
22887 ..lsp::CompletionItem::default()
22888 }),
22889 lsp_defaults: None,
22890 },
22891 label: CodeLabel {
22892 text: matching_prefix.clone(),
22893 runs: Vec::new(),
22894 filter_range: 0..matching_prefix.len(),
22895 },
22896 icon_path: None,
22897 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22898 single_line: snippet.name.clone().into(),
22899 plain_text: snippet
22900 .description
22901 .clone()
22902 .map(|description| description.into()),
22903 }),
22904 insert_text_mode: None,
22905 confirm: None,
22906 })
22907 }))
22908 }
22909
22910 Ok(CompletionResponse {
22911 completions,
22912 display_options: CompletionDisplayOptions::default(),
22913 is_incomplete,
22914 })
22915 })
22916}
22917
22918impl CompletionProvider for Entity<Project> {
22919 fn completions(
22920 &self,
22921 _excerpt_id: ExcerptId,
22922 buffer: &Entity<Buffer>,
22923 buffer_position: text::Anchor,
22924 options: CompletionContext,
22925 _window: &mut Window,
22926 cx: &mut Context<Editor>,
22927 ) -> Task<Result<Vec<CompletionResponse>>> {
22928 self.update(cx, |project, cx| {
22929 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22930 let project_completions = project.completions(buffer, buffer_position, options, cx);
22931 cx.background_spawn(async move {
22932 let mut responses = project_completions.await?;
22933 let snippets = snippets.await?;
22934 if !snippets.completions.is_empty() {
22935 responses.push(snippets);
22936 }
22937 Ok(responses)
22938 })
22939 })
22940 }
22941
22942 fn resolve_completions(
22943 &self,
22944 buffer: Entity<Buffer>,
22945 completion_indices: Vec<usize>,
22946 completions: Rc<RefCell<Box<[Completion]>>>,
22947 cx: &mut Context<Editor>,
22948 ) -> Task<Result<bool>> {
22949 self.update(cx, |project, cx| {
22950 project.lsp_store().update(cx, |lsp_store, cx| {
22951 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22952 })
22953 })
22954 }
22955
22956 fn apply_additional_edits_for_completion(
22957 &self,
22958 buffer: Entity<Buffer>,
22959 completions: Rc<RefCell<Box<[Completion]>>>,
22960 completion_index: usize,
22961 push_to_history: bool,
22962 cx: &mut Context<Editor>,
22963 ) -> Task<Result<Option<language::Transaction>>> {
22964 self.update(cx, |project, cx| {
22965 project.lsp_store().update(cx, |lsp_store, cx| {
22966 lsp_store.apply_additional_edits_for_completion(
22967 buffer,
22968 completions,
22969 completion_index,
22970 push_to_history,
22971 cx,
22972 )
22973 })
22974 })
22975 }
22976
22977 fn is_completion_trigger(
22978 &self,
22979 buffer: &Entity<Buffer>,
22980 position: language::Anchor,
22981 text: &str,
22982 trigger_in_words: bool,
22983 menu_is_open: bool,
22984 cx: &mut Context<Editor>,
22985 ) -> bool {
22986 let mut chars = text.chars();
22987 let char = if let Some(char) = chars.next() {
22988 char
22989 } else {
22990 return false;
22991 };
22992 if chars.next().is_some() {
22993 return false;
22994 }
22995
22996 let buffer = buffer.read(cx);
22997 let snapshot = buffer.snapshot();
22998 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22999 return false;
23000 }
23001 let classifier = snapshot
23002 .char_classifier_at(position)
23003 .scope_context(Some(CharScopeContext::Completion));
23004 if trigger_in_words && classifier.is_word(char) {
23005 return true;
23006 }
23007
23008 buffer.completion_triggers().contains(text)
23009 }
23010}
23011
23012impl SemanticsProvider for Entity<Project> {
23013 fn hover(
23014 &self,
23015 buffer: &Entity<Buffer>,
23016 position: text::Anchor,
23017 cx: &mut App,
23018 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23019 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23020 }
23021
23022 fn document_highlights(
23023 &self,
23024 buffer: &Entity<Buffer>,
23025 position: text::Anchor,
23026 cx: &mut App,
23027 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23028 Some(self.update(cx, |project, cx| {
23029 project.document_highlights(buffer, position, cx)
23030 }))
23031 }
23032
23033 fn definitions(
23034 &self,
23035 buffer: &Entity<Buffer>,
23036 position: text::Anchor,
23037 kind: GotoDefinitionKind,
23038 cx: &mut App,
23039 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23040 Some(self.update(cx, |project, cx| match kind {
23041 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23042 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23043 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23044 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23045 }))
23046 }
23047
23048 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23049 self.update(cx, |project, cx| {
23050 if project
23051 .active_debug_session(cx)
23052 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23053 {
23054 return true;
23055 }
23056
23057 buffer.update(cx, |buffer, cx| {
23058 project.any_language_server_supports_inlay_hints(buffer, cx)
23059 })
23060 })
23061 }
23062
23063 fn inline_values(
23064 &self,
23065 buffer_handle: Entity<Buffer>,
23066 range: Range<text::Anchor>,
23067 cx: &mut App,
23068 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23069 self.update(cx, |project, cx| {
23070 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23071
23072 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23073 })
23074 }
23075
23076 fn inlay_hints(
23077 &self,
23078 buffer_handle: Entity<Buffer>,
23079 range: Range<text::Anchor>,
23080 cx: &mut App,
23081 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23082 Some(self.update(cx, |project, cx| {
23083 project.inlay_hints(buffer_handle, range, cx)
23084 }))
23085 }
23086
23087 fn resolve_inlay_hint(
23088 &self,
23089 hint: InlayHint,
23090 buffer_handle: Entity<Buffer>,
23091 server_id: LanguageServerId,
23092 cx: &mut App,
23093 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23094 Some(self.update(cx, |project, cx| {
23095 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23096 }))
23097 }
23098
23099 fn range_for_rename(
23100 &self,
23101 buffer: &Entity<Buffer>,
23102 position: text::Anchor,
23103 cx: &mut App,
23104 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23105 Some(self.update(cx, |project, cx| {
23106 let buffer = buffer.clone();
23107 let task = project.prepare_rename(buffer.clone(), position, cx);
23108 cx.spawn(async move |_, cx| {
23109 Ok(match task.await? {
23110 PrepareRenameResponse::Success(range) => Some(range),
23111 PrepareRenameResponse::InvalidPosition => None,
23112 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23113 // Fallback on using TreeSitter info to determine identifier range
23114 buffer.read_with(cx, |buffer, _| {
23115 let snapshot = buffer.snapshot();
23116 let (range, kind) = snapshot.surrounding_word(position, None);
23117 if kind != Some(CharKind::Word) {
23118 return None;
23119 }
23120 Some(
23121 snapshot.anchor_before(range.start)
23122 ..snapshot.anchor_after(range.end),
23123 )
23124 })?
23125 }
23126 })
23127 })
23128 }))
23129 }
23130
23131 fn perform_rename(
23132 &self,
23133 buffer: &Entity<Buffer>,
23134 position: text::Anchor,
23135 new_name: String,
23136 cx: &mut App,
23137 ) -> Option<Task<Result<ProjectTransaction>>> {
23138 Some(self.update(cx, |project, cx| {
23139 project.perform_rename(buffer.clone(), position, new_name, cx)
23140 }))
23141 }
23142}
23143
23144fn inlay_hint_settings(
23145 location: Anchor,
23146 snapshot: &MultiBufferSnapshot,
23147 cx: &mut Context<Editor>,
23148) -> InlayHintSettings {
23149 let file = snapshot.file_at(location);
23150 let language = snapshot.language_at(location).map(|l| l.name());
23151 language_settings(language, file, cx).inlay_hints
23152}
23153
23154fn consume_contiguous_rows(
23155 contiguous_row_selections: &mut Vec<Selection<Point>>,
23156 selection: &Selection<Point>,
23157 display_map: &DisplaySnapshot,
23158 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23159) -> (MultiBufferRow, MultiBufferRow) {
23160 contiguous_row_selections.push(selection.clone());
23161 let start_row = starting_row(selection, display_map);
23162 let mut end_row = ending_row(selection, display_map);
23163
23164 while let Some(next_selection) = selections.peek() {
23165 if next_selection.start.row <= end_row.0 {
23166 end_row = ending_row(next_selection, display_map);
23167 contiguous_row_selections.push(selections.next().unwrap().clone());
23168 } else {
23169 break;
23170 }
23171 }
23172 (start_row, end_row)
23173}
23174
23175fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23176 if selection.start.column > 0 {
23177 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23178 } else {
23179 MultiBufferRow(selection.start.row)
23180 }
23181}
23182
23183fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23184 if next_selection.end.column > 0 || next_selection.is_empty() {
23185 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23186 } else {
23187 MultiBufferRow(next_selection.end.row)
23188 }
23189}
23190
23191impl EditorSnapshot {
23192 pub fn remote_selections_in_range<'a>(
23193 &'a self,
23194 range: &'a Range<Anchor>,
23195 collaboration_hub: &dyn CollaborationHub,
23196 cx: &'a App,
23197 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23198 let participant_names = collaboration_hub.user_names(cx);
23199 let participant_indices = collaboration_hub.user_participant_indices(cx);
23200 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23201 let collaborators_by_replica_id = collaborators_by_peer_id
23202 .values()
23203 .map(|collaborator| (collaborator.replica_id, collaborator))
23204 .collect::<HashMap<_, _>>();
23205 self.buffer_snapshot
23206 .selections_in_range(range, false)
23207 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23208 if replica_id == AGENT_REPLICA_ID {
23209 Some(RemoteSelection {
23210 replica_id,
23211 selection,
23212 cursor_shape,
23213 line_mode,
23214 collaborator_id: CollaboratorId::Agent,
23215 user_name: Some("Agent".into()),
23216 color: cx.theme().players().agent(),
23217 })
23218 } else {
23219 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23220 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23221 let user_name = participant_names.get(&collaborator.user_id).cloned();
23222 Some(RemoteSelection {
23223 replica_id,
23224 selection,
23225 cursor_shape,
23226 line_mode,
23227 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23228 user_name,
23229 color: if let Some(index) = participant_index {
23230 cx.theme().players().color_for_participant(index.0)
23231 } else {
23232 cx.theme().players().absent()
23233 },
23234 })
23235 }
23236 })
23237 }
23238
23239 pub fn hunks_for_ranges(
23240 &self,
23241 ranges: impl IntoIterator<Item = Range<Point>>,
23242 ) -> Vec<MultiBufferDiffHunk> {
23243 let mut hunks = Vec::new();
23244 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23245 HashMap::default();
23246 for query_range in ranges {
23247 let query_rows =
23248 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23249 for hunk in self.buffer_snapshot.diff_hunks_in_range(
23250 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23251 ) {
23252 // Include deleted hunks that are adjacent to the query range, because
23253 // otherwise they would be missed.
23254 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23255 if hunk.status().is_deleted() {
23256 intersects_range |= hunk.row_range.start == query_rows.end;
23257 intersects_range |= hunk.row_range.end == query_rows.start;
23258 }
23259 if intersects_range {
23260 if !processed_buffer_rows
23261 .entry(hunk.buffer_id)
23262 .or_default()
23263 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23264 {
23265 continue;
23266 }
23267 hunks.push(hunk);
23268 }
23269 }
23270 }
23271
23272 hunks
23273 }
23274
23275 fn display_diff_hunks_for_rows<'a>(
23276 &'a self,
23277 display_rows: Range<DisplayRow>,
23278 folded_buffers: &'a HashSet<BufferId>,
23279 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23280 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23281 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23282
23283 self.buffer_snapshot
23284 .diff_hunks_in_range(buffer_start..buffer_end)
23285 .filter_map(|hunk| {
23286 if folded_buffers.contains(&hunk.buffer_id) {
23287 return None;
23288 }
23289
23290 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23291 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23292
23293 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23294 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23295
23296 let display_hunk = if hunk_display_start.column() != 0 {
23297 DisplayDiffHunk::Folded {
23298 display_row: hunk_display_start.row(),
23299 }
23300 } else {
23301 let mut end_row = hunk_display_end.row();
23302 if hunk_display_end.column() > 0 {
23303 end_row.0 += 1;
23304 }
23305 let is_created_file = hunk.is_created_file();
23306 DisplayDiffHunk::Unfolded {
23307 status: hunk.status(),
23308 diff_base_byte_range: hunk.diff_base_byte_range,
23309 display_row_range: hunk_display_start.row()..end_row,
23310 multi_buffer_range: Anchor::range_in_buffer(
23311 hunk.excerpt_id,
23312 hunk.buffer_id,
23313 hunk.buffer_range,
23314 ),
23315 is_created_file,
23316 }
23317 };
23318
23319 Some(display_hunk)
23320 })
23321 }
23322
23323 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23324 self.display_snapshot.buffer_snapshot.language_at(position)
23325 }
23326
23327 pub fn is_focused(&self) -> bool {
23328 self.is_focused
23329 }
23330
23331 pub fn placeholder_text(&self) -> Option<String> {
23332 self.placeholder_display_snapshot
23333 .as_ref()
23334 .map(|display_map| display_map.text())
23335 }
23336
23337 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23338 self.scroll_anchor.scroll_position(&self.display_snapshot)
23339 }
23340
23341 fn gutter_dimensions(
23342 &self,
23343 font_id: FontId,
23344 font_size: Pixels,
23345 max_line_number_width: Pixels,
23346 cx: &App,
23347 ) -> Option<GutterDimensions> {
23348 if !self.show_gutter {
23349 return None;
23350 }
23351
23352 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23353 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23354
23355 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23356 matches!(
23357 ProjectSettings::get_global(cx).git.git_gutter,
23358 GitGutterSetting::TrackedFiles
23359 )
23360 });
23361 let gutter_settings = EditorSettings::get_global(cx).gutter;
23362 let show_line_numbers = self
23363 .show_line_numbers
23364 .unwrap_or(gutter_settings.line_numbers);
23365 let line_gutter_width = if show_line_numbers {
23366 // Avoid flicker-like gutter resizes when the line number gains another digit by
23367 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23368 let min_width_for_number_on_gutter =
23369 ch_advance * gutter_settings.min_line_number_digits as f32;
23370 max_line_number_width.max(min_width_for_number_on_gutter)
23371 } else {
23372 0.0.into()
23373 };
23374
23375 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23376 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23377
23378 let git_blame_entries_width =
23379 self.git_blame_gutter_max_author_length
23380 .map(|max_author_length| {
23381 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23382 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23383
23384 /// The number of characters to dedicate to gaps and margins.
23385 const SPACING_WIDTH: usize = 4;
23386
23387 let max_char_count = max_author_length.min(renderer.max_author_length())
23388 + ::git::SHORT_SHA_LENGTH
23389 + MAX_RELATIVE_TIMESTAMP.len()
23390 + SPACING_WIDTH;
23391
23392 ch_advance * max_char_count
23393 });
23394
23395 let is_singleton = self.buffer_snapshot.is_singleton();
23396
23397 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23398 left_padding += if !is_singleton {
23399 ch_width * 4.0
23400 } else if show_runnables || show_breakpoints {
23401 ch_width * 3.0
23402 } else if show_git_gutter && show_line_numbers {
23403 ch_width * 2.0
23404 } else if show_git_gutter || show_line_numbers {
23405 ch_width
23406 } else {
23407 px(0.)
23408 };
23409
23410 let shows_folds = is_singleton && gutter_settings.folds;
23411
23412 let right_padding = if shows_folds && show_line_numbers {
23413 ch_width * 4.0
23414 } else if shows_folds || (!is_singleton && show_line_numbers) {
23415 ch_width * 3.0
23416 } else if show_line_numbers {
23417 ch_width
23418 } else {
23419 px(0.)
23420 };
23421
23422 Some(GutterDimensions {
23423 left_padding,
23424 right_padding,
23425 width: line_gutter_width + left_padding + right_padding,
23426 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23427 git_blame_entries_width,
23428 })
23429 }
23430
23431 pub fn render_crease_toggle(
23432 &self,
23433 buffer_row: MultiBufferRow,
23434 row_contains_cursor: bool,
23435 editor: Entity<Editor>,
23436 window: &mut Window,
23437 cx: &mut App,
23438 ) -> Option<AnyElement> {
23439 let folded = self.is_line_folded(buffer_row);
23440 let mut is_foldable = false;
23441
23442 if let Some(crease) = self
23443 .crease_snapshot
23444 .query_row(buffer_row, &self.buffer_snapshot)
23445 {
23446 is_foldable = true;
23447 match crease {
23448 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23449 if let Some(render_toggle) = render_toggle {
23450 let toggle_callback =
23451 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23452 if folded {
23453 editor.update(cx, |editor, cx| {
23454 editor.fold_at(buffer_row, window, cx)
23455 });
23456 } else {
23457 editor.update(cx, |editor, cx| {
23458 editor.unfold_at(buffer_row, window, cx)
23459 });
23460 }
23461 });
23462 return Some((render_toggle)(
23463 buffer_row,
23464 folded,
23465 toggle_callback,
23466 window,
23467 cx,
23468 ));
23469 }
23470 }
23471 }
23472 }
23473
23474 is_foldable |= self.starts_indent(buffer_row);
23475
23476 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23477 Some(
23478 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23479 .toggle_state(folded)
23480 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23481 if folded {
23482 this.unfold_at(buffer_row, window, cx);
23483 } else {
23484 this.fold_at(buffer_row, window, cx);
23485 }
23486 }))
23487 .into_any_element(),
23488 )
23489 } else {
23490 None
23491 }
23492 }
23493
23494 pub fn render_crease_trailer(
23495 &self,
23496 buffer_row: MultiBufferRow,
23497 window: &mut Window,
23498 cx: &mut App,
23499 ) -> Option<AnyElement> {
23500 let folded = self.is_line_folded(buffer_row);
23501 if let Crease::Inline { render_trailer, .. } = self
23502 .crease_snapshot
23503 .query_row(buffer_row, &self.buffer_snapshot)?
23504 {
23505 let render_trailer = render_trailer.as_ref()?;
23506 Some(render_trailer(buffer_row, folded, window, cx))
23507 } else {
23508 None
23509 }
23510 }
23511}
23512
23513impl Deref for EditorSnapshot {
23514 type Target = DisplaySnapshot;
23515
23516 fn deref(&self) -> &Self::Target {
23517 &self.display_snapshot
23518 }
23519}
23520
23521#[derive(Clone, Debug, PartialEq, Eq)]
23522pub enum EditorEvent {
23523 InputIgnored {
23524 text: Arc<str>,
23525 },
23526 InputHandled {
23527 utf16_range_to_replace: Option<Range<isize>>,
23528 text: Arc<str>,
23529 },
23530 ExcerptsAdded {
23531 buffer: Entity<Buffer>,
23532 predecessor: ExcerptId,
23533 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23534 },
23535 ExcerptsRemoved {
23536 ids: Vec<ExcerptId>,
23537 removed_buffer_ids: Vec<BufferId>,
23538 },
23539 BufferFoldToggled {
23540 ids: Vec<ExcerptId>,
23541 folded: bool,
23542 },
23543 ExcerptsEdited {
23544 ids: Vec<ExcerptId>,
23545 },
23546 ExcerptsExpanded {
23547 ids: Vec<ExcerptId>,
23548 },
23549 BufferEdited,
23550 Edited {
23551 transaction_id: clock::Lamport,
23552 },
23553 Reparsed(BufferId),
23554 Focused,
23555 FocusedIn,
23556 Blurred,
23557 DirtyChanged,
23558 Saved,
23559 TitleChanged,
23560 SelectionsChanged {
23561 local: bool,
23562 },
23563 ScrollPositionChanged {
23564 local: bool,
23565 autoscroll: bool,
23566 },
23567 TransactionUndone {
23568 transaction_id: clock::Lamport,
23569 },
23570 TransactionBegun {
23571 transaction_id: clock::Lamport,
23572 },
23573 CursorShapeChanged,
23574 BreadcrumbsChanged,
23575 PushedToNavHistory {
23576 anchor: Anchor,
23577 is_deactivate: bool,
23578 },
23579}
23580
23581impl EventEmitter<EditorEvent> for Editor {}
23582
23583impl Focusable for Editor {
23584 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23585 self.focus_handle.clone()
23586 }
23587}
23588
23589impl Render for Editor {
23590 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23591 let settings = ThemeSettings::get_global(cx);
23592
23593 let mut text_style = match self.mode {
23594 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23595 color: cx.theme().colors().editor_foreground,
23596 font_family: settings.ui_font.family.clone(),
23597 font_features: settings.ui_font.features.clone(),
23598 font_fallbacks: settings.ui_font.fallbacks.clone(),
23599 font_size: rems(0.875).into(),
23600 font_weight: settings.ui_font.weight,
23601 line_height: relative(settings.buffer_line_height.value()),
23602 ..Default::default()
23603 },
23604 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23605 color: cx.theme().colors().editor_foreground,
23606 font_family: settings.buffer_font.family.clone(),
23607 font_features: settings.buffer_font.features.clone(),
23608 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23609 font_size: settings.buffer_font_size(cx).into(),
23610 font_weight: settings.buffer_font.weight,
23611 line_height: relative(settings.buffer_line_height.value()),
23612 ..Default::default()
23613 },
23614 };
23615 if let Some(text_style_refinement) = &self.text_style_refinement {
23616 text_style.refine(text_style_refinement)
23617 }
23618
23619 let background = match self.mode {
23620 EditorMode::SingleLine => cx.theme().system().transparent,
23621 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23622 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23623 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23624 };
23625
23626 EditorElement::new(
23627 &cx.entity(),
23628 EditorStyle {
23629 background,
23630 border: cx.theme().colors().border,
23631 local_player: cx.theme().players().local(),
23632 text: text_style,
23633 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23634 syntax: cx.theme().syntax().clone(),
23635 status: cx.theme().status().clone(),
23636 inlay_hints_style: make_inlay_hints_style(cx),
23637 edit_prediction_styles: make_suggestion_styles(cx),
23638 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23639 show_underlines: self.diagnostics_enabled(),
23640 },
23641 )
23642 }
23643}
23644
23645impl EntityInputHandler for Editor {
23646 fn text_for_range(
23647 &mut self,
23648 range_utf16: Range<usize>,
23649 adjusted_range: &mut Option<Range<usize>>,
23650 _: &mut Window,
23651 cx: &mut Context<Self>,
23652 ) -> Option<String> {
23653 let snapshot = self.buffer.read(cx).read(cx);
23654 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23655 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23656 if (start.0..end.0) != range_utf16 {
23657 adjusted_range.replace(start.0..end.0);
23658 }
23659 Some(snapshot.text_for_range(start..end).collect())
23660 }
23661
23662 fn selected_text_range(
23663 &mut self,
23664 ignore_disabled_input: bool,
23665 _: &mut Window,
23666 cx: &mut Context<Self>,
23667 ) -> Option<UTF16Selection> {
23668 // Prevent the IME menu from appearing when holding down an alphabetic key
23669 // while input is disabled.
23670 if !ignore_disabled_input && !self.input_enabled {
23671 return None;
23672 }
23673
23674 let selection = self.selections.newest::<OffsetUtf16>(cx);
23675 let range = selection.range();
23676
23677 Some(UTF16Selection {
23678 range: range.start.0..range.end.0,
23679 reversed: selection.reversed,
23680 })
23681 }
23682
23683 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23684 let snapshot = self.buffer.read(cx).read(cx);
23685 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23686 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23687 }
23688
23689 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23690 self.clear_highlights::<InputComposition>(cx);
23691 self.ime_transaction.take();
23692 }
23693
23694 fn replace_text_in_range(
23695 &mut self,
23696 range_utf16: Option<Range<usize>>,
23697 text: &str,
23698 window: &mut Window,
23699 cx: &mut Context<Self>,
23700 ) {
23701 if !self.input_enabled {
23702 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23703 return;
23704 }
23705
23706 self.transact(window, cx, |this, window, cx| {
23707 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23708 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23709 Some(this.selection_replacement_ranges(range_utf16, cx))
23710 } else {
23711 this.marked_text_ranges(cx)
23712 };
23713
23714 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23715 let newest_selection_id = this.selections.newest_anchor().id;
23716 this.selections
23717 .all::<OffsetUtf16>(cx)
23718 .iter()
23719 .zip(ranges_to_replace.iter())
23720 .find_map(|(selection, range)| {
23721 if selection.id == newest_selection_id {
23722 Some(
23723 (range.start.0 as isize - selection.head().0 as isize)
23724 ..(range.end.0 as isize - selection.head().0 as isize),
23725 )
23726 } else {
23727 None
23728 }
23729 })
23730 });
23731
23732 cx.emit(EditorEvent::InputHandled {
23733 utf16_range_to_replace: range_to_replace,
23734 text: text.into(),
23735 });
23736
23737 if let Some(new_selected_ranges) = new_selected_ranges {
23738 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23739 selections.select_ranges(new_selected_ranges)
23740 });
23741 this.backspace(&Default::default(), window, cx);
23742 }
23743
23744 this.handle_input(text, window, cx);
23745 });
23746
23747 if let Some(transaction) = self.ime_transaction {
23748 self.buffer.update(cx, |buffer, cx| {
23749 buffer.group_until_transaction(transaction, cx);
23750 });
23751 }
23752
23753 self.unmark_text(window, cx);
23754 }
23755
23756 fn replace_and_mark_text_in_range(
23757 &mut self,
23758 range_utf16: Option<Range<usize>>,
23759 text: &str,
23760 new_selected_range_utf16: Option<Range<usize>>,
23761 window: &mut Window,
23762 cx: &mut Context<Self>,
23763 ) {
23764 if !self.input_enabled {
23765 return;
23766 }
23767
23768 let transaction = self.transact(window, cx, |this, window, cx| {
23769 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23770 let snapshot = this.buffer.read(cx).read(cx);
23771 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23772 for marked_range in &mut marked_ranges {
23773 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23774 marked_range.start.0 += relative_range_utf16.start;
23775 marked_range.start =
23776 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23777 marked_range.end =
23778 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23779 }
23780 }
23781 Some(marked_ranges)
23782 } else if let Some(range_utf16) = range_utf16 {
23783 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23784 Some(this.selection_replacement_ranges(range_utf16, cx))
23785 } else {
23786 None
23787 };
23788
23789 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23790 let newest_selection_id = this.selections.newest_anchor().id;
23791 this.selections
23792 .all::<OffsetUtf16>(cx)
23793 .iter()
23794 .zip(ranges_to_replace.iter())
23795 .find_map(|(selection, range)| {
23796 if selection.id == newest_selection_id {
23797 Some(
23798 (range.start.0 as isize - selection.head().0 as isize)
23799 ..(range.end.0 as isize - selection.head().0 as isize),
23800 )
23801 } else {
23802 None
23803 }
23804 })
23805 });
23806
23807 cx.emit(EditorEvent::InputHandled {
23808 utf16_range_to_replace: range_to_replace,
23809 text: text.into(),
23810 });
23811
23812 if let Some(ranges) = ranges_to_replace {
23813 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23814 s.select_ranges(ranges)
23815 });
23816 }
23817
23818 let marked_ranges = {
23819 let snapshot = this.buffer.read(cx).read(cx);
23820 this.selections
23821 .disjoint_anchors_arc()
23822 .iter()
23823 .map(|selection| {
23824 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23825 })
23826 .collect::<Vec<_>>()
23827 };
23828
23829 if text.is_empty() {
23830 this.unmark_text(window, cx);
23831 } else {
23832 this.highlight_text::<InputComposition>(
23833 marked_ranges.clone(),
23834 HighlightStyle {
23835 underline: Some(UnderlineStyle {
23836 thickness: px(1.),
23837 color: None,
23838 wavy: false,
23839 }),
23840 ..Default::default()
23841 },
23842 cx,
23843 );
23844 }
23845
23846 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23847 let use_autoclose = this.use_autoclose;
23848 let use_auto_surround = this.use_auto_surround;
23849 this.set_use_autoclose(false);
23850 this.set_use_auto_surround(false);
23851 this.handle_input(text, window, cx);
23852 this.set_use_autoclose(use_autoclose);
23853 this.set_use_auto_surround(use_auto_surround);
23854
23855 if let Some(new_selected_range) = new_selected_range_utf16 {
23856 let snapshot = this.buffer.read(cx).read(cx);
23857 let new_selected_ranges = marked_ranges
23858 .into_iter()
23859 .map(|marked_range| {
23860 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23861 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23862 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23863 snapshot.clip_offset_utf16(new_start, Bias::Left)
23864 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23865 })
23866 .collect::<Vec<_>>();
23867
23868 drop(snapshot);
23869 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23870 selections.select_ranges(new_selected_ranges)
23871 });
23872 }
23873 });
23874
23875 self.ime_transaction = self.ime_transaction.or(transaction);
23876 if let Some(transaction) = self.ime_transaction {
23877 self.buffer.update(cx, |buffer, cx| {
23878 buffer.group_until_transaction(transaction, cx);
23879 });
23880 }
23881
23882 if self.text_highlights::<InputComposition>(cx).is_none() {
23883 self.ime_transaction.take();
23884 }
23885 }
23886
23887 fn bounds_for_range(
23888 &mut self,
23889 range_utf16: Range<usize>,
23890 element_bounds: gpui::Bounds<Pixels>,
23891 window: &mut Window,
23892 cx: &mut Context<Self>,
23893 ) -> Option<gpui::Bounds<Pixels>> {
23894 let text_layout_details = self.text_layout_details(window);
23895 let CharacterDimensions {
23896 em_width,
23897 em_advance,
23898 line_height,
23899 } = self.character_dimensions(window);
23900
23901 let snapshot = self.snapshot(window, cx);
23902 let scroll_position = snapshot.scroll_position();
23903 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
23904
23905 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23906 let x = Pixels::from(
23907 ScrollOffset::from(
23908 snapshot.x_for_display_point(start, &text_layout_details)
23909 + self.gutter_dimensions.full_width(),
23910 ) - scroll_left,
23911 );
23912 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
23913
23914 Some(Bounds {
23915 origin: element_bounds.origin + point(x, y),
23916 size: size(em_width, line_height),
23917 })
23918 }
23919
23920 fn character_index_for_point(
23921 &mut self,
23922 point: gpui::Point<Pixels>,
23923 _window: &mut Window,
23924 _cx: &mut Context<Self>,
23925 ) -> Option<usize> {
23926 let position_map = self.last_position_map.as_ref()?;
23927 if !position_map.text_hitbox.contains(&point) {
23928 return None;
23929 }
23930 let display_point = position_map.point_for_position(point).previous_valid;
23931 let anchor = position_map
23932 .snapshot
23933 .display_point_to_anchor(display_point, Bias::Left);
23934 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23935 Some(utf16_offset.0)
23936 }
23937}
23938
23939trait SelectionExt {
23940 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23941 fn spanned_rows(
23942 &self,
23943 include_end_if_at_line_start: bool,
23944 map: &DisplaySnapshot,
23945 ) -> Range<MultiBufferRow>;
23946}
23947
23948impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23949 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23950 let start = self
23951 .start
23952 .to_point(&map.buffer_snapshot)
23953 .to_display_point(map);
23954 let end = self
23955 .end
23956 .to_point(&map.buffer_snapshot)
23957 .to_display_point(map);
23958 if self.reversed {
23959 end..start
23960 } else {
23961 start..end
23962 }
23963 }
23964
23965 fn spanned_rows(
23966 &self,
23967 include_end_if_at_line_start: bool,
23968 map: &DisplaySnapshot,
23969 ) -> Range<MultiBufferRow> {
23970 let start = self.start.to_point(&map.buffer_snapshot);
23971 let mut end = self.end.to_point(&map.buffer_snapshot);
23972 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23973 end.row -= 1;
23974 }
23975
23976 let buffer_start = map.prev_line_boundary(start).0;
23977 let buffer_end = map.next_line_boundary(end).0;
23978 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23979 }
23980}
23981
23982impl<T: InvalidationRegion> InvalidationStack<T> {
23983 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23984 where
23985 S: Clone + ToOffset,
23986 {
23987 while let Some(region) = self.last() {
23988 let all_selections_inside_invalidation_ranges =
23989 if selections.len() == region.ranges().len() {
23990 selections
23991 .iter()
23992 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23993 .all(|(selection, invalidation_range)| {
23994 let head = selection.head().to_offset(buffer);
23995 invalidation_range.start <= head && invalidation_range.end >= head
23996 })
23997 } else {
23998 false
23999 };
24000
24001 if all_selections_inside_invalidation_ranges {
24002 break;
24003 } else {
24004 self.pop();
24005 }
24006 }
24007 }
24008}
24009
24010impl<T> Default for InvalidationStack<T> {
24011 fn default() -> Self {
24012 Self(Default::default())
24013 }
24014}
24015
24016impl<T> Deref for InvalidationStack<T> {
24017 type Target = Vec<T>;
24018
24019 fn deref(&self) -> &Self::Target {
24020 &self.0
24021 }
24022}
24023
24024impl<T> DerefMut for InvalidationStack<T> {
24025 fn deref_mut(&mut self) -> &mut Self::Target {
24026 &mut self.0
24027 }
24028}
24029
24030impl InvalidationRegion for SnippetState {
24031 fn ranges(&self) -> &[Range<Anchor>] {
24032 &self.ranges[self.active_index]
24033 }
24034}
24035
24036fn edit_prediction_edit_text(
24037 current_snapshot: &BufferSnapshot,
24038 edits: &[(Range<Anchor>, String)],
24039 edit_preview: &EditPreview,
24040 include_deletions: bool,
24041 cx: &App,
24042) -> HighlightedText {
24043 let edits = edits
24044 .iter()
24045 .map(|(anchor, text)| {
24046 (
24047 anchor.start.text_anchor..anchor.end.text_anchor,
24048 text.clone(),
24049 )
24050 })
24051 .collect::<Vec<_>>();
24052
24053 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24054}
24055
24056fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24057 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24058 // Just show the raw edit text with basic styling
24059 let mut text = String::new();
24060 let mut highlights = Vec::new();
24061
24062 let insertion_highlight_style = HighlightStyle {
24063 color: Some(cx.theme().colors().text),
24064 ..Default::default()
24065 };
24066
24067 for (_, edit_text) in edits {
24068 let start_offset = text.len();
24069 text.push_str(edit_text);
24070 let end_offset = text.len();
24071
24072 if start_offset < end_offset {
24073 highlights.push((start_offset..end_offset, insertion_highlight_style));
24074 }
24075 }
24076
24077 HighlightedText {
24078 text: text.into(),
24079 highlights,
24080 }
24081}
24082
24083pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24084 match severity {
24085 lsp::DiagnosticSeverity::ERROR => colors.error,
24086 lsp::DiagnosticSeverity::WARNING => colors.warning,
24087 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24088 lsp::DiagnosticSeverity::HINT => colors.info,
24089 _ => colors.ignored,
24090 }
24091}
24092
24093pub fn styled_runs_for_code_label<'a>(
24094 label: &'a CodeLabel,
24095 syntax_theme: &'a theme::SyntaxTheme,
24096) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24097 let fade_out = HighlightStyle {
24098 fade_out: Some(0.35),
24099 ..Default::default()
24100 };
24101
24102 let mut prev_end = label.filter_range.end;
24103 label
24104 .runs
24105 .iter()
24106 .enumerate()
24107 .flat_map(move |(ix, (range, highlight_id))| {
24108 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24109 style
24110 } else {
24111 return Default::default();
24112 };
24113 let muted_style = style.highlight(fade_out);
24114
24115 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24116 if range.start >= label.filter_range.end {
24117 if range.start > prev_end {
24118 runs.push((prev_end..range.start, fade_out));
24119 }
24120 runs.push((range.clone(), muted_style));
24121 } else if range.end <= label.filter_range.end {
24122 runs.push((range.clone(), style));
24123 } else {
24124 runs.push((range.start..label.filter_range.end, style));
24125 runs.push((label.filter_range.end..range.end, muted_style));
24126 }
24127 prev_end = cmp::max(prev_end, range.end);
24128
24129 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24130 runs.push((prev_end..label.text.len(), fade_out));
24131 }
24132
24133 runs
24134 })
24135}
24136
24137pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24138 let mut prev_index = 0;
24139 let mut prev_codepoint: Option<char> = None;
24140 text.char_indices()
24141 .chain([(text.len(), '\0')])
24142 .filter_map(move |(index, codepoint)| {
24143 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24144 let is_boundary = index == text.len()
24145 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24146 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24147 if is_boundary {
24148 let chunk = &text[prev_index..index];
24149 prev_index = index;
24150 Some(chunk)
24151 } else {
24152 None
24153 }
24154 })
24155}
24156
24157pub trait RangeToAnchorExt: Sized {
24158 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24159
24160 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24161 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
24162 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24163 }
24164}
24165
24166impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24167 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24168 let start_offset = self.start.to_offset(snapshot);
24169 let end_offset = self.end.to_offset(snapshot);
24170 if start_offset == end_offset {
24171 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24172 } else {
24173 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24174 }
24175 }
24176}
24177
24178pub trait RowExt {
24179 fn as_f64(&self) -> f64;
24180
24181 fn next_row(&self) -> Self;
24182
24183 fn previous_row(&self) -> Self;
24184
24185 fn minus(&self, other: Self) -> u32;
24186}
24187
24188impl RowExt for DisplayRow {
24189 fn as_f64(&self) -> f64 {
24190 self.0 as _
24191 }
24192
24193 fn next_row(&self) -> Self {
24194 Self(self.0 + 1)
24195 }
24196
24197 fn previous_row(&self) -> Self {
24198 Self(self.0.saturating_sub(1))
24199 }
24200
24201 fn minus(&self, other: Self) -> u32 {
24202 self.0 - other.0
24203 }
24204}
24205
24206impl RowExt for MultiBufferRow {
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
24224trait RowRangeExt {
24225 type Row;
24226
24227 fn len(&self) -> usize;
24228
24229 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24230}
24231
24232impl RowRangeExt for Range<MultiBufferRow> {
24233 type Row = MultiBufferRow;
24234
24235 fn len(&self) -> usize {
24236 (self.end.0 - self.start.0) as usize
24237 }
24238
24239 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24240 (self.start.0..self.end.0).map(MultiBufferRow)
24241 }
24242}
24243
24244impl RowRangeExt for Range<DisplayRow> {
24245 type Row = DisplayRow;
24246
24247 fn len(&self) -> usize {
24248 (self.end.0 - self.start.0) as usize
24249 }
24250
24251 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24252 (self.start.0..self.end.0).map(DisplayRow)
24253 }
24254}
24255
24256/// If select range has more than one line, we
24257/// just point the cursor to range.start.
24258fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24259 if range.start.row == range.end.row {
24260 range
24261 } else {
24262 range.start..range.start
24263 }
24264}
24265pub struct KillRing(ClipboardItem);
24266impl Global for KillRing {}
24267
24268const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24269
24270enum BreakpointPromptEditAction {
24271 Log,
24272 Condition,
24273 HitCondition,
24274}
24275
24276struct BreakpointPromptEditor {
24277 pub(crate) prompt: Entity<Editor>,
24278 editor: WeakEntity<Editor>,
24279 breakpoint_anchor: Anchor,
24280 breakpoint: Breakpoint,
24281 edit_action: BreakpointPromptEditAction,
24282 block_ids: HashSet<CustomBlockId>,
24283 editor_margins: Arc<Mutex<EditorMargins>>,
24284 _subscriptions: Vec<Subscription>,
24285}
24286
24287impl BreakpointPromptEditor {
24288 const MAX_LINES: u8 = 4;
24289
24290 fn new(
24291 editor: WeakEntity<Editor>,
24292 breakpoint_anchor: Anchor,
24293 breakpoint: Breakpoint,
24294 edit_action: BreakpointPromptEditAction,
24295 window: &mut Window,
24296 cx: &mut Context<Self>,
24297 ) -> Self {
24298 let base_text = match edit_action {
24299 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24300 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24301 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24302 }
24303 .map(|msg| msg.to_string())
24304 .unwrap_or_default();
24305
24306 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24307 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24308
24309 let prompt = cx.new(|cx| {
24310 let mut prompt = Editor::new(
24311 EditorMode::AutoHeight {
24312 min_lines: 1,
24313 max_lines: Some(Self::MAX_LINES as usize),
24314 },
24315 buffer,
24316 None,
24317 window,
24318 cx,
24319 );
24320 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24321 prompt.set_show_cursor_when_unfocused(false, cx);
24322 prompt.set_placeholder_text(
24323 match edit_action {
24324 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24325 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24326 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24327 },
24328 window,
24329 cx,
24330 );
24331
24332 prompt
24333 });
24334
24335 Self {
24336 prompt,
24337 editor,
24338 breakpoint_anchor,
24339 breakpoint,
24340 edit_action,
24341 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24342 block_ids: Default::default(),
24343 _subscriptions: vec![],
24344 }
24345 }
24346
24347 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24348 self.block_ids.extend(block_ids)
24349 }
24350
24351 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24352 if let Some(editor) = self.editor.upgrade() {
24353 let message = self
24354 .prompt
24355 .read(cx)
24356 .buffer
24357 .read(cx)
24358 .as_singleton()
24359 .expect("A multi buffer in breakpoint prompt isn't possible")
24360 .read(cx)
24361 .as_rope()
24362 .to_string();
24363
24364 editor.update(cx, |editor, cx| {
24365 editor.edit_breakpoint_at_anchor(
24366 self.breakpoint_anchor,
24367 self.breakpoint.clone(),
24368 match self.edit_action {
24369 BreakpointPromptEditAction::Log => {
24370 BreakpointEditAction::EditLogMessage(message.into())
24371 }
24372 BreakpointPromptEditAction::Condition => {
24373 BreakpointEditAction::EditCondition(message.into())
24374 }
24375 BreakpointPromptEditAction::HitCondition => {
24376 BreakpointEditAction::EditHitCondition(message.into())
24377 }
24378 },
24379 cx,
24380 );
24381
24382 editor.remove_blocks(self.block_ids.clone(), None, cx);
24383 cx.focus_self(window);
24384 });
24385 }
24386 }
24387
24388 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24389 self.editor
24390 .update(cx, |editor, cx| {
24391 editor.remove_blocks(self.block_ids.clone(), None, cx);
24392 window.focus(&editor.focus_handle);
24393 })
24394 .log_err();
24395 }
24396
24397 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24398 let settings = ThemeSettings::get_global(cx);
24399 let text_style = TextStyle {
24400 color: if self.prompt.read(cx).read_only(cx) {
24401 cx.theme().colors().text_disabled
24402 } else {
24403 cx.theme().colors().text
24404 },
24405 font_family: settings.buffer_font.family.clone(),
24406 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24407 font_size: settings.buffer_font_size(cx).into(),
24408 font_weight: settings.buffer_font.weight,
24409 line_height: relative(settings.buffer_line_height.value()),
24410 ..Default::default()
24411 };
24412 EditorElement::new(
24413 &self.prompt,
24414 EditorStyle {
24415 background: cx.theme().colors().editor_background,
24416 local_player: cx.theme().players().local(),
24417 text: text_style,
24418 ..Default::default()
24419 },
24420 )
24421 }
24422}
24423
24424impl Render for BreakpointPromptEditor {
24425 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24426 let editor_margins = *self.editor_margins.lock();
24427 let gutter_dimensions = editor_margins.gutter;
24428 h_flex()
24429 .key_context("Editor")
24430 .bg(cx.theme().colors().editor_background)
24431 .border_y_1()
24432 .border_color(cx.theme().status().info_border)
24433 .size_full()
24434 .py(window.line_height() / 2.5)
24435 .on_action(cx.listener(Self::confirm))
24436 .on_action(cx.listener(Self::cancel))
24437 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24438 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24439 }
24440}
24441
24442impl Focusable for BreakpointPromptEditor {
24443 fn focus_handle(&self, cx: &App) -> FocusHandle {
24444 self.prompt.focus_handle(cx)
24445 }
24446}
24447
24448fn all_edits_insertions_or_deletions(
24449 edits: &Vec<(Range<Anchor>, String)>,
24450 snapshot: &MultiBufferSnapshot,
24451) -> bool {
24452 let mut all_insertions = true;
24453 let mut all_deletions = true;
24454
24455 for (range, new_text) in edits.iter() {
24456 let range_is_empty = range.to_offset(snapshot).is_empty();
24457 let text_is_empty = new_text.is_empty();
24458
24459 if range_is_empty != text_is_empty {
24460 if range_is_empty {
24461 all_deletions = false;
24462 } else {
24463 all_insertions = false;
24464 }
24465 } else {
24466 return false;
24467 }
24468
24469 if !all_insertions && !all_deletions {
24470 return false;
24471 }
24472 }
24473 all_insertions || all_deletions
24474}
24475
24476struct MissingEditPredictionKeybindingTooltip;
24477
24478impl Render for MissingEditPredictionKeybindingTooltip {
24479 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24480 ui::tooltip_container(cx, |container, cx| {
24481 container
24482 .flex_shrink_0()
24483 .max_w_80()
24484 .min_h(rems_from_px(124.))
24485 .justify_between()
24486 .child(
24487 v_flex()
24488 .flex_1()
24489 .text_ui_sm(cx)
24490 .child(Label::new("Conflict with Accept Keybinding"))
24491 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24492 )
24493 .child(
24494 h_flex()
24495 .pb_1()
24496 .gap_1()
24497 .items_end()
24498 .w_full()
24499 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24500 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24501 }))
24502 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24503 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24504 })),
24505 )
24506 })
24507 }
24508}
24509
24510#[derive(Debug, Clone, Copy, PartialEq)]
24511pub struct LineHighlight {
24512 pub background: Background,
24513 pub border: Option<gpui::Hsla>,
24514 pub include_gutter: bool,
24515 pub type_id: Option<TypeId>,
24516}
24517
24518struct LineManipulationResult {
24519 pub new_text: String,
24520 pub line_count_before: usize,
24521 pub line_count_after: usize,
24522}
24523
24524fn render_diff_hunk_controls(
24525 row: u32,
24526 status: &DiffHunkStatus,
24527 hunk_range: Range<Anchor>,
24528 is_created_file: bool,
24529 line_height: Pixels,
24530 editor: &Entity<Editor>,
24531 _window: &mut Window,
24532 cx: &mut App,
24533) -> AnyElement {
24534 h_flex()
24535 .h(line_height)
24536 .mr_1()
24537 .gap_1()
24538 .px_0p5()
24539 .pb_1()
24540 .border_x_1()
24541 .border_b_1()
24542 .border_color(cx.theme().colors().border_variant)
24543 .rounded_b_lg()
24544 .bg(cx.theme().colors().editor_background)
24545 .gap_1()
24546 .block_mouse_except_scroll()
24547 .shadow_md()
24548 .child(if status.has_secondary_hunk() {
24549 Button::new(("stage", row as u64), "Stage")
24550 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24551 .tooltip({
24552 let focus_handle = editor.focus_handle(cx);
24553 move |window, cx| {
24554 Tooltip::for_action_in(
24555 "Stage Hunk",
24556 &::git::ToggleStaged,
24557 &focus_handle,
24558 window,
24559 cx,
24560 )
24561 }
24562 })
24563 .on_click({
24564 let editor = editor.clone();
24565 move |_event, _window, cx| {
24566 editor.update(cx, |editor, cx| {
24567 editor.stage_or_unstage_diff_hunks(
24568 true,
24569 vec![hunk_range.start..hunk_range.start],
24570 cx,
24571 );
24572 });
24573 }
24574 })
24575 } else {
24576 Button::new(("unstage", row as u64), "Unstage")
24577 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24578 .tooltip({
24579 let focus_handle = editor.focus_handle(cx);
24580 move |window, cx| {
24581 Tooltip::for_action_in(
24582 "Unstage Hunk",
24583 &::git::ToggleStaged,
24584 &focus_handle,
24585 window,
24586 cx,
24587 )
24588 }
24589 })
24590 .on_click({
24591 let editor = editor.clone();
24592 move |_event, _window, cx| {
24593 editor.update(cx, |editor, cx| {
24594 editor.stage_or_unstage_diff_hunks(
24595 false,
24596 vec![hunk_range.start..hunk_range.start],
24597 cx,
24598 );
24599 });
24600 }
24601 })
24602 })
24603 .child(
24604 Button::new(("restore", row as u64), "Restore")
24605 .tooltip({
24606 let focus_handle = editor.focus_handle(cx);
24607 move |window, cx| {
24608 Tooltip::for_action_in(
24609 "Restore Hunk",
24610 &::git::Restore,
24611 &focus_handle,
24612 window,
24613 cx,
24614 )
24615 }
24616 })
24617 .on_click({
24618 let editor = editor.clone();
24619 move |_event, window, cx| {
24620 editor.update(cx, |editor, cx| {
24621 let snapshot = editor.snapshot(window, cx);
24622 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24623 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24624 });
24625 }
24626 })
24627 .disabled(is_created_file),
24628 )
24629 .when(
24630 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24631 |el| {
24632 el.child(
24633 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24634 .shape(IconButtonShape::Square)
24635 .icon_size(IconSize::Small)
24636 // .disabled(!has_multiple_hunks)
24637 .tooltip({
24638 let focus_handle = editor.focus_handle(cx);
24639 move |window, cx| {
24640 Tooltip::for_action_in(
24641 "Next Hunk",
24642 &GoToHunk,
24643 &focus_handle,
24644 window,
24645 cx,
24646 )
24647 }
24648 })
24649 .on_click({
24650 let editor = editor.clone();
24651 move |_event, window, cx| {
24652 editor.update(cx, |editor, cx| {
24653 let snapshot = editor.snapshot(window, cx);
24654 let position =
24655 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24656 editor.go_to_hunk_before_or_after_position(
24657 &snapshot,
24658 position,
24659 Direction::Next,
24660 window,
24661 cx,
24662 );
24663 editor.expand_selected_diff_hunks(cx);
24664 });
24665 }
24666 }),
24667 )
24668 .child(
24669 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24670 .shape(IconButtonShape::Square)
24671 .icon_size(IconSize::Small)
24672 // .disabled(!has_multiple_hunks)
24673 .tooltip({
24674 let focus_handle = editor.focus_handle(cx);
24675 move |window, cx| {
24676 Tooltip::for_action_in(
24677 "Previous Hunk",
24678 &GoToPreviousHunk,
24679 &focus_handle,
24680 window,
24681 cx,
24682 )
24683 }
24684 })
24685 .on_click({
24686 let editor = editor.clone();
24687 move |_event, window, cx| {
24688 editor.update(cx, |editor, cx| {
24689 let snapshot = editor.snapshot(window, cx);
24690 let point =
24691 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24692 editor.go_to_hunk_before_or_after_position(
24693 &snapshot,
24694 point,
24695 Direction::Prev,
24696 window,
24697 cx,
24698 );
24699 editor.expand_selected_diff_hunks(cx);
24700 });
24701 }
24702 }),
24703 )
24704 },
24705 )
24706 .into_any_element()
24707}
24708
24709pub fn multibuffer_context_lines(cx: &App) -> u32 {
24710 EditorSettings::try_get(cx)
24711 .map(|settings| settings.excerpt_context_lines)
24712 .unwrap_or(2)
24713 .min(32)
24714}