1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlay_hint_cache;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use items::MAX_TAB_TITLE_LEN;
65pub use lsp::CompletionContext;
66pub use lsp_ext::lsp_tasks;
67pub use multi_buffer::{
68 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
69 RowInfo, ToOffset, ToPoint,
70};
71pub use proposed_changes_editor::{
72 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
73};
74pub use text::Bias;
75
76use ::git::{
77 Restore,
78 blame::{BlameEntry, ParsedCommitMessage},
79};
80use aho_corasick::AhoCorasick;
81use anyhow::{Context as _, Result, anyhow};
82use blink_manager::BlinkManager;
83use buffer_diff::DiffHunkStatus;
84use client::{Collaborator, ParticipantIndex, parse_zed_link};
85use clock::ReplicaId;
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use collections::{BTreeMap, HashMap, HashSet, VecDeque};
91use convert_case::{Case, Casing};
92use dap::TelemetrySpawnLocation;
93use display_map::*;
94use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
95use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
96use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
97use futures::{
98 FutureExt, StreamExt as _,
99 future::{self, Shared, join},
100 stream::FuturesUnordered,
101};
102use fuzzy::{StringMatch, StringMatchCandidate};
103use git::blame::{GitBlame, GlobalBlameRenderer};
104use gpui::{
105 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
106 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
107 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
108 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
109 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
110 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
111 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
112 div, point, prelude::*, pulsating_between, px, relative, size,
113};
114use highlight_matching_bracket::refresh_matching_bracket_highlights;
115use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
116use hover_popover::{HoverState, hide_hover};
117use indent_guides::ActiveIndentGuidesState;
118use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
119use itertools::{Either, Itertools};
120use language::{
121 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
122 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
123 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
124 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
125 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143 ToOffsetUtf16,
144};
145use parking_lot::Mutex;
146use persistence::DB;
147use project::{
148 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
149 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
150 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
151 ProjectTransaction, TaskSourceKind,
152 debugger::{
153 breakpoint_store::{
154 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
155 BreakpointStore, BreakpointStoreEvent,
156 },
157 session::{Session, SessionEvent},
158 },
159 git_store::{GitStoreEvent, RepositoryEvent},
160 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
161 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
162};
163use rand::seq::SliceRandom;
164use rpc::{ErrorCode, ErrorExt, proto::PeerId};
165use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
166use selections_collection::{
167 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
168};
169use serde::{Deserialize, Serialize};
170use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
171use smallvec::{SmallVec, smallvec};
172use snippet::Snippet;
173use std::{
174 any::{Any, TypeId},
175 borrow::Cow,
176 cell::{OnceCell, RefCell},
177 cmp::{self, Ordering, Reverse},
178 iter::{self, Peekable},
179 mem,
180 num::NonZeroU32,
181 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
182 path::{Path, PathBuf},
183 rc::Rc,
184 sync::Arc,
185 time::{Duration, Instant},
186};
187use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
188use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
189use theme::{
190 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
191 observe_buffer_font_size_adjustment,
192};
193use ui::{
194 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
195 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
196};
197use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
198use workspace::{
199 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
200 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
201 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
202 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
203 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
204 searchable::SearchEvent,
205};
206
207use crate::{
208 code_context_menus::CompletionsMenuSource,
209 editor_settings::MultiCursorModifier,
210 hover_links::{find_url, find_url_from_range},
211 scroll::{ScrollOffset, ScrollPixelOffset},
212 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
213};
214
215pub const FILE_HEADER_HEIGHT: u32 = 2;
216pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
217const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
218const MAX_LINE_LEN: usize = 1024;
219const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
220const MAX_SELECTION_HISTORY_LEN: usize = 1024;
221pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
222#[doc(hidden)]
223pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
224pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
225
226pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
227pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
229pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
230
231pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
232pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
233pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
234
235pub type RenderDiffHunkControlsFn = Arc<
236 dyn Fn(
237 u32,
238 &DiffHunkStatus,
239 Range<Anchor>,
240 bool,
241 Pixels,
242 &Entity<Editor>,
243 &mut Window,
244 &mut App,
245 ) -> AnyElement,
246>;
247
248enum ReportEditorEvent {
249 Saved { auto_saved: bool },
250 EditorOpened,
251 Closed,
252}
253
254impl ReportEditorEvent {
255 pub fn event_type(&self) -> &'static str {
256 match self {
257 Self::Saved { .. } => "Editor Saved",
258 Self::EditorOpened => "Editor Opened",
259 Self::Closed => "Editor Closed",
260 }
261 }
262}
263
264struct InlineValueCache {
265 enabled: bool,
266 inlays: Vec<InlayId>,
267 refresh_task: Task<Option<()>>,
268}
269
270impl InlineValueCache {
271 fn new(enabled: bool) -> Self {
272 Self {
273 enabled,
274 inlays: Vec::new(),
275 refresh_task: Task::ready(None),
276 }
277 }
278}
279
280#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
281pub enum InlayId {
282 EditPrediction(u32),
283 DebuggerValue(u32),
284 // LSP
285 Hint(u32),
286 Color(u32),
287}
288
289impl InlayId {
290 fn id(&self) -> u32 {
291 match self {
292 Self::EditPrediction(id) => *id,
293 Self::DebuggerValue(id) => *id,
294 Self::Hint(id) => *id,
295 Self::Color(id) => *id,
296 }
297 }
298}
299
300pub enum ActiveDebugLine {}
301pub enum DebugStackFrameLine {}
302enum DocumentHighlightRead {}
303enum DocumentHighlightWrite {}
304enum InputComposition {}
305pub enum PendingInput {}
306enum SelectedTextHighlight {}
307
308pub enum ConflictsOuter {}
309pub enum ConflictsOurs {}
310pub enum ConflictsTheirs {}
311pub enum ConflictsOursMarker {}
312pub enum ConflictsTheirsMarker {}
313
314#[derive(Debug, Copy, Clone, PartialEq, Eq)]
315pub enum Navigated {
316 Yes,
317 No,
318}
319
320impl Navigated {
321 pub fn from_bool(yes: bool) -> Navigated {
322 if yes { Navigated::Yes } else { Navigated::No }
323 }
324}
325
326#[derive(Debug, Clone, PartialEq, Eq)]
327enum DisplayDiffHunk {
328 Folded {
329 display_row: DisplayRow,
330 },
331 Unfolded {
332 is_created_file: bool,
333 diff_base_byte_range: Range<usize>,
334 display_row_range: Range<DisplayRow>,
335 multi_buffer_range: Range<Anchor>,
336 status: DiffHunkStatus,
337 },
338}
339
340pub enum HideMouseCursorOrigin {
341 TypingAction,
342 MovementAction,
343}
344
345pub fn init_settings(cx: &mut App) {
346 EditorSettings::register(cx);
347}
348
349pub fn init(cx: &mut App) {
350 init_settings(cx);
351
352 cx.set_global(GlobalBlameRenderer(Arc::new(())));
353
354 workspace::register_project_item::<Editor>(cx);
355 workspace::FollowableViewRegistry::register::<Editor>(cx);
356 workspace::register_serializable_item::<Editor>(cx);
357
358 cx.observe_new(
359 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
360 workspace.register_action(Editor::new_file);
361 workspace.register_action(Editor::new_file_split);
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 post_scroll_update: Task<()>,
1195 refresh_colors_task: Task<()>,
1196 folding_newlines: Task<()>,
1197 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1198}
1199
1200#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1201enum NextScrollCursorCenterTopBottom {
1202 #[default]
1203 Center,
1204 Top,
1205 Bottom,
1206}
1207
1208impl NextScrollCursorCenterTopBottom {
1209 fn next(&self) -> Self {
1210 match self {
1211 Self::Center => Self::Top,
1212 Self::Top => Self::Bottom,
1213 Self::Bottom => Self::Center,
1214 }
1215 }
1216}
1217
1218#[derive(Clone)]
1219pub struct EditorSnapshot {
1220 pub mode: EditorMode,
1221 show_gutter: bool,
1222 show_line_numbers: Option<bool>,
1223 show_git_diff_gutter: Option<bool>,
1224 show_code_actions: Option<bool>,
1225 show_runnables: Option<bool>,
1226 show_breakpoints: Option<bool>,
1227 git_blame_gutter_max_author_length: Option<usize>,
1228 pub display_snapshot: DisplaySnapshot,
1229 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1230 is_focused: bool,
1231 scroll_anchor: ScrollAnchor,
1232 ongoing_scroll: OngoingScroll,
1233 current_line_highlight: CurrentLineHighlight,
1234 gutter_hovered: bool,
1235}
1236
1237#[derive(Default, Debug, Clone, Copy)]
1238pub struct GutterDimensions {
1239 pub left_padding: Pixels,
1240 pub right_padding: Pixels,
1241 pub width: Pixels,
1242 pub margin: Pixels,
1243 pub git_blame_entries_width: Option<Pixels>,
1244}
1245
1246impl GutterDimensions {
1247 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1248 Self {
1249 margin: Self::default_gutter_margin(font_id, font_size, cx),
1250 ..Default::default()
1251 }
1252 }
1253
1254 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1255 -cx.text_system().descent(font_id, font_size)
1256 }
1257 /// The full width of the space taken up by the gutter.
1258 pub fn full_width(&self) -> Pixels {
1259 self.margin + self.width
1260 }
1261
1262 /// The width of the space reserved for the fold indicators,
1263 /// use alongside 'justify_end' and `gutter_width` to
1264 /// right align content with the line numbers
1265 pub fn fold_area_width(&self) -> Pixels {
1266 self.margin + self.right_padding
1267 }
1268}
1269
1270struct CharacterDimensions {
1271 em_width: Pixels,
1272 em_advance: Pixels,
1273 line_height: Pixels,
1274}
1275
1276#[derive(Debug)]
1277pub struct RemoteSelection {
1278 pub replica_id: ReplicaId,
1279 pub selection: Selection<Anchor>,
1280 pub cursor_shape: CursorShape,
1281 pub collaborator_id: CollaboratorId,
1282 pub line_mode: bool,
1283 pub user_name: Option<SharedString>,
1284 pub color: PlayerColor,
1285}
1286
1287#[derive(Clone, Debug)]
1288struct SelectionHistoryEntry {
1289 selections: Arc<[Selection<Anchor>]>,
1290 select_next_state: Option<SelectNextState>,
1291 select_prev_state: Option<SelectNextState>,
1292 add_selections_state: Option<AddSelectionsState>,
1293}
1294
1295#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1296enum SelectionHistoryMode {
1297 Normal,
1298 Undoing,
1299 Redoing,
1300 Skipping,
1301}
1302
1303#[derive(Clone, PartialEq, Eq, Hash)]
1304struct HoveredCursor {
1305 replica_id: ReplicaId,
1306 selection_id: usize,
1307}
1308
1309impl Default for SelectionHistoryMode {
1310 fn default() -> Self {
1311 Self::Normal
1312 }
1313}
1314
1315#[derive(Debug)]
1316/// SelectionEffects controls the side-effects of updating the selection.
1317///
1318/// The default behaviour does "what you mostly want":
1319/// - it pushes to the nav history if the cursor moved by >10 lines
1320/// - it re-triggers completion requests
1321/// - it scrolls to fit
1322///
1323/// You might want to modify these behaviours. For example when doing a "jump"
1324/// like go to definition, we always want to add to nav history; but when scrolling
1325/// in vim mode we never do.
1326///
1327/// Similarly, you might want to disable scrolling if you don't want the viewport to
1328/// move.
1329#[derive(Clone)]
1330pub struct SelectionEffects {
1331 nav_history: Option<bool>,
1332 completions: bool,
1333 scroll: Option<Autoscroll>,
1334}
1335
1336impl Default for SelectionEffects {
1337 fn default() -> Self {
1338 Self {
1339 nav_history: None,
1340 completions: true,
1341 scroll: Some(Autoscroll::fit()),
1342 }
1343 }
1344}
1345impl SelectionEffects {
1346 pub fn scroll(scroll: Autoscroll) -> Self {
1347 Self {
1348 scroll: Some(scroll),
1349 ..Default::default()
1350 }
1351 }
1352
1353 pub fn no_scroll() -> Self {
1354 Self {
1355 scroll: None,
1356 ..Default::default()
1357 }
1358 }
1359
1360 pub fn completions(self, completions: bool) -> Self {
1361 Self {
1362 completions,
1363 ..self
1364 }
1365 }
1366
1367 pub fn nav_history(self, nav_history: bool) -> Self {
1368 Self {
1369 nav_history: Some(nav_history),
1370 ..self
1371 }
1372 }
1373}
1374
1375struct DeferredSelectionEffectsState {
1376 changed: bool,
1377 effects: SelectionEffects,
1378 old_cursor_position: Anchor,
1379 history_entry: SelectionHistoryEntry,
1380}
1381
1382#[derive(Default)]
1383struct SelectionHistory {
1384 #[allow(clippy::type_complexity)]
1385 selections_by_transaction:
1386 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1387 mode: SelectionHistoryMode,
1388 undo_stack: VecDeque<SelectionHistoryEntry>,
1389 redo_stack: VecDeque<SelectionHistoryEntry>,
1390}
1391
1392impl SelectionHistory {
1393 #[track_caller]
1394 fn insert_transaction(
1395 &mut self,
1396 transaction_id: TransactionId,
1397 selections: Arc<[Selection<Anchor>]>,
1398 ) {
1399 if selections.is_empty() {
1400 log::error!(
1401 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1402 std::panic::Location::caller()
1403 );
1404 return;
1405 }
1406 self.selections_by_transaction
1407 .insert(transaction_id, (selections, None));
1408 }
1409
1410 #[allow(clippy::type_complexity)]
1411 fn transaction(
1412 &self,
1413 transaction_id: TransactionId,
1414 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1415 self.selections_by_transaction.get(&transaction_id)
1416 }
1417
1418 #[allow(clippy::type_complexity)]
1419 fn transaction_mut(
1420 &mut self,
1421 transaction_id: TransactionId,
1422 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1423 self.selections_by_transaction.get_mut(&transaction_id)
1424 }
1425
1426 fn push(&mut self, entry: SelectionHistoryEntry) {
1427 if !entry.selections.is_empty() {
1428 match self.mode {
1429 SelectionHistoryMode::Normal => {
1430 self.push_undo(entry);
1431 self.redo_stack.clear();
1432 }
1433 SelectionHistoryMode::Undoing => self.push_redo(entry),
1434 SelectionHistoryMode::Redoing => self.push_undo(entry),
1435 SelectionHistoryMode::Skipping => {}
1436 }
1437 }
1438 }
1439
1440 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1441 if self
1442 .undo_stack
1443 .back()
1444 .is_none_or(|e| e.selections != entry.selections)
1445 {
1446 self.undo_stack.push_back(entry);
1447 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1448 self.undo_stack.pop_front();
1449 }
1450 }
1451 }
1452
1453 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1454 if self
1455 .redo_stack
1456 .back()
1457 .is_none_or(|e| e.selections != entry.selections)
1458 {
1459 self.redo_stack.push_back(entry);
1460 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1461 self.redo_stack.pop_front();
1462 }
1463 }
1464 }
1465}
1466
1467#[derive(Clone, Copy)]
1468pub struct RowHighlightOptions {
1469 pub autoscroll: bool,
1470 pub include_gutter: bool,
1471}
1472
1473impl Default for RowHighlightOptions {
1474 fn default() -> Self {
1475 Self {
1476 autoscroll: Default::default(),
1477 include_gutter: true,
1478 }
1479 }
1480}
1481
1482struct RowHighlight {
1483 index: usize,
1484 range: Range<Anchor>,
1485 color: Hsla,
1486 options: RowHighlightOptions,
1487 type_id: TypeId,
1488}
1489
1490#[derive(Clone, Debug)]
1491struct AddSelectionsState {
1492 groups: Vec<AddSelectionsGroup>,
1493}
1494
1495#[derive(Clone, Debug)]
1496struct AddSelectionsGroup {
1497 above: bool,
1498 stack: Vec<usize>,
1499}
1500
1501#[derive(Clone)]
1502struct SelectNextState {
1503 query: AhoCorasick,
1504 wordwise: bool,
1505 done: bool,
1506}
1507
1508impl std::fmt::Debug for SelectNextState {
1509 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1510 f.debug_struct(std::any::type_name::<Self>())
1511 .field("wordwise", &self.wordwise)
1512 .field("done", &self.done)
1513 .finish()
1514 }
1515}
1516
1517#[derive(Debug)]
1518struct AutocloseRegion {
1519 selection_id: usize,
1520 range: Range<Anchor>,
1521 pair: BracketPair,
1522}
1523
1524#[derive(Debug)]
1525struct SnippetState {
1526 ranges: Vec<Vec<Range<Anchor>>>,
1527 active_index: usize,
1528 choices: Vec<Option<Vec<String>>>,
1529}
1530
1531#[doc(hidden)]
1532pub struct RenameState {
1533 pub range: Range<Anchor>,
1534 pub old_name: Arc<str>,
1535 pub editor: Entity<Editor>,
1536 block_id: CustomBlockId,
1537}
1538
1539struct InvalidationStack<T>(Vec<T>);
1540
1541struct RegisteredEditPredictionProvider {
1542 provider: Arc<dyn EditPredictionProviderHandle>,
1543 _subscription: Subscription,
1544}
1545
1546#[derive(Debug, PartialEq, Eq)]
1547pub struct ActiveDiagnosticGroup {
1548 pub active_range: Range<Anchor>,
1549 pub active_message: String,
1550 pub group_id: usize,
1551 pub blocks: HashSet<CustomBlockId>,
1552}
1553
1554#[derive(Debug, PartialEq, Eq)]
1555
1556pub(crate) enum ActiveDiagnostic {
1557 None,
1558 All,
1559 Group(ActiveDiagnosticGroup),
1560}
1561
1562#[derive(Serialize, Deserialize, Clone, Debug)]
1563pub struct ClipboardSelection {
1564 /// The number of bytes in this selection.
1565 pub len: usize,
1566 /// Whether this was a full-line selection.
1567 pub is_entire_line: bool,
1568 /// The indentation of the first line when this content was originally copied.
1569 pub first_line_indent: u32,
1570}
1571
1572// selections, scroll behavior, was newest selection reversed
1573type SelectSyntaxNodeHistoryState = (
1574 Box<[Selection<usize>]>,
1575 SelectSyntaxNodeScrollBehavior,
1576 bool,
1577);
1578
1579#[derive(Default)]
1580struct SelectSyntaxNodeHistory {
1581 stack: Vec<SelectSyntaxNodeHistoryState>,
1582 // disable temporarily to allow changing selections without losing the stack
1583 pub disable_clearing: bool,
1584}
1585
1586impl SelectSyntaxNodeHistory {
1587 pub fn try_clear(&mut self) {
1588 if !self.disable_clearing {
1589 self.stack.clear();
1590 }
1591 }
1592
1593 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1594 self.stack.push(selection);
1595 }
1596
1597 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1598 self.stack.pop()
1599 }
1600}
1601
1602enum SelectSyntaxNodeScrollBehavior {
1603 CursorTop,
1604 FitSelection,
1605 CursorBottom,
1606}
1607
1608#[derive(Debug)]
1609pub(crate) struct NavigationData {
1610 cursor_anchor: Anchor,
1611 cursor_position: Point,
1612 scroll_anchor: ScrollAnchor,
1613 scroll_top_row: u32,
1614}
1615
1616#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1617pub enum GotoDefinitionKind {
1618 Symbol,
1619 Declaration,
1620 Type,
1621 Implementation,
1622}
1623
1624#[derive(Debug, Clone)]
1625enum InlayHintRefreshReason {
1626 ModifiersChanged(bool),
1627 Toggle(bool),
1628 SettingsChange(InlayHintSettings),
1629 NewLinesShown,
1630 BufferEdited(HashSet<Arc<Language>>),
1631 RefreshRequested,
1632 ExcerptsRemoved(Vec<ExcerptId>),
1633}
1634
1635impl InlayHintRefreshReason {
1636 fn description(&self) -> &'static str {
1637 match self {
1638 Self::ModifiersChanged(_) => "modifiers changed",
1639 Self::Toggle(_) => "toggle",
1640 Self::SettingsChange(_) => "settings change",
1641 Self::NewLinesShown => "new lines shown",
1642 Self::BufferEdited(_) => "buffer edited",
1643 Self::RefreshRequested => "refresh requested",
1644 Self::ExcerptsRemoved(_) => "excerpts removed",
1645 }
1646 }
1647}
1648
1649pub enum FormatTarget {
1650 Buffers(HashSet<Entity<Buffer>>),
1651 Ranges(Vec<Range<MultiBufferPoint>>),
1652}
1653
1654pub(crate) struct FocusedBlock {
1655 id: BlockId,
1656 focus_handle: WeakFocusHandle,
1657}
1658
1659#[derive(Clone)]
1660enum JumpData {
1661 MultiBufferRow {
1662 row: MultiBufferRow,
1663 line_offset_from_top: u32,
1664 },
1665 MultiBufferPoint {
1666 excerpt_id: ExcerptId,
1667 position: Point,
1668 anchor: text::Anchor,
1669 line_offset_from_top: u32,
1670 },
1671}
1672
1673pub enum MultibufferSelectionMode {
1674 First,
1675 All,
1676}
1677
1678#[derive(Clone, Copy, Debug, Default)]
1679pub struct RewrapOptions {
1680 pub override_language_settings: bool,
1681 pub preserve_existing_whitespace: bool,
1682}
1683
1684impl Editor {
1685 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1686 let buffer = cx.new(|cx| Buffer::local("", cx));
1687 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1688 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1689 }
1690
1691 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1692 let buffer = cx.new(|cx| Buffer::local("", cx));
1693 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1694 Self::new(EditorMode::full(), buffer, None, window, cx)
1695 }
1696
1697 pub fn auto_height(
1698 min_lines: usize,
1699 max_lines: usize,
1700 window: &mut Window,
1701 cx: &mut Context<Self>,
1702 ) -> Self {
1703 let buffer = cx.new(|cx| Buffer::local("", cx));
1704 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1705 Self::new(
1706 EditorMode::AutoHeight {
1707 min_lines,
1708 max_lines: Some(max_lines),
1709 },
1710 buffer,
1711 None,
1712 window,
1713 cx,
1714 )
1715 }
1716
1717 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1718 /// The editor grows as tall as needed to fit its content.
1719 pub fn auto_height_unbounded(
1720 min_lines: usize,
1721 window: &mut Window,
1722 cx: &mut Context<Self>,
1723 ) -> Self {
1724 let buffer = cx.new(|cx| Buffer::local("", cx));
1725 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1726 Self::new(
1727 EditorMode::AutoHeight {
1728 min_lines,
1729 max_lines: None,
1730 },
1731 buffer,
1732 None,
1733 window,
1734 cx,
1735 )
1736 }
1737
1738 pub fn for_buffer(
1739 buffer: Entity<Buffer>,
1740 project: Option<Entity<Project>>,
1741 window: &mut Window,
1742 cx: &mut Context<Self>,
1743 ) -> Self {
1744 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1745 Self::new(EditorMode::full(), buffer, project, window, cx)
1746 }
1747
1748 pub fn for_multibuffer(
1749 buffer: Entity<MultiBuffer>,
1750 project: Option<Entity<Project>>,
1751 window: &mut Window,
1752 cx: &mut Context<Self>,
1753 ) -> Self {
1754 Self::new(EditorMode::full(), buffer, project, window, cx)
1755 }
1756
1757 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1758 let mut clone = Self::new(
1759 self.mode.clone(),
1760 self.buffer.clone(),
1761 self.project.clone(),
1762 window,
1763 cx,
1764 );
1765 self.display_map.update(cx, |display_map, cx| {
1766 let snapshot = display_map.snapshot(cx);
1767 clone.display_map.update(cx, |display_map, cx| {
1768 display_map.set_state(&snapshot, cx);
1769 });
1770 });
1771 clone.folds_did_change(cx);
1772 clone.selections.clone_state(&self.selections);
1773 clone.scroll_manager.clone_state(&self.scroll_manager);
1774 clone.searchable = self.searchable;
1775 clone.read_only = self.read_only;
1776 clone
1777 }
1778
1779 pub fn new(
1780 mode: EditorMode,
1781 buffer: Entity<MultiBuffer>,
1782 project: Option<Entity<Project>>,
1783 window: &mut Window,
1784 cx: &mut Context<Self>,
1785 ) -> Self {
1786 Editor::new_internal(mode, buffer, project, None, window, cx)
1787 }
1788
1789 fn new_internal(
1790 mode: EditorMode,
1791 multi_buffer: Entity<MultiBuffer>,
1792 project: Option<Entity<Project>>,
1793 display_map: Option<Entity<DisplayMap>>,
1794 window: &mut Window,
1795 cx: &mut Context<Self>,
1796 ) -> Self {
1797 debug_assert!(
1798 display_map.is_none() || mode.is_minimap(),
1799 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1800 );
1801
1802 let full_mode = mode.is_full();
1803 let is_minimap = mode.is_minimap();
1804 let diagnostics_max_severity = if full_mode {
1805 EditorSettings::get_global(cx)
1806 .diagnostics_max_severity
1807 .unwrap_or(DiagnosticSeverity::Hint)
1808 } else {
1809 DiagnosticSeverity::Off
1810 };
1811 let style = window.text_style();
1812 let font_size = style.font_size.to_pixels(window.rem_size());
1813 let editor = cx.entity().downgrade();
1814 let fold_placeholder = FoldPlaceholder {
1815 constrain_width: false,
1816 render: Arc::new(move |fold_id, fold_range, cx| {
1817 let editor = editor.clone();
1818 div()
1819 .id(fold_id)
1820 .bg(cx.theme().colors().ghost_element_background)
1821 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1822 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1823 .rounded_xs()
1824 .size_full()
1825 .cursor_pointer()
1826 .child("⋯")
1827 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1828 .on_click(move |_, _window, cx| {
1829 editor
1830 .update(cx, |editor, cx| {
1831 editor.unfold_ranges(
1832 &[fold_range.start..fold_range.end],
1833 true,
1834 false,
1835 cx,
1836 );
1837 cx.stop_propagation();
1838 })
1839 .ok();
1840 })
1841 .into_any()
1842 }),
1843 merge_adjacent: true,
1844 ..FoldPlaceholder::default()
1845 };
1846 let display_map = display_map.unwrap_or_else(|| {
1847 cx.new(|cx| {
1848 DisplayMap::new(
1849 multi_buffer.clone(),
1850 style.font(),
1851 font_size,
1852 None,
1853 FILE_HEADER_HEIGHT,
1854 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1855 fold_placeholder,
1856 diagnostics_max_severity,
1857 cx,
1858 )
1859 })
1860 });
1861
1862 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1863
1864 let blink_manager = cx.new(|cx| {
1865 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1866 if is_minimap {
1867 blink_manager.disable(cx);
1868 }
1869 blink_manager
1870 });
1871
1872 let soft_wrap_mode_override =
1873 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1874
1875 let mut project_subscriptions = Vec::new();
1876 if full_mode && let Some(project) = project.as_ref() {
1877 project_subscriptions.push(cx.subscribe_in(
1878 project,
1879 window,
1880 |editor, _, event, window, cx| match event {
1881 project::Event::RefreshCodeLens => {
1882 // we always query lens with actions, without storing them, always refreshing them
1883 }
1884 project::Event::RefreshInlayHints => {
1885 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1886 }
1887 project::Event::LanguageServerRemoved(..) => {
1888 if editor.tasks_update_task.is_none() {
1889 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1890 }
1891 editor.registered_buffers.clear();
1892 editor.register_visible_buffers(cx);
1893 }
1894 project::Event::LanguageServerAdded(..) => {
1895 if editor.tasks_update_task.is_none() {
1896 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1897 }
1898 }
1899 project::Event::SnippetEdit(id, snippet_edits) => {
1900 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1901 let focus_handle = editor.focus_handle(cx);
1902 if focus_handle.is_focused(window) {
1903 let snapshot = buffer.read(cx).snapshot();
1904 for (range, snippet) in snippet_edits {
1905 let editor_range =
1906 language::range_from_lsp(*range).to_offset(&snapshot);
1907 editor
1908 .insert_snippet(
1909 &[editor_range],
1910 snippet.clone(),
1911 window,
1912 cx,
1913 )
1914 .ok();
1915 }
1916 }
1917 }
1918 }
1919 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1920 let buffer_id = *buffer_id;
1921 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1922 let registered = editor.register_buffer(buffer_id, cx);
1923 if registered {
1924 editor.update_lsp_data(Some(buffer_id), window, cx);
1925 editor.refresh_inlay_hints(
1926 InlayHintRefreshReason::RefreshRequested,
1927 cx,
1928 );
1929 refresh_linked_ranges(editor, window, cx);
1930 editor.refresh_code_actions(window, cx);
1931 editor.refresh_document_highlights(cx);
1932 }
1933 }
1934 }
1935
1936 project::Event::EntryRenamed(transaction) => {
1937 let Some(workspace) = editor.workspace() else {
1938 return;
1939 };
1940 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1941 else {
1942 return;
1943 };
1944 if active_editor.entity_id() == cx.entity_id() {
1945 let edited_buffers_already_open = {
1946 let other_editors: Vec<Entity<Editor>> = workspace
1947 .read(cx)
1948 .panes()
1949 .iter()
1950 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1951 .filter(|editor| editor.entity_id() != cx.entity_id())
1952 .collect();
1953
1954 transaction.0.keys().all(|buffer| {
1955 other_editors.iter().any(|editor| {
1956 let multi_buffer = editor.read(cx).buffer();
1957 multi_buffer.read(cx).is_singleton()
1958 && multi_buffer.read(cx).as_singleton().map_or(
1959 false,
1960 |singleton| {
1961 singleton.entity_id() == buffer.entity_id()
1962 },
1963 )
1964 })
1965 })
1966 };
1967
1968 if !edited_buffers_already_open {
1969 let workspace = workspace.downgrade();
1970 let transaction = transaction.clone();
1971 cx.defer_in(window, move |_, window, cx| {
1972 cx.spawn_in(window, async move |editor, cx| {
1973 Self::open_project_transaction(
1974 &editor,
1975 workspace,
1976 transaction,
1977 "Rename".to_string(),
1978 cx,
1979 )
1980 .await
1981 .ok()
1982 })
1983 .detach();
1984 });
1985 }
1986 }
1987 }
1988
1989 _ => {}
1990 },
1991 ));
1992 if let Some(task_inventory) = project
1993 .read(cx)
1994 .task_store()
1995 .read(cx)
1996 .task_inventory()
1997 .cloned()
1998 {
1999 project_subscriptions.push(cx.observe_in(
2000 &task_inventory,
2001 window,
2002 |editor, _, window, cx| {
2003 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2004 },
2005 ));
2006 };
2007
2008 project_subscriptions.push(cx.subscribe_in(
2009 &project.read(cx).breakpoint_store(),
2010 window,
2011 |editor, _, event, window, cx| match event {
2012 BreakpointStoreEvent::ClearDebugLines => {
2013 editor.clear_row_highlights::<ActiveDebugLine>();
2014 editor.refresh_inline_values(cx);
2015 }
2016 BreakpointStoreEvent::SetDebugLine => {
2017 if editor.go_to_active_debug_line(window, cx) {
2018 cx.stop_propagation();
2019 }
2020
2021 editor.refresh_inline_values(cx);
2022 }
2023 _ => {}
2024 },
2025 ));
2026 let git_store = project.read(cx).git_store().clone();
2027 let project = project.clone();
2028 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2029 if let GitStoreEvent::RepositoryUpdated(
2030 _,
2031 RepositoryEvent::Updated {
2032 new_instance: true, ..
2033 },
2034 _,
2035 ) = event
2036 {
2037 this.load_diff_task = Some(
2038 update_uncommitted_diff_for_buffer(
2039 cx.entity(),
2040 &project,
2041 this.buffer.read(cx).all_buffers(),
2042 this.buffer.clone(),
2043 cx,
2044 )
2045 .shared(),
2046 );
2047 }
2048 }));
2049 }
2050
2051 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2052
2053 let inlay_hint_settings =
2054 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2055 let focus_handle = cx.focus_handle();
2056 if !is_minimap {
2057 cx.on_focus(&focus_handle, window, Self::handle_focus)
2058 .detach();
2059 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2060 .detach();
2061 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2062 .detach();
2063 cx.on_blur(&focus_handle, window, Self::handle_blur)
2064 .detach();
2065 cx.observe_pending_input(window, Self::observe_pending_input)
2066 .detach();
2067 }
2068
2069 let show_indent_guides =
2070 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2071 Some(false)
2072 } else {
2073 None
2074 };
2075
2076 let breakpoint_store = match (&mode, project.as_ref()) {
2077 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2078 _ => None,
2079 };
2080
2081 let mut code_action_providers = Vec::new();
2082 let mut load_uncommitted_diff = None;
2083 if let Some(project) = project.clone() {
2084 load_uncommitted_diff = Some(
2085 update_uncommitted_diff_for_buffer(
2086 cx.entity(),
2087 &project,
2088 multi_buffer.read(cx).all_buffers(),
2089 multi_buffer.clone(),
2090 cx,
2091 )
2092 .shared(),
2093 );
2094 code_action_providers.push(Rc::new(project) as Rc<_>);
2095 }
2096
2097 let mut editor = Self {
2098 focus_handle,
2099 show_cursor_when_unfocused: false,
2100 last_focused_descendant: None,
2101 buffer: multi_buffer.clone(),
2102 display_map: display_map.clone(),
2103 placeholder_display_map: None,
2104 selections,
2105 scroll_manager: ScrollManager::new(cx),
2106 columnar_selection_state: None,
2107 add_selections_state: None,
2108 select_next_state: None,
2109 select_prev_state: None,
2110 selection_history: SelectionHistory::default(),
2111 defer_selection_effects: false,
2112 deferred_selection_effects_state: None,
2113 autoclose_regions: Vec::new(),
2114 snippet_stack: InvalidationStack::default(),
2115 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2116 ime_transaction: None,
2117 active_diagnostics: ActiveDiagnostic::None,
2118 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2119 inline_diagnostics_update: Task::ready(()),
2120 inline_diagnostics: Vec::new(),
2121 soft_wrap_mode_override,
2122 diagnostics_max_severity,
2123 hard_wrap: None,
2124 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2125 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2126 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2127 project,
2128 blink_manager: blink_manager.clone(),
2129 show_local_selections: true,
2130 show_scrollbars: ScrollbarAxes {
2131 horizontal: full_mode,
2132 vertical: full_mode,
2133 },
2134 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2135 offset_content: !matches!(mode, EditorMode::SingleLine),
2136 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2137 show_gutter: full_mode,
2138 show_line_numbers: (!full_mode).then_some(false),
2139 use_relative_line_numbers: None,
2140 disable_expand_excerpt_buttons: !full_mode,
2141 show_git_diff_gutter: None,
2142 show_code_actions: None,
2143 show_runnables: None,
2144 show_breakpoints: None,
2145 show_wrap_guides: None,
2146 show_indent_guides,
2147 highlight_order: 0,
2148 highlighted_rows: HashMap::default(),
2149 background_highlights: HashMap::default(),
2150 gutter_highlights: HashMap::default(),
2151 scrollbar_marker_state: ScrollbarMarkerState::default(),
2152 active_indent_guides_state: ActiveIndentGuidesState::default(),
2153 nav_history: None,
2154 context_menu: RefCell::new(None),
2155 context_menu_options: None,
2156 mouse_context_menu: None,
2157 completion_tasks: Vec::new(),
2158 inline_blame_popover: None,
2159 inline_blame_popover_show_task: None,
2160 signature_help_state: SignatureHelpState::default(),
2161 auto_signature_help: None,
2162 find_all_references_task_sources: Vec::new(),
2163 next_completion_id: 0,
2164 next_inlay_id: 0,
2165 code_action_providers,
2166 available_code_actions: None,
2167 code_actions_task: None,
2168 quick_selection_highlight_task: None,
2169 debounced_selection_highlight_task: None,
2170 document_highlights_task: None,
2171 linked_editing_range_task: None,
2172 pending_rename: None,
2173 searchable: !is_minimap,
2174 cursor_shape: EditorSettings::get_global(cx)
2175 .cursor_shape
2176 .unwrap_or_default(),
2177 current_line_highlight: None,
2178 autoindent_mode: Some(AutoindentMode::EachLine),
2179 collapse_matches: false,
2180 workspace: None,
2181 input_enabled: !is_minimap,
2182 use_modal_editing: full_mode,
2183 read_only: is_minimap,
2184 use_autoclose: true,
2185 use_auto_surround: true,
2186 auto_replace_emoji_shortcode: false,
2187 jsx_tag_auto_close_enabled_in_any_buffer: false,
2188 leader_id: None,
2189 remote_id: None,
2190 hover_state: HoverState::default(),
2191 pending_mouse_down: None,
2192 hovered_link_state: None,
2193 edit_prediction_provider: None,
2194 active_edit_prediction: None,
2195 stale_edit_prediction_in_menu: None,
2196 edit_prediction_preview: EditPredictionPreview::Inactive {
2197 released_too_fast: false,
2198 },
2199 inline_diagnostics_enabled: full_mode,
2200 diagnostics_enabled: full_mode,
2201 word_completions_enabled: full_mode,
2202 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2203 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2204 gutter_hovered: false,
2205 pixel_position_of_newest_cursor: None,
2206 last_bounds: None,
2207 last_position_map: None,
2208 expect_bounds_change: None,
2209 gutter_dimensions: GutterDimensions::default(),
2210 style: None,
2211 show_cursor_names: false,
2212 hovered_cursors: HashMap::default(),
2213 next_editor_action_id: EditorActionId::default(),
2214 editor_actions: Rc::default(),
2215 edit_predictions_hidden_for_vim_mode: false,
2216 show_edit_predictions_override: None,
2217 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2218 edit_prediction_settings: EditPredictionSettings::Disabled,
2219 edit_prediction_indent_conflict: false,
2220 edit_prediction_requires_modifier_in_indent_conflict: true,
2221 custom_context_menu: None,
2222 show_git_blame_gutter: false,
2223 show_git_blame_inline: false,
2224 show_selection_menu: None,
2225 show_git_blame_inline_delay_task: None,
2226 git_blame_inline_enabled: full_mode
2227 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2228 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2229 serialize_dirty_buffers: !is_minimap
2230 && ProjectSettings::get_global(cx)
2231 .session
2232 .restore_unsaved_buffers,
2233 blame: None,
2234 blame_subscription: None,
2235 tasks: BTreeMap::default(),
2236
2237 breakpoint_store,
2238 gutter_breakpoint_indicator: (None, None),
2239 hovered_diff_hunk_row: None,
2240 _subscriptions: (!is_minimap)
2241 .then(|| {
2242 vec![
2243 cx.observe(&multi_buffer, Self::on_buffer_changed),
2244 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2245 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2246 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2247 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2248 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2249 cx.observe_window_activation(window, |editor, window, cx| {
2250 let active = window.is_window_active();
2251 editor.blink_manager.update(cx, |blink_manager, cx| {
2252 if active {
2253 blink_manager.enable(cx);
2254 } else {
2255 blink_manager.disable(cx);
2256 }
2257 });
2258 if active {
2259 editor.show_mouse_cursor(cx);
2260 }
2261 }),
2262 ]
2263 })
2264 .unwrap_or_default(),
2265 tasks_update_task: None,
2266 pull_diagnostics_task: Task::ready(()),
2267 colors: None,
2268 refresh_colors_task: Task::ready(()),
2269 next_color_inlay_id: 0,
2270 post_scroll_update: Task::ready(()),
2271 linked_edit_ranges: Default::default(),
2272 in_project_search: false,
2273 previous_search_ranges: None,
2274 breadcrumb_header: None,
2275 focused_block: None,
2276 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2277 addons: HashMap::default(),
2278 registered_buffers: HashMap::default(),
2279 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2280 selection_mark_mode: false,
2281 toggle_fold_multiple_buffers: Task::ready(()),
2282 serialize_selections: Task::ready(()),
2283 serialize_folds: Task::ready(()),
2284 text_style_refinement: None,
2285 load_diff_task: load_uncommitted_diff,
2286 temporary_diff_override: false,
2287 mouse_cursor_hidden: false,
2288 minimap: None,
2289 hide_mouse_mode: EditorSettings::get_global(cx)
2290 .hide_mouse
2291 .unwrap_or_default(),
2292 change_list: ChangeList::new(),
2293 mode,
2294 selection_drag_state: SelectionDragState::None,
2295 folding_newlines: Task::ready(()),
2296 lookup_key: None,
2297 };
2298
2299 if is_minimap {
2300 return editor;
2301 }
2302
2303 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2304 editor
2305 ._subscriptions
2306 .push(cx.observe(breakpoints, |_, _, cx| {
2307 cx.notify();
2308 }));
2309 }
2310 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2311 editor._subscriptions.extend(project_subscriptions);
2312
2313 editor._subscriptions.push(cx.subscribe_in(
2314 &cx.entity(),
2315 window,
2316 |editor, _, e: &EditorEvent, window, cx| match e {
2317 EditorEvent::ScrollPositionChanged { local, .. } => {
2318 if *local {
2319 let new_anchor = editor.scroll_manager.anchor();
2320 let snapshot = editor.snapshot(window, cx);
2321 editor.update_restoration_data(cx, move |data| {
2322 data.scroll_position = (
2323 new_anchor.top_row(snapshot.buffer_snapshot()),
2324 new_anchor.offset,
2325 );
2326 });
2327 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2328 editor.inline_blame_popover.take();
2329 }
2330 }
2331 EditorEvent::Edited { .. } => {
2332 if !vim_enabled(cx) {
2333 let display_map = editor.display_snapshot(cx);
2334 let selections = editor.selections.all_adjusted_display(&display_map);
2335 let pop_state = editor
2336 .change_list
2337 .last()
2338 .map(|previous| {
2339 previous.len() == selections.len()
2340 && previous.iter().enumerate().all(|(ix, p)| {
2341 p.to_display_point(&display_map).row()
2342 == selections[ix].head().row()
2343 })
2344 })
2345 .unwrap_or(false);
2346 let new_positions = selections
2347 .into_iter()
2348 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2349 .collect();
2350 editor
2351 .change_list
2352 .push_to_change_list(pop_state, new_positions);
2353 }
2354 }
2355 _ => (),
2356 },
2357 ));
2358
2359 if let Some(dap_store) = editor
2360 .project
2361 .as_ref()
2362 .map(|project| project.read(cx).dap_store())
2363 {
2364 let weak_editor = cx.weak_entity();
2365
2366 editor
2367 ._subscriptions
2368 .push(
2369 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2370 let session_entity = cx.entity();
2371 weak_editor
2372 .update(cx, |editor, cx| {
2373 editor._subscriptions.push(
2374 cx.subscribe(&session_entity, Self::on_debug_session_event),
2375 );
2376 })
2377 .ok();
2378 }),
2379 );
2380
2381 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2382 editor
2383 ._subscriptions
2384 .push(cx.subscribe(&session, Self::on_debug_session_event));
2385 }
2386 }
2387
2388 // skip adding the initial selection to selection history
2389 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2390 editor.end_selection(window, cx);
2391 editor.selection_history.mode = SelectionHistoryMode::Normal;
2392
2393 editor.scroll_manager.show_scrollbars(window, cx);
2394 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2395
2396 if full_mode {
2397 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2398 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2399
2400 if editor.git_blame_inline_enabled {
2401 editor.start_git_blame_inline(false, window, cx);
2402 }
2403
2404 editor.go_to_active_debug_line(window, cx);
2405
2406 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2407 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2408 }
2409
2410 editor.minimap =
2411 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2412 editor.colors = Some(LspColorData::new(cx));
2413 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2414 }
2415
2416 editor
2417 }
2418
2419 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2420 self.selections.display_map(cx)
2421 }
2422
2423 pub fn deploy_mouse_context_menu(
2424 &mut self,
2425 position: gpui::Point<Pixels>,
2426 context_menu: Entity<ContextMenu>,
2427 window: &mut Window,
2428 cx: &mut Context<Self>,
2429 ) {
2430 self.mouse_context_menu = Some(MouseContextMenu::new(
2431 self,
2432 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2433 context_menu,
2434 window,
2435 cx,
2436 ));
2437 }
2438
2439 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2440 self.mouse_context_menu
2441 .as_ref()
2442 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2443 }
2444
2445 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2446 if self
2447 .selections
2448 .pending_anchor()
2449 .is_some_and(|pending_selection| {
2450 let snapshot = self.buffer().read(cx).snapshot(cx);
2451 pending_selection.range().includes(range, &snapshot)
2452 })
2453 {
2454 return true;
2455 }
2456
2457 self.selections
2458 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2459 .into_iter()
2460 .any(|selection| {
2461 // This is needed to cover a corner case, if we just check for an existing
2462 // selection in the fold range, having a cursor at the start of the fold
2463 // marks it as selected. Non-empty selections don't cause this.
2464 let length = selection.end - selection.start;
2465 length > 0
2466 })
2467 }
2468
2469 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2470 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2471 }
2472
2473 fn key_context_internal(
2474 &self,
2475 has_active_edit_prediction: bool,
2476 window: &mut Window,
2477 cx: &mut App,
2478 ) -> KeyContext {
2479 let mut key_context = KeyContext::new_with_defaults();
2480 key_context.add("Editor");
2481 let mode = match self.mode {
2482 EditorMode::SingleLine => "single_line",
2483 EditorMode::AutoHeight { .. } => "auto_height",
2484 EditorMode::Minimap { .. } => "minimap",
2485 EditorMode::Full { .. } => "full",
2486 };
2487
2488 if EditorSettings::jupyter_enabled(cx) {
2489 key_context.add("jupyter");
2490 }
2491
2492 key_context.set("mode", mode);
2493 if self.pending_rename.is_some() {
2494 key_context.add("renaming");
2495 }
2496
2497 match self.context_menu.borrow().as_ref() {
2498 Some(CodeContextMenu::Completions(menu)) => {
2499 if menu.visible() {
2500 key_context.add("menu");
2501 key_context.add("showing_completions");
2502 }
2503 }
2504 Some(CodeContextMenu::CodeActions(menu)) => {
2505 if menu.visible() {
2506 key_context.add("menu");
2507 key_context.add("showing_code_actions")
2508 }
2509 }
2510 None => {}
2511 }
2512
2513 if self.signature_help_state.has_multiple_signatures() {
2514 key_context.add("showing_signature_help");
2515 }
2516
2517 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2518 if !self.focus_handle(cx).contains_focused(window, cx)
2519 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2520 {
2521 for addon in self.addons.values() {
2522 addon.extend_key_context(&mut key_context, cx)
2523 }
2524 }
2525
2526 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2527 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2528 Some(
2529 file.full_path(cx)
2530 .extension()?
2531 .to_string_lossy()
2532 .into_owned(),
2533 )
2534 }) {
2535 key_context.set("extension", extension);
2536 }
2537 } else {
2538 key_context.add("multibuffer");
2539 }
2540
2541 if has_active_edit_prediction {
2542 if self.edit_prediction_in_conflict() {
2543 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2544 } else {
2545 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2546 key_context.add("copilot_suggestion");
2547 }
2548 }
2549
2550 if self.selection_mark_mode {
2551 key_context.add("selection_mode");
2552 }
2553
2554 let disjoint = self.selections.disjoint_anchors();
2555 let snapshot = self.snapshot(window, cx);
2556 let snapshot = snapshot.buffer_snapshot();
2557 if self.mode == EditorMode::SingleLine
2558 && let [selection] = disjoint
2559 && selection.start == selection.end
2560 && selection.end.to_offset(snapshot) == snapshot.len()
2561 {
2562 key_context.add("end_of_input");
2563 }
2564
2565 key_context
2566 }
2567
2568 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2569 self.last_bounds.as_ref()
2570 }
2571
2572 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2573 if self.mouse_cursor_hidden {
2574 self.mouse_cursor_hidden = false;
2575 cx.notify();
2576 }
2577 }
2578
2579 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2580 let hide_mouse_cursor = match origin {
2581 HideMouseCursorOrigin::TypingAction => {
2582 matches!(
2583 self.hide_mouse_mode,
2584 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2585 )
2586 }
2587 HideMouseCursorOrigin::MovementAction => {
2588 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2589 }
2590 };
2591 if self.mouse_cursor_hidden != hide_mouse_cursor {
2592 self.mouse_cursor_hidden = hide_mouse_cursor;
2593 cx.notify();
2594 }
2595 }
2596
2597 pub fn edit_prediction_in_conflict(&self) -> bool {
2598 if !self.show_edit_predictions_in_menu() {
2599 return false;
2600 }
2601
2602 let showing_completions = self
2603 .context_menu
2604 .borrow()
2605 .as_ref()
2606 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2607
2608 showing_completions
2609 || self.edit_prediction_requires_modifier()
2610 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2611 // bindings to insert tab characters.
2612 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2613 }
2614
2615 pub fn accept_edit_prediction_keybind(
2616 &self,
2617 accept_partial: bool,
2618 window: &mut Window,
2619 cx: &mut App,
2620 ) -> AcceptEditPredictionBinding {
2621 let key_context = self.key_context_internal(true, window, cx);
2622 let in_conflict = self.edit_prediction_in_conflict();
2623
2624 let bindings = if accept_partial {
2625 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2626 } else {
2627 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2628 };
2629
2630 // TODO: if the binding contains multiple keystrokes, display all of them, not
2631 // just the first one.
2632 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2633 !in_conflict
2634 || binding
2635 .keystrokes()
2636 .first()
2637 .is_some_and(|keystroke| keystroke.modifiers().modified())
2638 }))
2639 }
2640
2641 pub fn new_file(
2642 workspace: &mut Workspace,
2643 _: &workspace::NewFile,
2644 window: &mut Window,
2645 cx: &mut Context<Workspace>,
2646 ) {
2647 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2648 "Failed to create buffer",
2649 window,
2650 cx,
2651 |e, _, _| match e.error_code() {
2652 ErrorCode::RemoteUpgradeRequired => Some(format!(
2653 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2654 e.error_tag("required").unwrap_or("the latest version")
2655 )),
2656 _ => None,
2657 },
2658 );
2659 }
2660
2661 pub fn new_in_workspace(
2662 workspace: &mut Workspace,
2663 window: &mut Window,
2664 cx: &mut Context<Workspace>,
2665 ) -> Task<Result<Entity<Editor>>> {
2666 let project = workspace.project().clone();
2667 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2668
2669 cx.spawn_in(window, async move |workspace, cx| {
2670 let buffer = create.await?;
2671 workspace.update_in(cx, |workspace, window, cx| {
2672 let editor =
2673 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2674 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2675 editor
2676 })
2677 })
2678 }
2679
2680 fn new_file_vertical(
2681 workspace: &mut Workspace,
2682 _: &workspace::NewFileSplitVertical,
2683 window: &mut Window,
2684 cx: &mut Context<Workspace>,
2685 ) {
2686 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2687 }
2688
2689 fn new_file_horizontal(
2690 workspace: &mut Workspace,
2691 _: &workspace::NewFileSplitHorizontal,
2692 window: &mut Window,
2693 cx: &mut Context<Workspace>,
2694 ) {
2695 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2696 }
2697
2698 fn new_file_split(
2699 workspace: &mut Workspace,
2700 action: &workspace::NewFileSplit,
2701 window: &mut Window,
2702 cx: &mut Context<Workspace>,
2703 ) {
2704 Self::new_file_in_direction(workspace, action.0, window, cx)
2705 }
2706
2707 fn new_file_in_direction(
2708 workspace: &mut Workspace,
2709 direction: SplitDirection,
2710 window: &mut Window,
2711 cx: &mut Context<Workspace>,
2712 ) {
2713 let project = workspace.project().clone();
2714 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2715
2716 cx.spawn_in(window, async move |workspace, cx| {
2717 let buffer = create.await?;
2718 workspace.update_in(cx, move |workspace, window, cx| {
2719 workspace.split_item(
2720 direction,
2721 Box::new(
2722 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2723 ),
2724 window,
2725 cx,
2726 )
2727 })?;
2728 anyhow::Ok(())
2729 })
2730 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2731 match e.error_code() {
2732 ErrorCode::RemoteUpgradeRequired => Some(format!(
2733 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2734 e.error_tag("required").unwrap_or("the latest version")
2735 )),
2736 _ => None,
2737 }
2738 });
2739 }
2740
2741 pub fn leader_id(&self) -> Option<CollaboratorId> {
2742 self.leader_id
2743 }
2744
2745 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2746 &self.buffer
2747 }
2748
2749 pub fn project(&self) -> Option<&Entity<Project>> {
2750 self.project.as_ref()
2751 }
2752
2753 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2754 self.workspace.as_ref()?.0.upgrade()
2755 }
2756
2757 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2758 self.buffer().read(cx).title(cx)
2759 }
2760
2761 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2762 let git_blame_gutter_max_author_length = self
2763 .render_git_blame_gutter(cx)
2764 .then(|| {
2765 if let Some(blame) = self.blame.as_ref() {
2766 let max_author_length =
2767 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2768 Some(max_author_length)
2769 } else {
2770 None
2771 }
2772 })
2773 .flatten();
2774
2775 EditorSnapshot {
2776 mode: self.mode.clone(),
2777 show_gutter: self.show_gutter,
2778 show_line_numbers: self.show_line_numbers,
2779 show_git_diff_gutter: self.show_git_diff_gutter,
2780 show_code_actions: self.show_code_actions,
2781 show_runnables: self.show_runnables,
2782 show_breakpoints: self.show_breakpoints,
2783 git_blame_gutter_max_author_length,
2784 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2785 placeholder_display_snapshot: self
2786 .placeholder_display_map
2787 .as_ref()
2788 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2789 scroll_anchor: self.scroll_manager.anchor(),
2790 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2791 is_focused: self.focus_handle.is_focused(window),
2792 current_line_highlight: self
2793 .current_line_highlight
2794 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2795 gutter_hovered: self.gutter_hovered,
2796 }
2797 }
2798
2799 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2800 self.buffer.read(cx).language_at(point, cx)
2801 }
2802
2803 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2804 self.buffer.read(cx).read(cx).file_at(point).cloned()
2805 }
2806
2807 pub fn active_excerpt(
2808 &self,
2809 cx: &App,
2810 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2811 self.buffer
2812 .read(cx)
2813 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2814 }
2815
2816 pub fn mode(&self) -> &EditorMode {
2817 &self.mode
2818 }
2819
2820 pub fn set_mode(&mut self, mode: EditorMode) {
2821 self.mode = mode;
2822 }
2823
2824 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2825 self.collaboration_hub.as_deref()
2826 }
2827
2828 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2829 self.collaboration_hub = Some(hub);
2830 }
2831
2832 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2833 self.in_project_search = in_project_search;
2834 }
2835
2836 pub fn set_custom_context_menu(
2837 &mut self,
2838 f: impl 'static
2839 + Fn(
2840 &mut Self,
2841 DisplayPoint,
2842 &mut Window,
2843 &mut Context<Self>,
2844 ) -> Option<Entity<ui::ContextMenu>>,
2845 ) {
2846 self.custom_context_menu = Some(Box::new(f))
2847 }
2848
2849 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2850 self.completion_provider = provider;
2851 }
2852
2853 #[cfg(any(test, feature = "test-support"))]
2854 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2855 self.completion_provider.clone()
2856 }
2857
2858 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2859 self.semantics_provider.clone()
2860 }
2861
2862 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2863 self.semantics_provider = provider;
2864 }
2865
2866 pub fn set_edit_prediction_provider<T>(
2867 &mut self,
2868 provider: Option<Entity<T>>,
2869 window: &mut Window,
2870 cx: &mut Context<Self>,
2871 ) where
2872 T: EditPredictionProvider,
2873 {
2874 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2875 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2876 if this.focus_handle.is_focused(window) {
2877 this.update_visible_edit_prediction(window, cx);
2878 }
2879 }),
2880 provider: Arc::new(provider),
2881 });
2882 self.update_edit_prediction_settings(cx);
2883 self.refresh_edit_prediction(false, false, window, cx);
2884 }
2885
2886 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2887 self.placeholder_display_map
2888 .as_ref()
2889 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2890 }
2891
2892 pub fn set_placeholder_text(
2893 &mut self,
2894 placeholder_text: &str,
2895 window: &mut Window,
2896 cx: &mut Context<Self>,
2897 ) {
2898 let multibuffer = cx
2899 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2900
2901 let style = window.text_style();
2902
2903 self.placeholder_display_map = Some(cx.new(|cx| {
2904 DisplayMap::new(
2905 multibuffer,
2906 style.font(),
2907 style.font_size.to_pixels(window.rem_size()),
2908 None,
2909 FILE_HEADER_HEIGHT,
2910 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2911 Default::default(),
2912 DiagnosticSeverity::Off,
2913 cx,
2914 )
2915 }));
2916 cx.notify();
2917 }
2918
2919 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2920 self.cursor_shape = cursor_shape;
2921
2922 // Disrupt blink for immediate user feedback that the cursor shape has changed
2923 self.blink_manager.update(cx, BlinkManager::show_cursor);
2924
2925 cx.notify();
2926 }
2927
2928 pub fn set_current_line_highlight(
2929 &mut self,
2930 current_line_highlight: Option<CurrentLineHighlight>,
2931 ) {
2932 self.current_line_highlight = current_line_highlight;
2933 }
2934
2935 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2936 self.collapse_matches = collapse_matches;
2937 }
2938
2939 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2940 if self.collapse_matches {
2941 return range.start..range.start;
2942 }
2943 range.clone()
2944 }
2945
2946 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2947 if self.display_map.read(cx).clip_at_line_ends != clip {
2948 self.display_map
2949 .update(cx, |map, _| map.clip_at_line_ends = clip);
2950 }
2951 }
2952
2953 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2954 self.input_enabled = input_enabled;
2955 }
2956
2957 pub fn set_edit_predictions_hidden_for_vim_mode(
2958 &mut self,
2959 hidden: bool,
2960 window: &mut Window,
2961 cx: &mut Context<Self>,
2962 ) {
2963 if hidden != self.edit_predictions_hidden_for_vim_mode {
2964 self.edit_predictions_hidden_for_vim_mode = hidden;
2965 if hidden {
2966 self.update_visible_edit_prediction(window, cx);
2967 } else {
2968 self.refresh_edit_prediction(true, false, window, cx);
2969 }
2970 }
2971 }
2972
2973 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2974 self.menu_edit_predictions_policy = value;
2975 }
2976
2977 pub fn set_autoindent(&mut self, autoindent: bool) {
2978 if autoindent {
2979 self.autoindent_mode = Some(AutoindentMode::EachLine);
2980 } else {
2981 self.autoindent_mode = None;
2982 }
2983 }
2984
2985 pub fn read_only(&self, cx: &App) -> bool {
2986 self.read_only || self.buffer.read(cx).read_only()
2987 }
2988
2989 pub fn set_read_only(&mut self, read_only: bool) {
2990 self.read_only = read_only;
2991 }
2992
2993 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2994 self.use_autoclose = autoclose;
2995 }
2996
2997 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2998 self.use_auto_surround = auto_surround;
2999 }
3000
3001 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3002 self.auto_replace_emoji_shortcode = auto_replace;
3003 }
3004
3005 pub fn toggle_edit_predictions(
3006 &mut self,
3007 _: &ToggleEditPrediction,
3008 window: &mut Window,
3009 cx: &mut Context<Self>,
3010 ) {
3011 if self.show_edit_predictions_override.is_some() {
3012 self.set_show_edit_predictions(None, window, cx);
3013 } else {
3014 let show_edit_predictions = !self.edit_predictions_enabled();
3015 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3016 }
3017 }
3018
3019 pub fn set_show_edit_predictions(
3020 &mut self,
3021 show_edit_predictions: Option<bool>,
3022 window: &mut Window,
3023 cx: &mut Context<Self>,
3024 ) {
3025 self.show_edit_predictions_override = show_edit_predictions;
3026 self.update_edit_prediction_settings(cx);
3027
3028 if let Some(false) = show_edit_predictions {
3029 self.discard_edit_prediction(false, cx);
3030 } else {
3031 self.refresh_edit_prediction(false, true, window, cx);
3032 }
3033 }
3034
3035 fn edit_predictions_disabled_in_scope(
3036 &self,
3037 buffer: &Entity<Buffer>,
3038 buffer_position: language::Anchor,
3039 cx: &App,
3040 ) -> bool {
3041 let snapshot = buffer.read(cx).snapshot();
3042 let settings = snapshot.settings_at(buffer_position, cx);
3043
3044 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3045 return false;
3046 };
3047
3048 scope.override_name().is_some_and(|scope_name| {
3049 settings
3050 .edit_predictions_disabled_in
3051 .iter()
3052 .any(|s| s == scope_name)
3053 })
3054 }
3055
3056 pub fn set_use_modal_editing(&mut self, to: bool) {
3057 self.use_modal_editing = to;
3058 }
3059
3060 pub fn use_modal_editing(&self) -> bool {
3061 self.use_modal_editing
3062 }
3063
3064 fn selections_did_change(
3065 &mut self,
3066 local: bool,
3067 old_cursor_position: &Anchor,
3068 effects: SelectionEffects,
3069 window: &mut Window,
3070 cx: &mut Context<Self>,
3071 ) {
3072 window.invalidate_character_coordinates();
3073
3074 // Copy selections to primary selection buffer
3075 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3076 if local {
3077 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3078 let buffer_handle = self.buffer.read(cx).read(cx);
3079
3080 let mut text = String::new();
3081 for (index, selection) in selections.iter().enumerate() {
3082 let text_for_selection = buffer_handle
3083 .text_for_range(selection.start..selection.end)
3084 .collect::<String>();
3085
3086 text.push_str(&text_for_selection);
3087 if index != selections.len() - 1 {
3088 text.push('\n');
3089 }
3090 }
3091
3092 if !text.is_empty() {
3093 cx.write_to_primary(ClipboardItem::new_string(text));
3094 }
3095 }
3096
3097 let selection_anchors = self.selections.disjoint_anchors_arc();
3098
3099 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3100 self.buffer.update(cx, |buffer, cx| {
3101 buffer.set_active_selections(
3102 &selection_anchors,
3103 self.selections.line_mode(),
3104 self.cursor_shape,
3105 cx,
3106 )
3107 });
3108 }
3109 let display_map = self
3110 .display_map
3111 .update(cx, |display_map, cx| display_map.snapshot(cx));
3112 let buffer = display_map.buffer_snapshot();
3113 if self.selections.count() == 1 {
3114 self.add_selections_state = None;
3115 }
3116 self.select_next_state = None;
3117 self.select_prev_state = None;
3118 self.select_syntax_node_history.try_clear();
3119 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3120 self.snippet_stack.invalidate(&selection_anchors, buffer);
3121 self.take_rename(false, window, cx);
3122
3123 let newest_selection = self.selections.newest_anchor();
3124 let new_cursor_position = newest_selection.head();
3125 let selection_start = newest_selection.start;
3126
3127 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3128 self.push_to_nav_history(
3129 *old_cursor_position,
3130 Some(new_cursor_position.to_point(buffer)),
3131 false,
3132 effects.nav_history == Some(true),
3133 cx,
3134 );
3135 }
3136
3137 if local {
3138 if let Some(buffer_id) = new_cursor_position.buffer_id {
3139 self.register_buffer(buffer_id, cx);
3140 }
3141
3142 let mut context_menu = self.context_menu.borrow_mut();
3143 let completion_menu = match context_menu.as_ref() {
3144 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3145 Some(CodeContextMenu::CodeActions(_)) => {
3146 *context_menu = None;
3147 None
3148 }
3149 None => None,
3150 };
3151 let completion_position = completion_menu.map(|menu| menu.initial_position);
3152 drop(context_menu);
3153
3154 if effects.completions
3155 && let Some(completion_position) = completion_position
3156 {
3157 let start_offset = selection_start.to_offset(buffer);
3158 let position_matches = start_offset == completion_position.to_offset(buffer);
3159 let continue_showing = if position_matches {
3160 if self.snippet_stack.is_empty() {
3161 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3162 == Some(CharKind::Word)
3163 } else {
3164 // Snippet choices can be shown even when the cursor is in whitespace.
3165 // Dismissing the menu with actions like backspace is handled by
3166 // invalidation regions.
3167 true
3168 }
3169 } else {
3170 false
3171 };
3172
3173 if continue_showing {
3174 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3175 } else {
3176 self.hide_context_menu(window, cx);
3177 }
3178 }
3179
3180 hide_hover(self, cx);
3181
3182 if old_cursor_position.to_display_point(&display_map).row()
3183 != new_cursor_position.to_display_point(&display_map).row()
3184 {
3185 self.available_code_actions.take();
3186 }
3187 self.refresh_code_actions(window, cx);
3188 self.refresh_document_highlights(cx);
3189 refresh_linked_ranges(self, window, cx);
3190
3191 self.refresh_selected_text_highlights(false, window, cx);
3192 refresh_matching_bracket_highlights(self, cx);
3193 self.update_visible_edit_prediction(window, cx);
3194 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3195 self.inline_blame_popover.take();
3196 if self.git_blame_inline_enabled {
3197 self.start_inline_blame_timer(window, cx);
3198 }
3199 }
3200
3201 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3202 cx.emit(EditorEvent::SelectionsChanged { local });
3203
3204 let selections = &self.selections.disjoint_anchors_arc();
3205 if selections.len() == 1 {
3206 cx.emit(SearchEvent::ActiveMatchChanged)
3207 }
3208 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3209 let inmemory_selections = selections
3210 .iter()
3211 .map(|s| {
3212 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3213 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3214 })
3215 .collect();
3216 self.update_restoration_data(cx, |data| {
3217 data.selections = inmemory_selections;
3218 });
3219
3220 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3221 && let Some(workspace_id) =
3222 self.workspace.as_ref().and_then(|workspace| workspace.1)
3223 {
3224 let snapshot = self.buffer().read(cx).snapshot(cx);
3225 let selections = selections.clone();
3226 let background_executor = cx.background_executor().clone();
3227 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3228 self.serialize_selections = cx.background_spawn(async move {
3229 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3230 let db_selections = selections
3231 .iter()
3232 .map(|selection| {
3233 (
3234 selection.start.to_offset(&snapshot),
3235 selection.end.to_offset(&snapshot),
3236 )
3237 })
3238 .collect();
3239
3240 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3241 .await
3242 .with_context(|| {
3243 format!(
3244 "persisting editor selections for editor {editor_id}, \
3245 workspace {workspace_id:?}"
3246 )
3247 })
3248 .log_err();
3249 });
3250 }
3251 }
3252
3253 cx.notify();
3254 }
3255
3256 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3257 use text::ToOffset as _;
3258 use text::ToPoint as _;
3259
3260 if self.mode.is_minimap()
3261 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3262 {
3263 return;
3264 }
3265
3266 if !self.buffer().read(cx).is_singleton() {
3267 return;
3268 }
3269
3270 let display_snapshot = self
3271 .display_map
3272 .update(cx, |display_map, cx| display_map.snapshot(cx));
3273 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3274 return;
3275 };
3276 let inmemory_folds = display_snapshot
3277 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3278 .map(|fold| {
3279 fold.range.start.text_anchor.to_point(&snapshot)
3280 ..fold.range.end.text_anchor.to_point(&snapshot)
3281 })
3282 .collect();
3283 self.update_restoration_data(cx, |data| {
3284 data.folds = inmemory_folds;
3285 });
3286
3287 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3288 return;
3289 };
3290 let background_executor = cx.background_executor().clone();
3291 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3292 let db_folds = display_snapshot
3293 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3294 .map(|fold| {
3295 (
3296 fold.range.start.text_anchor.to_offset(&snapshot),
3297 fold.range.end.text_anchor.to_offset(&snapshot),
3298 )
3299 })
3300 .collect();
3301 self.serialize_folds = cx.background_spawn(async move {
3302 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3303 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3304 .await
3305 .with_context(|| {
3306 format!(
3307 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3308 )
3309 })
3310 .log_err();
3311 });
3312 }
3313
3314 pub fn sync_selections(
3315 &mut self,
3316 other: Entity<Editor>,
3317 cx: &mut Context<Self>,
3318 ) -> gpui::Subscription {
3319 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3320 if !other_selections.is_empty() {
3321 self.selections.change_with(cx, |selections| {
3322 selections.select_anchors(other_selections);
3323 });
3324 }
3325
3326 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3327 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3328 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3329 if other_selections.is_empty() {
3330 return;
3331 }
3332 this.selections.change_with(cx, |selections| {
3333 selections.select_anchors(other_selections);
3334 });
3335 }
3336 });
3337
3338 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3339 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3340 let these_selections = this.selections.disjoint_anchors().to_vec();
3341 if these_selections.is_empty() {
3342 return;
3343 }
3344 other.update(cx, |other_editor, cx| {
3345 other_editor.selections.change_with(cx, |selections| {
3346 selections.select_anchors(these_selections);
3347 })
3348 });
3349 }
3350 });
3351
3352 Subscription::join(other_subscription, this_subscription)
3353 }
3354
3355 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3356 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3357 /// effects of selection change occur at the end of the transaction.
3358 pub fn change_selections<R>(
3359 &mut self,
3360 effects: SelectionEffects,
3361 window: &mut Window,
3362 cx: &mut Context<Self>,
3363 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3364 ) -> R {
3365 if let Some(state) = &mut self.deferred_selection_effects_state {
3366 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3367 state.effects.completions = effects.completions;
3368 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3369 let (changed, result) = self.selections.change_with(cx, change);
3370 state.changed |= changed;
3371 return result;
3372 }
3373 let mut state = DeferredSelectionEffectsState {
3374 changed: false,
3375 effects,
3376 old_cursor_position: self.selections.newest_anchor().head(),
3377 history_entry: SelectionHistoryEntry {
3378 selections: self.selections.disjoint_anchors_arc(),
3379 select_next_state: self.select_next_state.clone(),
3380 select_prev_state: self.select_prev_state.clone(),
3381 add_selections_state: self.add_selections_state.clone(),
3382 },
3383 };
3384 let (changed, result) = self.selections.change_with(cx, change);
3385 state.changed = state.changed || changed;
3386 if self.defer_selection_effects {
3387 self.deferred_selection_effects_state = Some(state);
3388 } else {
3389 self.apply_selection_effects(state, window, cx);
3390 }
3391 result
3392 }
3393
3394 /// Defers the effects of selection change, so that the effects of multiple calls to
3395 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3396 /// to selection history and the state of popovers based on selection position aren't
3397 /// erroneously updated.
3398 pub fn with_selection_effects_deferred<R>(
3399 &mut self,
3400 window: &mut Window,
3401 cx: &mut Context<Self>,
3402 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3403 ) -> R {
3404 let already_deferred = self.defer_selection_effects;
3405 self.defer_selection_effects = true;
3406 let result = update(self, window, cx);
3407 if !already_deferred {
3408 self.defer_selection_effects = false;
3409 if let Some(state) = self.deferred_selection_effects_state.take() {
3410 self.apply_selection_effects(state, window, cx);
3411 }
3412 }
3413 result
3414 }
3415
3416 fn apply_selection_effects(
3417 &mut self,
3418 state: DeferredSelectionEffectsState,
3419 window: &mut Window,
3420 cx: &mut Context<Self>,
3421 ) {
3422 if state.changed {
3423 self.selection_history.push(state.history_entry);
3424
3425 if let Some(autoscroll) = state.effects.scroll {
3426 self.request_autoscroll(autoscroll, cx);
3427 }
3428
3429 let old_cursor_position = &state.old_cursor_position;
3430
3431 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3432
3433 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3434 self.show_signature_help(&ShowSignatureHelp, window, cx);
3435 }
3436 }
3437 }
3438
3439 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3440 where
3441 I: IntoIterator<Item = (Range<S>, T)>,
3442 S: ToOffset,
3443 T: Into<Arc<str>>,
3444 {
3445 if self.read_only(cx) {
3446 return;
3447 }
3448
3449 self.buffer
3450 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3451 }
3452
3453 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3454 where
3455 I: IntoIterator<Item = (Range<S>, T)>,
3456 S: ToOffset,
3457 T: Into<Arc<str>>,
3458 {
3459 if self.read_only(cx) {
3460 return;
3461 }
3462
3463 self.buffer.update(cx, |buffer, cx| {
3464 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3465 });
3466 }
3467
3468 pub fn edit_with_block_indent<I, S, T>(
3469 &mut self,
3470 edits: I,
3471 original_indent_columns: Vec<Option<u32>>,
3472 cx: &mut Context<Self>,
3473 ) where
3474 I: IntoIterator<Item = (Range<S>, T)>,
3475 S: ToOffset,
3476 T: Into<Arc<str>>,
3477 {
3478 if self.read_only(cx) {
3479 return;
3480 }
3481
3482 self.buffer.update(cx, |buffer, cx| {
3483 buffer.edit(
3484 edits,
3485 Some(AutoindentMode::Block {
3486 original_indent_columns,
3487 }),
3488 cx,
3489 )
3490 });
3491 }
3492
3493 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3494 self.hide_context_menu(window, cx);
3495
3496 match phase {
3497 SelectPhase::Begin {
3498 position,
3499 add,
3500 click_count,
3501 } => self.begin_selection(position, add, click_count, window, cx),
3502 SelectPhase::BeginColumnar {
3503 position,
3504 goal_column,
3505 reset,
3506 mode,
3507 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3508 SelectPhase::Extend {
3509 position,
3510 click_count,
3511 } => self.extend_selection(position, click_count, window, cx),
3512 SelectPhase::Update {
3513 position,
3514 goal_column,
3515 scroll_delta,
3516 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3517 SelectPhase::End => self.end_selection(window, cx),
3518 }
3519 }
3520
3521 fn extend_selection(
3522 &mut self,
3523 position: DisplayPoint,
3524 click_count: usize,
3525 window: &mut Window,
3526 cx: &mut Context<Self>,
3527 ) {
3528 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3529 let tail = self.selections.newest::<usize>(&display_map).tail();
3530 let click_count = click_count.max(match self.selections.select_mode() {
3531 SelectMode::Character => 1,
3532 SelectMode::Word(_) => 2,
3533 SelectMode::Line(_) => 3,
3534 SelectMode::All => 4,
3535 });
3536 self.begin_selection(position, false, click_count, window, cx);
3537
3538 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3539
3540 let current_selection = match self.selections.select_mode() {
3541 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3542 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3543 };
3544
3545 let mut pending_selection = self
3546 .selections
3547 .pending_anchor()
3548 .cloned()
3549 .expect("extend_selection not called with pending selection");
3550
3551 if pending_selection
3552 .start
3553 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3554 == Ordering::Greater
3555 {
3556 pending_selection.start = current_selection.start;
3557 }
3558 if pending_selection
3559 .end
3560 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3561 == Ordering::Less
3562 {
3563 pending_selection.end = current_selection.end;
3564 pending_selection.reversed = true;
3565 }
3566
3567 let mut pending_mode = self.selections.pending_mode().unwrap();
3568 match &mut pending_mode {
3569 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3570 _ => {}
3571 }
3572
3573 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3574 SelectionEffects::scroll(Autoscroll::fit())
3575 } else {
3576 SelectionEffects::no_scroll()
3577 };
3578
3579 self.change_selections(effects, window, cx, |s| {
3580 s.set_pending(pending_selection.clone(), pending_mode);
3581 s.set_is_extending(true);
3582 });
3583 }
3584
3585 fn begin_selection(
3586 &mut self,
3587 position: DisplayPoint,
3588 add: bool,
3589 click_count: usize,
3590 window: &mut Window,
3591 cx: &mut Context<Self>,
3592 ) {
3593 if !self.focus_handle.is_focused(window) {
3594 self.last_focused_descendant = None;
3595 window.focus(&self.focus_handle);
3596 }
3597
3598 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3599 let buffer = display_map.buffer_snapshot();
3600 let position = display_map.clip_point(position, Bias::Left);
3601
3602 let start;
3603 let end;
3604 let mode;
3605 let mut auto_scroll;
3606 match click_count {
3607 1 => {
3608 start = buffer.anchor_before(position.to_point(&display_map));
3609 end = start;
3610 mode = SelectMode::Character;
3611 auto_scroll = true;
3612 }
3613 2 => {
3614 let position = display_map
3615 .clip_point(position, Bias::Left)
3616 .to_offset(&display_map, Bias::Left);
3617 let (range, _) = buffer.surrounding_word(position, None);
3618 start = buffer.anchor_before(range.start);
3619 end = buffer.anchor_before(range.end);
3620 mode = SelectMode::Word(start..end);
3621 auto_scroll = true;
3622 }
3623 3 => {
3624 let position = display_map
3625 .clip_point(position, Bias::Left)
3626 .to_point(&display_map);
3627 let line_start = display_map.prev_line_boundary(position).0;
3628 let next_line_start = buffer.clip_point(
3629 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3630 Bias::Left,
3631 );
3632 start = buffer.anchor_before(line_start);
3633 end = buffer.anchor_before(next_line_start);
3634 mode = SelectMode::Line(start..end);
3635 auto_scroll = true;
3636 }
3637 _ => {
3638 start = buffer.anchor_before(0);
3639 end = buffer.anchor_before(buffer.len());
3640 mode = SelectMode::All;
3641 auto_scroll = false;
3642 }
3643 }
3644 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3645
3646 let point_to_delete: Option<usize> = {
3647 let selected_points: Vec<Selection<Point>> =
3648 self.selections.disjoint_in_range(start..end, &display_map);
3649
3650 if !add || click_count > 1 {
3651 None
3652 } else if !selected_points.is_empty() {
3653 Some(selected_points[0].id)
3654 } else {
3655 let clicked_point_already_selected =
3656 self.selections.disjoint_anchors().iter().find(|selection| {
3657 selection.start.to_point(buffer) == start.to_point(buffer)
3658 || selection.end.to_point(buffer) == end.to_point(buffer)
3659 });
3660
3661 clicked_point_already_selected.map(|selection| selection.id)
3662 }
3663 };
3664
3665 let selections_count = self.selections.count();
3666 let effects = if auto_scroll {
3667 SelectionEffects::default()
3668 } else {
3669 SelectionEffects::no_scroll()
3670 };
3671
3672 self.change_selections(effects, window, cx, |s| {
3673 if let Some(point_to_delete) = point_to_delete {
3674 s.delete(point_to_delete);
3675
3676 if selections_count == 1 {
3677 s.set_pending_anchor_range(start..end, mode);
3678 }
3679 } else {
3680 if !add {
3681 s.clear_disjoint();
3682 }
3683
3684 s.set_pending_anchor_range(start..end, mode);
3685 }
3686 });
3687 }
3688
3689 fn begin_columnar_selection(
3690 &mut self,
3691 position: DisplayPoint,
3692 goal_column: u32,
3693 reset: bool,
3694 mode: ColumnarMode,
3695 window: &mut Window,
3696 cx: &mut Context<Self>,
3697 ) {
3698 if !self.focus_handle.is_focused(window) {
3699 self.last_focused_descendant = None;
3700 window.focus(&self.focus_handle);
3701 }
3702
3703 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3704
3705 if reset {
3706 let pointer_position = display_map
3707 .buffer_snapshot()
3708 .anchor_before(position.to_point(&display_map));
3709
3710 self.change_selections(
3711 SelectionEffects::scroll(Autoscroll::newest()),
3712 window,
3713 cx,
3714 |s| {
3715 s.clear_disjoint();
3716 s.set_pending_anchor_range(
3717 pointer_position..pointer_position,
3718 SelectMode::Character,
3719 );
3720 },
3721 );
3722 };
3723
3724 let tail = self.selections.newest::<Point>(&display_map).tail();
3725 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3726 self.columnar_selection_state = match mode {
3727 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3728 selection_tail: selection_anchor,
3729 display_point: if reset {
3730 if position.column() != goal_column {
3731 Some(DisplayPoint::new(position.row(), goal_column))
3732 } else {
3733 None
3734 }
3735 } else {
3736 None
3737 },
3738 }),
3739 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3740 selection_tail: selection_anchor,
3741 }),
3742 };
3743
3744 if !reset {
3745 self.select_columns(position, goal_column, &display_map, window, cx);
3746 }
3747 }
3748
3749 fn update_selection(
3750 &mut self,
3751 position: DisplayPoint,
3752 goal_column: u32,
3753 scroll_delta: gpui::Point<f32>,
3754 window: &mut Window,
3755 cx: &mut Context<Self>,
3756 ) {
3757 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3758
3759 if self.columnar_selection_state.is_some() {
3760 self.select_columns(position, goal_column, &display_map, window, cx);
3761 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3762 let buffer = display_map.buffer_snapshot();
3763 let head;
3764 let tail;
3765 let mode = self.selections.pending_mode().unwrap();
3766 match &mode {
3767 SelectMode::Character => {
3768 head = position.to_point(&display_map);
3769 tail = pending.tail().to_point(buffer);
3770 }
3771 SelectMode::Word(original_range) => {
3772 let offset = display_map
3773 .clip_point(position, Bias::Left)
3774 .to_offset(&display_map, Bias::Left);
3775 let original_range = original_range.to_offset(buffer);
3776
3777 let head_offset = if buffer.is_inside_word(offset, None)
3778 || original_range.contains(&offset)
3779 {
3780 let (word_range, _) = buffer.surrounding_word(offset, None);
3781 if word_range.start < original_range.start {
3782 word_range.start
3783 } else {
3784 word_range.end
3785 }
3786 } else {
3787 offset
3788 };
3789
3790 head = head_offset.to_point(buffer);
3791 if head_offset <= original_range.start {
3792 tail = original_range.end.to_point(buffer);
3793 } else {
3794 tail = original_range.start.to_point(buffer);
3795 }
3796 }
3797 SelectMode::Line(original_range) => {
3798 let original_range = original_range.to_point(display_map.buffer_snapshot());
3799
3800 let position = display_map
3801 .clip_point(position, Bias::Left)
3802 .to_point(&display_map);
3803 let line_start = display_map.prev_line_boundary(position).0;
3804 let next_line_start = buffer.clip_point(
3805 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3806 Bias::Left,
3807 );
3808
3809 if line_start < original_range.start {
3810 head = line_start
3811 } else {
3812 head = next_line_start
3813 }
3814
3815 if head <= original_range.start {
3816 tail = original_range.end;
3817 } else {
3818 tail = original_range.start;
3819 }
3820 }
3821 SelectMode::All => {
3822 return;
3823 }
3824 };
3825
3826 if head < tail {
3827 pending.start = buffer.anchor_before(head);
3828 pending.end = buffer.anchor_before(tail);
3829 pending.reversed = true;
3830 } else {
3831 pending.start = buffer.anchor_before(tail);
3832 pending.end = buffer.anchor_before(head);
3833 pending.reversed = false;
3834 }
3835
3836 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3837 s.set_pending(pending.clone(), mode);
3838 });
3839 } else {
3840 log::error!("update_selection dispatched with no pending selection");
3841 return;
3842 }
3843
3844 self.apply_scroll_delta(scroll_delta, window, cx);
3845 cx.notify();
3846 }
3847
3848 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3849 self.columnar_selection_state.take();
3850 if let Some(pending_mode) = self.selections.pending_mode() {
3851 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3852 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3853 s.select(selections);
3854 s.clear_pending();
3855 if s.is_extending() {
3856 s.set_is_extending(false);
3857 } else {
3858 s.set_select_mode(pending_mode);
3859 }
3860 });
3861 }
3862 }
3863
3864 fn select_columns(
3865 &mut self,
3866 head: DisplayPoint,
3867 goal_column: u32,
3868 display_map: &DisplaySnapshot,
3869 window: &mut Window,
3870 cx: &mut Context<Self>,
3871 ) {
3872 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3873 return;
3874 };
3875
3876 let tail = match columnar_state {
3877 ColumnarSelectionState::FromMouse {
3878 selection_tail,
3879 display_point,
3880 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3881 ColumnarSelectionState::FromSelection { selection_tail } => {
3882 selection_tail.to_display_point(display_map)
3883 }
3884 };
3885
3886 let start_row = cmp::min(tail.row(), head.row());
3887 let end_row = cmp::max(tail.row(), head.row());
3888 let start_column = cmp::min(tail.column(), goal_column);
3889 let end_column = cmp::max(tail.column(), goal_column);
3890 let reversed = start_column < tail.column();
3891
3892 let selection_ranges = (start_row.0..=end_row.0)
3893 .map(DisplayRow)
3894 .filter_map(|row| {
3895 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3896 || start_column <= display_map.line_len(row))
3897 && !display_map.is_block_line(row)
3898 {
3899 let start = display_map
3900 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3901 .to_point(display_map);
3902 let end = display_map
3903 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3904 .to_point(display_map);
3905 if reversed {
3906 Some(end..start)
3907 } else {
3908 Some(start..end)
3909 }
3910 } else {
3911 None
3912 }
3913 })
3914 .collect::<Vec<_>>();
3915 if selection_ranges.is_empty() {
3916 return;
3917 }
3918
3919 let ranges = match columnar_state {
3920 ColumnarSelectionState::FromMouse { .. } => {
3921 let mut non_empty_ranges = selection_ranges
3922 .iter()
3923 .filter(|selection_range| selection_range.start != selection_range.end)
3924 .peekable();
3925 if non_empty_ranges.peek().is_some() {
3926 non_empty_ranges.cloned().collect()
3927 } else {
3928 selection_ranges
3929 }
3930 }
3931 _ => selection_ranges,
3932 };
3933
3934 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3935 s.select_ranges(ranges);
3936 });
3937 cx.notify();
3938 }
3939
3940 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3941 self.selections
3942 .all_adjusted(snapshot)
3943 .iter()
3944 .any(|selection| !selection.is_empty())
3945 }
3946
3947 pub fn has_pending_nonempty_selection(&self) -> bool {
3948 let pending_nonempty_selection = match self.selections.pending_anchor() {
3949 Some(Selection { start, end, .. }) => start != end,
3950 None => false,
3951 };
3952
3953 pending_nonempty_selection
3954 || (self.columnar_selection_state.is_some()
3955 && self.selections.disjoint_anchors().len() > 1)
3956 }
3957
3958 pub fn has_pending_selection(&self) -> bool {
3959 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3960 }
3961
3962 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3963 self.selection_mark_mode = false;
3964 self.selection_drag_state = SelectionDragState::None;
3965
3966 if self.clear_expanded_diff_hunks(cx) {
3967 cx.notify();
3968 return;
3969 }
3970 if self.dismiss_menus_and_popups(true, window, cx) {
3971 return;
3972 }
3973
3974 if self.mode.is_full()
3975 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3976 {
3977 return;
3978 }
3979
3980 cx.propagate();
3981 }
3982
3983 pub fn dismiss_menus_and_popups(
3984 &mut self,
3985 is_user_requested: bool,
3986 window: &mut Window,
3987 cx: &mut Context<Self>,
3988 ) -> bool {
3989 if self.take_rename(false, window, cx).is_some() {
3990 return true;
3991 }
3992
3993 if hide_hover(self, cx) {
3994 return true;
3995 }
3996
3997 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3998 return true;
3999 }
4000
4001 if self.hide_context_menu(window, cx).is_some() {
4002 return true;
4003 }
4004
4005 if self.mouse_context_menu.take().is_some() {
4006 return true;
4007 }
4008
4009 if is_user_requested && self.discard_edit_prediction(true, cx) {
4010 return true;
4011 }
4012
4013 if self.snippet_stack.pop().is_some() {
4014 return true;
4015 }
4016
4017 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4018 self.dismiss_diagnostics(cx);
4019 return true;
4020 }
4021
4022 false
4023 }
4024
4025 fn linked_editing_ranges_for(
4026 &self,
4027 selection: Range<text::Anchor>,
4028 cx: &App,
4029 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4030 if self.linked_edit_ranges.is_empty() {
4031 return None;
4032 }
4033 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4034 selection.end.buffer_id.and_then(|end_buffer_id| {
4035 if selection.start.buffer_id != Some(end_buffer_id) {
4036 return None;
4037 }
4038 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4039 let snapshot = buffer.read(cx).snapshot();
4040 self.linked_edit_ranges
4041 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4042 .map(|ranges| (ranges, snapshot, buffer))
4043 })?;
4044 use text::ToOffset as TO;
4045 // find offset from the start of current range to current cursor position
4046 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4047
4048 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4049 let start_difference = start_offset - start_byte_offset;
4050 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4051 let end_difference = end_offset - start_byte_offset;
4052 // Current range has associated linked ranges.
4053 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4054 for range in linked_ranges.iter() {
4055 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4056 let end_offset = start_offset + end_difference;
4057 let start_offset = start_offset + start_difference;
4058 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4059 continue;
4060 }
4061 if self.selections.disjoint_anchor_ranges().any(|s| {
4062 if s.start.buffer_id != selection.start.buffer_id
4063 || s.end.buffer_id != selection.end.buffer_id
4064 {
4065 return false;
4066 }
4067 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4068 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4069 }) {
4070 continue;
4071 }
4072 let start = buffer_snapshot.anchor_after(start_offset);
4073 let end = buffer_snapshot.anchor_after(end_offset);
4074 linked_edits
4075 .entry(buffer.clone())
4076 .or_default()
4077 .push(start..end);
4078 }
4079 Some(linked_edits)
4080 }
4081
4082 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4083 let text: Arc<str> = text.into();
4084
4085 if self.read_only(cx) {
4086 return;
4087 }
4088
4089 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4090
4091 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4092 let mut bracket_inserted = false;
4093 let mut edits = Vec::new();
4094 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4095 let mut new_selections = Vec::with_capacity(selections.len());
4096 let mut new_autoclose_regions = Vec::new();
4097 let snapshot = self.buffer.read(cx).read(cx);
4098 let mut clear_linked_edit_ranges = false;
4099
4100 for (selection, autoclose_region) in
4101 self.selections_with_autoclose_regions(selections, &snapshot)
4102 {
4103 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4104 // Determine if the inserted text matches the opening or closing
4105 // bracket of any of this language's bracket pairs.
4106 let mut bracket_pair = None;
4107 let mut is_bracket_pair_start = false;
4108 let mut is_bracket_pair_end = false;
4109 if !text.is_empty() {
4110 let mut bracket_pair_matching_end = None;
4111 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4112 // and they are removing the character that triggered IME popup.
4113 for (pair, enabled) in scope.brackets() {
4114 if !pair.close && !pair.surround {
4115 continue;
4116 }
4117
4118 if enabled && pair.start.ends_with(text.as_ref()) {
4119 let prefix_len = pair.start.len() - text.len();
4120 let preceding_text_matches_prefix = prefix_len == 0
4121 || (selection.start.column >= (prefix_len as u32)
4122 && snapshot.contains_str_at(
4123 Point::new(
4124 selection.start.row,
4125 selection.start.column - (prefix_len as u32),
4126 ),
4127 &pair.start[..prefix_len],
4128 ));
4129 if preceding_text_matches_prefix {
4130 bracket_pair = Some(pair.clone());
4131 is_bracket_pair_start = true;
4132 break;
4133 }
4134 }
4135 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4136 {
4137 // take first bracket pair matching end, but don't break in case a later bracket
4138 // pair matches start
4139 bracket_pair_matching_end = Some(pair.clone());
4140 }
4141 }
4142 if let Some(end) = bracket_pair_matching_end
4143 && bracket_pair.is_none()
4144 {
4145 bracket_pair = Some(end);
4146 is_bracket_pair_end = true;
4147 }
4148 }
4149
4150 if let Some(bracket_pair) = bracket_pair {
4151 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4152 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4153 let auto_surround =
4154 self.use_auto_surround && snapshot_settings.use_auto_surround;
4155 if selection.is_empty() {
4156 if is_bracket_pair_start {
4157 // If the inserted text is a suffix of an opening bracket and the
4158 // selection is preceded by the rest of the opening bracket, then
4159 // insert the closing bracket.
4160 let following_text_allows_autoclose = snapshot
4161 .chars_at(selection.start)
4162 .next()
4163 .is_none_or(|c| scope.should_autoclose_before(c));
4164
4165 let preceding_text_allows_autoclose = selection.start.column == 0
4166 || snapshot
4167 .reversed_chars_at(selection.start)
4168 .next()
4169 .is_none_or(|c| {
4170 bracket_pair.start != bracket_pair.end
4171 || !snapshot
4172 .char_classifier_at(selection.start)
4173 .is_word(c)
4174 });
4175
4176 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4177 && bracket_pair.start.len() == 1
4178 {
4179 let target = bracket_pair.start.chars().next().unwrap();
4180 let current_line_count = snapshot
4181 .reversed_chars_at(selection.start)
4182 .take_while(|&c| c != '\n')
4183 .filter(|&c| c == target)
4184 .count();
4185 current_line_count % 2 == 1
4186 } else {
4187 false
4188 };
4189
4190 if autoclose
4191 && bracket_pair.close
4192 && following_text_allows_autoclose
4193 && preceding_text_allows_autoclose
4194 && !is_closing_quote
4195 {
4196 let anchor = snapshot.anchor_before(selection.end);
4197 new_selections.push((selection.map(|_| anchor), text.len()));
4198 new_autoclose_regions.push((
4199 anchor,
4200 text.len(),
4201 selection.id,
4202 bracket_pair.clone(),
4203 ));
4204 edits.push((
4205 selection.range(),
4206 format!("{}{}", text, bracket_pair.end).into(),
4207 ));
4208 bracket_inserted = true;
4209 continue;
4210 }
4211 }
4212
4213 if let Some(region) = autoclose_region {
4214 // If the selection is followed by an auto-inserted closing bracket,
4215 // then don't insert that closing bracket again; just move the selection
4216 // past the closing bracket.
4217 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4218 && text.as_ref() == region.pair.end.as_str()
4219 && snapshot.contains_str_at(region.range.end, text.as_ref());
4220 if should_skip {
4221 let anchor = snapshot.anchor_after(selection.end);
4222 new_selections
4223 .push((selection.map(|_| anchor), region.pair.end.len()));
4224 continue;
4225 }
4226 }
4227
4228 let always_treat_brackets_as_autoclosed = snapshot
4229 .language_settings_at(selection.start, cx)
4230 .always_treat_brackets_as_autoclosed;
4231 if always_treat_brackets_as_autoclosed
4232 && is_bracket_pair_end
4233 && snapshot.contains_str_at(selection.end, text.as_ref())
4234 {
4235 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4236 // and the inserted text is a closing bracket and the selection is followed
4237 // by the closing bracket then move the selection past the closing bracket.
4238 let anchor = snapshot.anchor_after(selection.end);
4239 new_selections.push((selection.map(|_| anchor), text.len()));
4240 continue;
4241 }
4242 }
4243 // If an opening bracket is 1 character long and is typed while
4244 // text is selected, then surround that text with the bracket pair.
4245 else if auto_surround
4246 && bracket_pair.surround
4247 && is_bracket_pair_start
4248 && bracket_pair.start.chars().count() == 1
4249 {
4250 edits.push((selection.start..selection.start, text.clone()));
4251 edits.push((
4252 selection.end..selection.end,
4253 bracket_pair.end.as_str().into(),
4254 ));
4255 bracket_inserted = true;
4256 new_selections.push((
4257 Selection {
4258 id: selection.id,
4259 start: snapshot.anchor_after(selection.start),
4260 end: snapshot.anchor_before(selection.end),
4261 reversed: selection.reversed,
4262 goal: selection.goal,
4263 },
4264 0,
4265 ));
4266 continue;
4267 }
4268 }
4269 }
4270
4271 if self.auto_replace_emoji_shortcode
4272 && selection.is_empty()
4273 && text.as_ref().ends_with(':')
4274 && let Some(possible_emoji_short_code) =
4275 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4276 && !possible_emoji_short_code.is_empty()
4277 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4278 {
4279 let emoji_shortcode_start = Point::new(
4280 selection.start.row,
4281 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4282 );
4283
4284 // Remove shortcode from buffer
4285 edits.push((
4286 emoji_shortcode_start..selection.start,
4287 "".to_string().into(),
4288 ));
4289 new_selections.push((
4290 Selection {
4291 id: selection.id,
4292 start: snapshot.anchor_after(emoji_shortcode_start),
4293 end: snapshot.anchor_before(selection.start),
4294 reversed: selection.reversed,
4295 goal: selection.goal,
4296 },
4297 0,
4298 ));
4299
4300 // Insert emoji
4301 let selection_start_anchor = snapshot.anchor_after(selection.start);
4302 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4303 edits.push((selection.start..selection.end, emoji.to_string().into()));
4304
4305 continue;
4306 }
4307
4308 // If not handling any auto-close operation, then just replace the selected
4309 // text with the given input and move the selection to the end of the
4310 // newly inserted text.
4311 let anchor = snapshot.anchor_after(selection.end);
4312 if !self.linked_edit_ranges.is_empty() {
4313 let start_anchor = snapshot.anchor_before(selection.start);
4314
4315 let is_word_char = text.chars().next().is_none_or(|char| {
4316 let classifier = snapshot
4317 .char_classifier_at(start_anchor.to_offset(&snapshot))
4318 .scope_context(Some(CharScopeContext::LinkedEdit));
4319 classifier.is_word(char)
4320 });
4321
4322 if is_word_char {
4323 if let Some(ranges) = self
4324 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4325 {
4326 for (buffer, edits) in ranges {
4327 linked_edits
4328 .entry(buffer.clone())
4329 .or_default()
4330 .extend(edits.into_iter().map(|range| (range, text.clone())));
4331 }
4332 }
4333 } else {
4334 clear_linked_edit_ranges = true;
4335 }
4336 }
4337
4338 new_selections.push((selection.map(|_| anchor), 0));
4339 edits.push((selection.start..selection.end, text.clone()));
4340 }
4341
4342 drop(snapshot);
4343
4344 self.transact(window, cx, |this, window, cx| {
4345 if clear_linked_edit_ranges {
4346 this.linked_edit_ranges.clear();
4347 }
4348 let initial_buffer_versions =
4349 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4350
4351 this.buffer.update(cx, |buffer, cx| {
4352 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4353 });
4354 for (buffer, edits) in linked_edits {
4355 buffer.update(cx, |buffer, cx| {
4356 let snapshot = buffer.snapshot();
4357 let edits = edits
4358 .into_iter()
4359 .map(|(range, text)| {
4360 use text::ToPoint as TP;
4361 let end_point = TP::to_point(&range.end, &snapshot);
4362 let start_point = TP::to_point(&range.start, &snapshot);
4363 (start_point..end_point, text)
4364 })
4365 .sorted_by_key(|(range, _)| range.start);
4366 buffer.edit(edits, None, cx);
4367 })
4368 }
4369 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4370 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4371 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4372 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4373 .zip(new_selection_deltas)
4374 .map(|(selection, delta)| Selection {
4375 id: selection.id,
4376 start: selection.start + delta,
4377 end: selection.end + delta,
4378 reversed: selection.reversed,
4379 goal: SelectionGoal::None,
4380 })
4381 .collect::<Vec<_>>();
4382
4383 let mut i = 0;
4384 for (position, delta, selection_id, pair) in new_autoclose_regions {
4385 let position = position.to_offset(map.buffer_snapshot()) + delta;
4386 let start = map.buffer_snapshot().anchor_before(position);
4387 let end = map.buffer_snapshot().anchor_after(position);
4388 while let Some(existing_state) = this.autoclose_regions.get(i) {
4389 match existing_state
4390 .range
4391 .start
4392 .cmp(&start, map.buffer_snapshot())
4393 {
4394 Ordering::Less => i += 1,
4395 Ordering::Greater => break,
4396 Ordering::Equal => {
4397 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4398 Ordering::Less => i += 1,
4399 Ordering::Equal => break,
4400 Ordering::Greater => break,
4401 }
4402 }
4403 }
4404 }
4405 this.autoclose_regions.insert(
4406 i,
4407 AutocloseRegion {
4408 selection_id,
4409 range: start..end,
4410 pair,
4411 },
4412 );
4413 }
4414
4415 let had_active_edit_prediction = this.has_active_edit_prediction();
4416 this.change_selections(
4417 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4418 window,
4419 cx,
4420 |s| s.select(new_selections),
4421 );
4422
4423 if !bracket_inserted
4424 && let Some(on_type_format_task) =
4425 this.trigger_on_type_formatting(text.to_string(), window, cx)
4426 {
4427 on_type_format_task.detach_and_log_err(cx);
4428 }
4429
4430 let editor_settings = EditorSettings::get_global(cx);
4431 if bracket_inserted
4432 && (editor_settings.auto_signature_help
4433 || editor_settings.show_signature_help_after_edits)
4434 {
4435 this.show_signature_help(&ShowSignatureHelp, window, cx);
4436 }
4437
4438 let trigger_in_words =
4439 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4440 if this.hard_wrap.is_some() {
4441 let latest: Range<Point> = this.selections.newest(&map).range();
4442 if latest.is_empty()
4443 && this
4444 .buffer()
4445 .read(cx)
4446 .snapshot(cx)
4447 .line_len(MultiBufferRow(latest.start.row))
4448 == latest.start.column
4449 {
4450 this.rewrap_impl(
4451 RewrapOptions {
4452 override_language_settings: true,
4453 preserve_existing_whitespace: true,
4454 },
4455 cx,
4456 )
4457 }
4458 }
4459 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4460 refresh_linked_ranges(this, window, cx);
4461 this.refresh_edit_prediction(true, false, window, cx);
4462 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4463 });
4464 }
4465
4466 fn find_possible_emoji_shortcode_at_position(
4467 snapshot: &MultiBufferSnapshot,
4468 position: Point,
4469 ) -> Option<String> {
4470 let mut chars = Vec::new();
4471 let mut found_colon = false;
4472 for char in snapshot.reversed_chars_at(position).take(100) {
4473 // Found a possible emoji shortcode in the middle of the buffer
4474 if found_colon {
4475 if char.is_whitespace() {
4476 chars.reverse();
4477 return Some(chars.iter().collect());
4478 }
4479 // If the previous character is not a whitespace, we are in the middle of a word
4480 // and we only want to complete the shortcode if the word is made up of other emojis
4481 let mut containing_word = String::new();
4482 for ch in snapshot
4483 .reversed_chars_at(position)
4484 .skip(chars.len() + 1)
4485 .take(100)
4486 {
4487 if ch.is_whitespace() {
4488 break;
4489 }
4490 containing_word.push(ch);
4491 }
4492 let containing_word = containing_word.chars().rev().collect::<String>();
4493 if util::word_consists_of_emojis(containing_word.as_str()) {
4494 chars.reverse();
4495 return Some(chars.iter().collect());
4496 }
4497 }
4498
4499 if char.is_whitespace() || !char.is_ascii() {
4500 return None;
4501 }
4502 if char == ':' {
4503 found_colon = true;
4504 } else {
4505 chars.push(char);
4506 }
4507 }
4508 // Found a possible emoji shortcode at the beginning of the buffer
4509 chars.reverse();
4510 Some(chars.iter().collect())
4511 }
4512
4513 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4514 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4515 self.transact(window, cx, |this, window, cx| {
4516 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4517 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4518 let multi_buffer = this.buffer.read(cx);
4519 let buffer = multi_buffer.snapshot(cx);
4520 selections
4521 .iter()
4522 .map(|selection| {
4523 let start_point = selection.start.to_point(&buffer);
4524 let mut existing_indent =
4525 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4526 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4527 let start = selection.start;
4528 let end = selection.end;
4529 let selection_is_empty = start == end;
4530 let language_scope = buffer.language_scope_at(start);
4531 let (
4532 comment_delimiter,
4533 doc_delimiter,
4534 insert_extra_newline,
4535 indent_on_newline,
4536 indent_on_extra_newline,
4537 ) = if let Some(language) = &language_scope {
4538 let mut insert_extra_newline =
4539 insert_extra_newline_brackets(&buffer, start..end, language)
4540 || insert_extra_newline_tree_sitter(&buffer, start..end);
4541
4542 // Comment extension on newline is allowed only for cursor selections
4543 let comment_delimiter = maybe!({
4544 if !selection_is_empty {
4545 return None;
4546 }
4547
4548 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4549 return None;
4550 }
4551
4552 let delimiters = language.line_comment_prefixes();
4553 let max_len_of_delimiter =
4554 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4555 let (snapshot, range) =
4556 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4557
4558 let num_of_whitespaces = snapshot
4559 .chars_for_range(range.clone())
4560 .take_while(|c| c.is_whitespace())
4561 .count();
4562 let comment_candidate = snapshot
4563 .chars_for_range(range.clone())
4564 .skip(num_of_whitespaces)
4565 .take(max_len_of_delimiter)
4566 .collect::<String>();
4567 let (delimiter, trimmed_len) = delimiters
4568 .iter()
4569 .filter_map(|delimiter| {
4570 let prefix = delimiter.trim_end();
4571 if comment_candidate.starts_with(prefix) {
4572 Some((delimiter, prefix.len()))
4573 } else {
4574 None
4575 }
4576 })
4577 .max_by_key(|(_, len)| *len)?;
4578
4579 if let Some(BlockCommentConfig {
4580 start: block_start, ..
4581 }) = language.block_comment()
4582 {
4583 let block_start_trimmed = block_start.trim_end();
4584 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4585 let line_content = snapshot
4586 .chars_for_range(range)
4587 .skip(num_of_whitespaces)
4588 .take(block_start_trimmed.len())
4589 .collect::<String>();
4590
4591 if line_content.starts_with(block_start_trimmed) {
4592 return None;
4593 }
4594 }
4595 }
4596
4597 let cursor_is_placed_after_comment_marker =
4598 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4599 if cursor_is_placed_after_comment_marker {
4600 Some(delimiter.clone())
4601 } else {
4602 None
4603 }
4604 });
4605
4606 let mut indent_on_newline = IndentSize::spaces(0);
4607 let mut indent_on_extra_newline = IndentSize::spaces(0);
4608
4609 let doc_delimiter = maybe!({
4610 if !selection_is_empty {
4611 return None;
4612 }
4613
4614 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4615 return None;
4616 }
4617
4618 let BlockCommentConfig {
4619 start: start_tag,
4620 end: end_tag,
4621 prefix: delimiter,
4622 tab_size: len,
4623 } = language.documentation_comment()?;
4624 let is_within_block_comment = buffer
4625 .language_scope_at(start_point)
4626 .is_some_and(|scope| scope.override_name() == Some("comment"));
4627 if !is_within_block_comment {
4628 return None;
4629 }
4630
4631 let (snapshot, range) =
4632 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4633
4634 let num_of_whitespaces = snapshot
4635 .chars_for_range(range.clone())
4636 .take_while(|c| c.is_whitespace())
4637 .count();
4638
4639 // 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.
4640 let column = start_point.column;
4641 let cursor_is_after_start_tag = {
4642 let start_tag_len = start_tag.len();
4643 let start_tag_line = snapshot
4644 .chars_for_range(range.clone())
4645 .skip(num_of_whitespaces)
4646 .take(start_tag_len)
4647 .collect::<String>();
4648 if start_tag_line.starts_with(start_tag.as_ref()) {
4649 num_of_whitespaces + start_tag_len <= column as usize
4650 } else {
4651 false
4652 }
4653 };
4654
4655 let cursor_is_after_delimiter = {
4656 let delimiter_trim = delimiter.trim_end();
4657 let delimiter_line = snapshot
4658 .chars_for_range(range.clone())
4659 .skip(num_of_whitespaces)
4660 .take(delimiter_trim.len())
4661 .collect::<String>();
4662 if delimiter_line.starts_with(delimiter_trim) {
4663 num_of_whitespaces + delimiter_trim.len() <= column as usize
4664 } else {
4665 false
4666 }
4667 };
4668
4669 let cursor_is_before_end_tag_if_exists = {
4670 let mut char_position = 0u32;
4671 let mut end_tag_offset = None;
4672
4673 'outer: for chunk in snapshot.text_for_range(range) {
4674 if let Some(byte_pos) = chunk.find(&**end_tag) {
4675 let chars_before_match =
4676 chunk[..byte_pos].chars().count() as u32;
4677 end_tag_offset =
4678 Some(char_position + chars_before_match);
4679 break 'outer;
4680 }
4681 char_position += chunk.chars().count() as u32;
4682 }
4683
4684 if let Some(end_tag_offset) = end_tag_offset {
4685 let cursor_is_before_end_tag = column <= end_tag_offset;
4686 if cursor_is_after_start_tag {
4687 if cursor_is_before_end_tag {
4688 insert_extra_newline = true;
4689 }
4690 let cursor_is_at_start_of_end_tag =
4691 column == end_tag_offset;
4692 if cursor_is_at_start_of_end_tag {
4693 indent_on_extra_newline.len = *len;
4694 }
4695 }
4696 cursor_is_before_end_tag
4697 } else {
4698 true
4699 }
4700 };
4701
4702 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4703 && cursor_is_before_end_tag_if_exists
4704 {
4705 if cursor_is_after_start_tag {
4706 indent_on_newline.len = *len;
4707 }
4708 Some(delimiter.clone())
4709 } else {
4710 None
4711 }
4712 });
4713
4714 (
4715 comment_delimiter,
4716 doc_delimiter,
4717 insert_extra_newline,
4718 indent_on_newline,
4719 indent_on_extra_newline,
4720 )
4721 } else {
4722 (
4723 None,
4724 None,
4725 false,
4726 IndentSize::default(),
4727 IndentSize::default(),
4728 )
4729 };
4730
4731 let prevent_auto_indent = doc_delimiter.is_some();
4732 let delimiter = comment_delimiter.or(doc_delimiter);
4733
4734 let capacity_for_delimiter =
4735 delimiter.as_deref().map(str::len).unwrap_or_default();
4736 let mut new_text = String::with_capacity(
4737 1 + capacity_for_delimiter
4738 + existing_indent.len as usize
4739 + indent_on_newline.len as usize
4740 + indent_on_extra_newline.len as usize,
4741 );
4742 new_text.push('\n');
4743 new_text.extend(existing_indent.chars());
4744 new_text.extend(indent_on_newline.chars());
4745
4746 if let Some(delimiter) = &delimiter {
4747 new_text.push_str(delimiter);
4748 }
4749
4750 if insert_extra_newline {
4751 new_text.push('\n');
4752 new_text.extend(existing_indent.chars());
4753 new_text.extend(indent_on_extra_newline.chars());
4754 }
4755
4756 let anchor = buffer.anchor_after(end);
4757 let new_selection = selection.map(|_| anchor);
4758 (
4759 ((start..end, new_text), prevent_auto_indent),
4760 (insert_extra_newline, new_selection),
4761 )
4762 })
4763 .unzip()
4764 };
4765
4766 let mut auto_indent_edits = Vec::new();
4767 let mut edits = Vec::new();
4768 for (edit, prevent_auto_indent) in edits_with_flags {
4769 if prevent_auto_indent {
4770 edits.push(edit);
4771 } else {
4772 auto_indent_edits.push(edit);
4773 }
4774 }
4775 if !edits.is_empty() {
4776 this.edit(edits, cx);
4777 }
4778 if !auto_indent_edits.is_empty() {
4779 this.edit_with_autoindent(auto_indent_edits, cx);
4780 }
4781
4782 let buffer = this.buffer.read(cx).snapshot(cx);
4783 let new_selections = selection_info
4784 .into_iter()
4785 .map(|(extra_newline_inserted, new_selection)| {
4786 let mut cursor = new_selection.end.to_point(&buffer);
4787 if extra_newline_inserted {
4788 cursor.row -= 1;
4789 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4790 }
4791 new_selection.map(|_| cursor)
4792 })
4793 .collect();
4794
4795 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4796 this.refresh_edit_prediction(true, false, window, cx);
4797 });
4798 }
4799
4800 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4801 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4802
4803 let buffer = self.buffer.read(cx);
4804 let snapshot = buffer.snapshot(cx);
4805
4806 let mut edits = Vec::new();
4807 let mut rows = Vec::new();
4808
4809 for (rows_inserted, selection) in self
4810 .selections
4811 .all_adjusted(&self.display_snapshot(cx))
4812 .into_iter()
4813 .enumerate()
4814 {
4815 let cursor = selection.head();
4816 let row = cursor.row;
4817
4818 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4819
4820 let newline = "\n".to_string();
4821 edits.push((start_of_line..start_of_line, newline));
4822
4823 rows.push(row + rows_inserted as u32);
4824 }
4825
4826 self.transact(window, cx, |editor, window, cx| {
4827 editor.edit(edits, cx);
4828
4829 editor.change_selections(Default::default(), window, cx, |s| {
4830 let mut index = 0;
4831 s.move_cursors_with(|map, _, _| {
4832 let row = rows[index];
4833 index += 1;
4834
4835 let point = Point::new(row, 0);
4836 let boundary = map.next_line_boundary(point).1;
4837 let clipped = map.clip_point(boundary, Bias::Left);
4838
4839 (clipped, SelectionGoal::None)
4840 });
4841 });
4842
4843 let mut indent_edits = Vec::new();
4844 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4845 for row in rows {
4846 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4847 for (row, indent) in indents {
4848 if indent.len == 0 {
4849 continue;
4850 }
4851
4852 let text = match indent.kind {
4853 IndentKind::Space => " ".repeat(indent.len as usize),
4854 IndentKind::Tab => "\t".repeat(indent.len as usize),
4855 };
4856 let point = Point::new(row.0, 0);
4857 indent_edits.push((point..point, text));
4858 }
4859 }
4860 editor.edit(indent_edits, cx);
4861 });
4862 }
4863
4864 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4865 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4866
4867 let buffer = self.buffer.read(cx);
4868 let snapshot = buffer.snapshot(cx);
4869
4870 let mut edits = Vec::new();
4871 let mut rows = Vec::new();
4872 let mut rows_inserted = 0;
4873
4874 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4875 let cursor = selection.head();
4876 let row = cursor.row;
4877
4878 let point = Point::new(row + 1, 0);
4879 let start_of_line = snapshot.clip_point(point, Bias::Left);
4880
4881 let newline = "\n".to_string();
4882 edits.push((start_of_line..start_of_line, newline));
4883
4884 rows_inserted += 1;
4885 rows.push(row + rows_inserted);
4886 }
4887
4888 self.transact(window, cx, |editor, window, cx| {
4889 editor.edit(edits, cx);
4890
4891 editor.change_selections(Default::default(), window, cx, |s| {
4892 let mut index = 0;
4893 s.move_cursors_with(|map, _, _| {
4894 let row = rows[index];
4895 index += 1;
4896
4897 let point = Point::new(row, 0);
4898 let boundary = map.next_line_boundary(point).1;
4899 let clipped = map.clip_point(boundary, Bias::Left);
4900
4901 (clipped, SelectionGoal::None)
4902 });
4903 });
4904
4905 let mut indent_edits = Vec::new();
4906 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4907 for row in rows {
4908 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4909 for (row, indent) in indents {
4910 if indent.len == 0 {
4911 continue;
4912 }
4913
4914 let text = match indent.kind {
4915 IndentKind::Space => " ".repeat(indent.len as usize),
4916 IndentKind::Tab => "\t".repeat(indent.len as usize),
4917 };
4918 let point = Point::new(row.0, 0);
4919 indent_edits.push((point..point, text));
4920 }
4921 }
4922 editor.edit(indent_edits, cx);
4923 });
4924 }
4925
4926 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4927 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4928 original_indent_columns: Vec::new(),
4929 });
4930 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4931 }
4932
4933 fn insert_with_autoindent_mode(
4934 &mut self,
4935 text: &str,
4936 autoindent_mode: Option<AutoindentMode>,
4937 window: &mut Window,
4938 cx: &mut Context<Self>,
4939 ) {
4940 if self.read_only(cx) {
4941 return;
4942 }
4943
4944 let text: Arc<str> = text.into();
4945 self.transact(window, cx, |this, window, cx| {
4946 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4947 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4948 let anchors = {
4949 let snapshot = buffer.read(cx);
4950 old_selections
4951 .iter()
4952 .map(|s| {
4953 let anchor = snapshot.anchor_after(s.head());
4954 s.map(|_| anchor)
4955 })
4956 .collect::<Vec<_>>()
4957 };
4958 buffer.edit(
4959 old_selections
4960 .iter()
4961 .map(|s| (s.start..s.end, text.clone())),
4962 autoindent_mode,
4963 cx,
4964 );
4965 anchors
4966 });
4967
4968 this.change_selections(Default::default(), window, cx, |s| {
4969 s.select_anchors(selection_anchors);
4970 });
4971
4972 cx.notify();
4973 });
4974 }
4975
4976 fn trigger_completion_on_input(
4977 &mut self,
4978 text: &str,
4979 trigger_in_words: bool,
4980 window: &mut Window,
4981 cx: &mut Context<Self>,
4982 ) {
4983 let completions_source = self
4984 .context_menu
4985 .borrow()
4986 .as_ref()
4987 .and_then(|menu| match menu {
4988 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4989 CodeContextMenu::CodeActions(_) => None,
4990 });
4991
4992 match completions_source {
4993 Some(CompletionsMenuSource::Words { .. }) => {
4994 self.open_or_update_completions_menu(
4995 Some(CompletionsMenuSource::Words {
4996 ignore_threshold: false,
4997 }),
4998 None,
4999 window,
5000 cx,
5001 );
5002 }
5003 Some(CompletionsMenuSource::Normal)
5004 | Some(CompletionsMenuSource::SnippetChoices)
5005 | None
5006 if self.is_completion_trigger(
5007 text,
5008 trigger_in_words,
5009 completions_source.is_some(),
5010 cx,
5011 ) =>
5012 {
5013 self.show_completions(
5014 &ShowCompletions {
5015 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
5016 },
5017 window,
5018 cx,
5019 )
5020 }
5021 _ => {
5022 self.hide_context_menu(window, cx);
5023 }
5024 }
5025 }
5026
5027 fn is_completion_trigger(
5028 &self,
5029 text: &str,
5030 trigger_in_words: bool,
5031 menu_is_open: bool,
5032 cx: &mut Context<Self>,
5033 ) -> bool {
5034 let position = self.selections.newest_anchor().head();
5035 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5036 return false;
5037 };
5038
5039 if let Some(completion_provider) = &self.completion_provider {
5040 completion_provider.is_completion_trigger(
5041 &buffer,
5042 position.text_anchor,
5043 text,
5044 trigger_in_words,
5045 menu_is_open,
5046 cx,
5047 )
5048 } else {
5049 false
5050 }
5051 }
5052
5053 /// If any empty selections is touching the start of its innermost containing autoclose
5054 /// region, expand it to select the brackets.
5055 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5056 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5057 let buffer = self.buffer.read(cx).read(cx);
5058 let new_selections = self
5059 .selections_with_autoclose_regions(selections, &buffer)
5060 .map(|(mut selection, region)| {
5061 if !selection.is_empty() {
5062 return selection;
5063 }
5064
5065 if let Some(region) = region {
5066 let mut range = region.range.to_offset(&buffer);
5067 if selection.start == range.start && range.start >= region.pair.start.len() {
5068 range.start -= region.pair.start.len();
5069 if buffer.contains_str_at(range.start, ®ion.pair.start)
5070 && buffer.contains_str_at(range.end, ®ion.pair.end)
5071 {
5072 range.end += region.pair.end.len();
5073 selection.start = range.start;
5074 selection.end = range.end;
5075
5076 return selection;
5077 }
5078 }
5079 }
5080
5081 let always_treat_brackets_as_autoclosed = buffer
5082 .language_settings_at(selection.start, cx)
5083 .always_treat_brackets_as_autoclosed;
5084
5085 if !always_treat_brackets_as_autoclosed {
5086 return selection;
5087 }
5088
5089 if let Some(scope) = buffer.language_scope_at(selection.start) {
5090 for (pair, enabled) in scope.brackets() {
5091 if !enabled || !pair.close {
5092 continue;
5093 }
5094
5095 if buffer.contains_str_at(selection.start, &pair.end) {
5096 let pair_start_len = pair.start.len();
5097 if buffer.contains_str_at(
5098 selection.start.saturating_sub(pair_start_len),
5099 &pair.start,
5100 ) {
5101 selection.start -= pair_start_len;
5102 selection.end += pair.end.len();
5103
5104 return selection;
5105 }
5106 }
5107 }
5108 }
5109
5110 selection
5111 })
5112 .collect();
5113
5114 drop(buffer);
5115 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5116 selections.select(new_selections)
5117 });
5118 }
5119
5120 /// Iterate the given selections, and for each one, find the smallest surrounding
5121 /// autoclose region. This uses the ordering of the selections and the autoclose
5122 /// regions to avoid repeated comparisons.
5123 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5124 &'a self,
5125 selections: impl IntoIterator<Item = Selection<D>>,
5126 buffer: &'a MultiBufferSnapshot,
5127 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5128 let mut i = 0;
5129 let mut regions = self.autoclose_regions.as_slice();
5130 selections.into_iter().map(move |selection| {
5131 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5132
5133 let mut enclosing = None;
5134 while let Some(pair_state) = regions.get(i) {
5135 if pair_state.range.end.to_offset(buffer) < range.start {
5136 regions = ®ions[i + 1..];
5137 i = 0;
5138 } else if pair_state.range.start.to_offset(buffer) > range.end {
5139 break;
5140 } else {
5141 if pair_state.selection_id == selection.id {
5142 enclosing = Some(pair_state);
5143 }
5144 i += 1;
5145 }
5146 }
5147
5148 (selection, enclosing)
5149 })
5150 }
5151
5152 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5153 fn invalidate_autoclose_regions(
5154 &mut self,
5155 mut selections: &[Selection<Anchor>],
5156 buffer: &MultiBufferSnapshot,
5157 ) {
5158 self.autoclose_regions.retain(|state| {
5159 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5160 return false;
5161 }
5162
5163 let mut i = 0;
5164 while let Some(selection) = selections.get(i) {
5165 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5166 selections = &selections[1..];
5167 continue;
5168 }
5169 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5170 break;
5171 }
5172 if selection.id == state.selection_id {
5173 return true;
5174 } else {
5175 i += 1;
5176 }
5177 }
5178 false
5179 });
5180 }
5181
5182 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5183 let offset = position.to_offset(buffer);
5184 let (word_range, kind) =
5185 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5186 if offset > word_range.start && kind == Some(CharKind::Word) {
5187 Some(
5188 buffer
5189 .text_for_range(word_range.start..offset)
5190 .collect::<String>(),
5191 )
5192 } else {
5193 None
5194 }
5195 }
5196
5197 pub fn toggle_inline_values(
5198 &mut self,
5199 _: &ToggleInlineValues,
5200 _: &mut Window,
5201 cx: &mut Context<Self>,
5202 ) {
5203 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5204
5205 self.refresh_inline_values(cx);
5206 }
5207
5208 pub fn toggle_inlay_hints(
5209 &mut self,
5210 _: &ToggleInlayHints,
5211 _: &mut Window,
5212 cx: &mut Context<Self>,
5213 ) {
5214 self.refresh_inlay_hints(
5215 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5216 cx,
5217 );
5218 }
5219
5220 pub fn inlay_hints_enabled(&self) -> bool {
5221 self.inlay_hint_cache.enabled
5222 }
5223
5224 pub fn inline_values_enabled(&self) -> bool {
5225 self.inline_value_cache.enabled
5226 }
5227
5228 #[cfg(any(test, feature = "test-support"))]
5229 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5230 self.display_map
5231 .read(cx)
5232 .current_inlays()
5233 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5234 .cloned()
5235 .collect()
5236 }
5237
5238 #[cfg(any(test, feature = "test-support"))]
5239 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5240 self.display_map
5241 .read(cx)
5242 .current_inlays()
5243 .cloned()
5244 .collect()
5245 }
5246
5247 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5248 if self.semantics_provider.is_none() || !self.mode.is_full() {
5249 return;
5250 }
5251
5252 let reason_description = reason.description();
5253 let ignore_debounce = matches!(
5254 reason,
5255 InlayHintRefreshReason::SettingsChange(_)
5256 | InlayHintRefreshReason::Toggle(_)
5257 | InlayHintRefreshReason::ExcerptsRemoved(_)
5258 | InlayHintRefreshReason::ModifiersChanged(_)
5259 );
5260 let (invalidate_cache, required_languages) = match reason {
5261 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5262 match self.inlay_hint_cache.modifiers_override(enabled) {
5263 Some(enabled) => {
5264 if enabled {
5265 (InvalidationStrategy::RefreshRequested, None)
5266 } else {
5267 self.clear_inlay_hints(cx);
5268 return;
5269 }
5270 }
5271 None => return,
5272 }
5273 }
5274 InlayHintRefreshReason::Toggle(enabled) => {
5275 if self.inlay_hint_cache.toggle(enabled) {
5276 if enabled {
5277 (InvalidationStrategy::RefreshRequested, None)
5278 } else {
5279 self.clear_inlay_hints(cx);
5280 return;
5281 }
5282 } else {
5283 return;
5284 }
5285 }
5286 InlayHintRefreshReason::SettingsChange(new_settings) => {
5287 match self.inlay_hint_cache.update_settings(
5288 &self.buffer,
5289 new_settings,
5290 self.visible_inlay_hints(cx).cloned().collect::<Vec<_>>(),
5291 cx,
5292 ) {
5293 ControlFlow::Break(Some(InlaySplice {
5294 to_remove,
5295 to_insert,
5296 })) => {
5297 self.splice_inlays(&to_remove, to_insert, cx);
5298 return;
5299 }
5300 ControlFlow::Break(None) => return,
5301 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5302 }
5303 }
5304 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5305 if let Some(InlaySplice {
5306 to_remove,
5307 to_insert,
5308 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5309 {
5310 self.splice_inlays(&to_remove, to_insert, cx);
5311 }
5312 self.display_map.update(cx, |display_map, cx| {
5313 display_map.remove_inlays_for_excerpts(&excerpts_removed, cx)
5314 });
5315 return;
5316 }
5317 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5318 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5319 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5320 }
5321 InlayHintRefreshReason::RefreshRequested => {
5322 (InvalidationStrategy::RefreshRequested, None)
5323 }
5324 };
5325
5326 let mut visible_excerpts = self.visible_excerpts(required_languages.as_ref(), cx);
5327 visible_excerpts.retain(|_, (buffer, _, _)| {
5328 self.registered_buffers
5329 .contains_key(&buffer.read(cx).remote_id())
5330 });
5331
5332 if let Some(InlaySplice {
5333 to_remove,
5334 to_insert,
5335 }) = self.inlay_hint_cache.spawn_hint_refresh(
5336 reason_description,
5337 visible_excerpts,
5338 invalidate_cache,
5339 ignore_debounce,
5340 cx,
5341 ) {
5342 self.splice_inlays(&to_remove, to_insert, cx);
5343 }
5344 }
5345
5346 pub fn clear_inlay_hints(&self, cx: &mut Context<Editor>) {
5347 self.splice_inlays(
5348 &self
5349 .visible_inlay_hints(cx)
5350 .map(|inlay| inlay.id)
5351 .collect::<Vec<_>>(),
5352 Vec::new(),
5353 cx,
5354 );
5355 }
5356
5357 fn visible_inlay_hints<'a>(
5358 &'a self,
5359 cx: &'a Context<Editor>,
5360 ) -> impl Iterator<Item = &'a Inlay> {
5361 self.display_map
5362 .read(cx)
5363 .current_inlays()
5364 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5365 }
5366
5367 pub fn visible_excerpts(
5368 &self,
5369 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5370 cx: &mut Context<Editor>,
5371 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5372 let Some(project) = self.project() else {
5373 return HashMap::default();
5374 };
5375 let project = project.read(cx);
5376 let multi_buffer = self.buffer().read(cx);
5377 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5378 let multi_buffer_visible_start = self
5379 .scroll_manager
5380 .anchor()
5381 .anchor
5382 .to_point(&multi_buffer_snapshot);
5383 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5384 multi_buffer_visible_start
5385 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5386 Bias::Left,
5387 );
5388 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5389 multi_buffer_snapshot
5390 .range_to_buffer_ranges(multi_buffer_visible_range)
5391 .into_iter()
5392 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5393 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5394 let buffer_file = project::File::from_dyn(buffer.file())?;
5395 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5396 let worktree_entry = buffer_worktree
5397 .read(cx)
5398 .entry_for_id(buffer_file.project_entry_id()?)?;
5399 if worktree_entry.is_ignored {
5400 return None;
5401 }
5402
5403 let language = buffer.language()?;
5404 if let Some(restrict_to_languages) = restrict_to_languages
5405 && !restrict_to_languages.contains(language)
5406 {
5407 return None;
5408 }
5409 Some((
5410 excerpt_id,
5411 (
5412 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5413 buffer.version().clone(),
5414 excerpt_visible_range,
5415 ),
5416 ))
5417 })
5418 .collect()
5419 }
5420
5421 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5422 TextLayoutDetails {
5423 text_system: window.text_system().clone(),
5424 editor_style: self.style.clone().unwrap(),
5425 rem_size: window.rem_size(),
5426 scroll_anchor: self.scroll_manager.anchor(),
5427 visible_rows: self.visible_line_count(),
5428 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5429 }
5430 }
5431
5432 pub fn splice_inlays(
5433 &self,
5434 to_remove: &[InlayId],
5435 to_insert: Vec<Inlay>,
5436 cx: &mut Context<Self>,
5437 ) {
5438 self.display_map.update(cx, |display_map, cx| {
5439 display_map.splice_inlays(to_remove, to_insert, cx)
5440 });
5441 cx.notify();
5442 }
5443
5444 fn trigger_on_type_formatting(
5445 &self,
5446 input: String,
5447 window: &mut Window,
5448 cx: &mut Context<Self>,
5449 ) -> Option<Task<Result<()>>> {
5450 if input.len() != 1 {
5451 return None;
5452 }
5453
5454 let project = self.project()?;
5455 let position = self.selections.newest_anchor().head();
5456 let (buffer, buffer_position) = self
5457 .buffer
5458 .read(cx)
5459 .text_anchor_for_position(position, cx)?;
5460
5461 let settings = language_settings::language_settings(
5462 buffer
5463 .read(cx)
5464 .language_at(buffer_position)
5465 .map(|l| l.name()),
5466 buffer.read(cx).file(),
5467 cx,
5468 );
5469 if !settings.use_on_type_format {
5470 return None;
5471 }
5472
5473 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5474 // hence we do LSP request & edit on host side only — add formats to host's history.
5475 let push_to_lsp_host_history = true;
5476 // If this is not the host, append its history with new edits.
5477 let push_to_client_history = project.read(cx).is_via_collab();
5478
5479 let on_type_formatting = project.update(cx, |project, cx| {
5480 project.on_type_format(
5481 buffer.clone(),
5482 buffer_position,
5483 input,
5484 push_to_lsp_host_history,
5485 cx,
5486 )
5487 });
5488 Some(cx.spawn_in(window, async move |editor, cx| {
5489 if let Some(transaction) = on_type_formatting.await? {
5490 if push_to_client_history {
5491 buffer
5492 .update(cx, |buffer, _| {
5493 buffer.push_transaction(transaction, Instant::now());
5494 buffer.finalize_last_transaction();
5495 })
5496 .ok();
5497 }
5498 editor.update(cx, |editor, cx| {
5499 editor.refresh_document_highlights(cx);
5500 })?;
5501 }
5502 Ok(())
5503 }))
5504 }
5505
5506 pub fn show_word_completions(
5507 &mut self,
5508 _: &ShowWordCompletions,
5509 window: &mut Window,
5510 cx: &mut Context<Self>,
5511 ) {
5512 self.open_or_update_completions_menu(
5513 Some(CompletionsMenuSource::Words {
5514 ignore_threshold: true,
5515 }),
5516 None,
5517 window,
5518 cx,
5519 );
5520 }
5521
5522 pub fn show_completions(
5523 &mut self,
5524 options: &ShowCompletions,
5525 window: &mut Window,
5526 cx: &mut Context<Self>,
5527 ) {
5528 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5529 }
5530
5531 fn open_or_update_completions_menu(
5532 &mut self,
5533 requested_source: Option<CompletionsMenuSource>,
5534 trigger: Option<&str>,
5535 window: &mut Window,
5536 cx: &mut Context<Self>,
5537 ) {
5538 if self.pending_rename.is_some() {
5539 return;
5540 }
5541
5542 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5543
5544 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5545 // inserted and selected. To handle that case, the start of the selection is used so that
5546 // the menu starts with all choices.
5547 let position = self
5548 .selections
5549 .newest_anchor()
5550 .start
5551 .bias_right(&multibuffer_snapshot);
5552 if position.diff_base_anchor.is_some() {
5553 return;
5554 }
5555 let buffer_position = multibuffer_snapshot.anchor_before(position);
5556 let Some(buffer) = buffer_position
5557 .buffer_id
5558 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5559 else {
5560 return;
5561 };
5562 let buffer_snapshot = buffer.read(cx).snapshot();
5563
5564 let query: Option<Arc<String>> =
5565 Self::completion_query(&multibuffer_snapshot, buffer_position)
5566 .map(|query| query.into());
5567
5568 drop(multibuffer_snapshot);
5569
5570 // Hide the current completions menu when query is empty. Without this, cached
5571 // completions from before the trigger char may be reused (#32774).
5572 if query.is_none() {
5573 let menu_is_open = matches!(
5574 self.context_menu.borrow().as_ref(),
5575 Some(CodeContextMenu::Completions(_))
5576 );
5577 if menu_is_open {
5578 self.hide_context_menu(window, cx);
5579 }
5580 }
5581
5582 let mut ignore_word_threshold = false;
5583 let provider = match requested_source {
5584 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5585 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5586 ignore_word_threshold = ignore_threshold;
5587 None
5588 }
5589 Some(CompletionsMenuSource::SnippetChoices) => {
5590 log::error!("bug: SnippetChoices requested_source is not handled");
5591 None
5592 }
5593 };
5594
5595 let sort_completions = provider
5596 .as_ref()
5597 .is_some_and(|provider| provider.sort_completions());
5598
5599 let filter_completions = provider
5600 .as_ref()
5601 .is_none_or(|provider| provider.filter_completions());
5602
5603 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5604 if filter_completions {
5605 menu.filter(query.clone(), provider.clone(), window, cx);
5606 }
5607 // When `is_incomplete` is false, no need to re-query completions when the current query
5608 // is a suffix of the initial query.
5609 if !menu.is_incomplete {
5610 // If the new query is a suffix of the old query (typing more characters) and
5611 // the previous result was complete, the existing completions can be filtered.
5612 //
5613 // Note that this is always true for snippet completions.
5614 let query_matches = match (&menu.initial_query, &query) {
5615 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5616 (None, _) => true,
5617 _ => false,
5618 };
5619 if query_matches {
5620 let position_matches = if menu.initial_position == position {
5621 true
5622 } else {
5623 let snapshot = self.buffer.read(cx).read(cx);
5624 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5625 };
5626 if position_matches {
5627 return;
5628 }
5629 }
5630 }
5631 };
5632
5633 let trigger_kind = match trigger {
5634 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5635 CompletionTriggerKind::TRIGGER_CHARACTER
5636 }
5637 _ => CompletionTriggerKind::INVOKED,
5638 };
5639 let completion_context = CompletionContext {
5640 trigger_character: trigger.and_then(|trigger| {
5641 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5642 Some(String::from(trigger))
5643 } else {
5644 None
5645 }
5646 }),
5647 trigger_kind,
5648 };
5649
5650 let Anchor {
5651 excerpt_id: buffer_excerpt_id,
5652 text_anchor: buffer_position,
5653 ..
5654 } = buffer_position;
5655
5656 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5657 buffer_snapshot.surrounding_word(buffer_position, None)
5658 {
5659 let word_to_exclude = buffer_snapshot
5660 .text_for_range(word_range.clone())
5661 .collect::<String>();
5662 (
5663 buffer_snapshot.anchor_before(word_range.start)
5664 ..buffer_snapshot.anchor_after(buffer_position),
5665 Some(word_to_exclude),
5666 )
5667 } else {
5668 (buffer_position..buffer_position, None)
5669 };
5670
5671 let language = buffer_snapshot
5672 .language_at(buffer_position)
5673 .map(|language| language.name());
5674
5675 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5676 .completions
5677 .clone();
5678
5679 let show_completion_documentation = buffer_snapshot
5680 .settings_at(buffer_position, cx)
5681 .show_completion_documentation;
5682
5683 // The document can be large, so stay in reasonable bounds when searching for words,
5684 // otherwise completion pop-up might be slow to appear.
5685 const WORD_LOOKUP_ROWS: u32 = 5_000;
5686 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5687 let min_word_search = buffer_snapshot.clip_point(
5688 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5689 Bias::Left,
5690 );
5691 let max_word_search = buffer_snapshot.clip_point(
5692 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5693 Bias::Right,
5694 );
5695 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5696 ..buffer_snapshot.point_to_offset(max_word_search);
5697
5698 let skip_digits = query
5699 .as_ref()
5700 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5701
5702 let omit_word_completions = !self.word_completions_enabled
5703 || (!ignore_word_threshold
5704 && match &query {
5705 Some(query) => query.chars().count() < completion_settings.words_min_length,
5706 None => completion_settings.words_min_length != 0,
5707 });
5708
5709 let (mut words, provider_responses) = match &provider {
5710 Some(provider) => {
5711 let provider_responses = provider.completions(
5712 buffer_excerpt_id,
5713 &buffer,
5714 buffer_position,
5715 completion_context,
5716 window,
5717 cx,
5718 );
5719
5720 let words = match (omit_word_completions, completion_settings.words) {
5721 (true, _) | (_, WordsCompletionMode::Disabled) => {
5722 Task::ready(BTreeMap::default())
5723 }
5724 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5725 .background_spawn(async move {
5726 buffer_snapshot.words_in_range(WordsQuery {
5727 fuzzy_contents: None,
5728 range: word_search_range,
5729 skip_digits,
5730 })
5731 }),
5732 };
5733
5734 (words, provider_responses)
5735 }
5736 None => {
5737 let words = if omit_word_completions {
5738 Task::ready(BTreeMap::default())
5739 } else {
5740 cx.background_spawn(async move {
5741 buffer_snapshot.words_in_range(WordsQuery {
5742 fuzzy_contents: None,
5743 range: word_search_range,
5744 skip_digits,
5745 })
5746 })
5747 };
5748 (words, Task::ready(Ok(Vec::new())))
5749 }
5750 };
5751
5752 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5753
5754 let id = post_inc(&mut self.next_completion_id);
5755 let task = cx.spawn_in(window, async move |editor, cx| {
5756 let Ok(()) = editor.update(cx, |this, _| {
5757 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5758 }) else {
5759 return;
5760 };
5761
5762 // TODO: Ideally completions from different sources would be selectively re-queried, so
5763 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5764 let mut completions = Vec::new();
5765 let mut is_incomplete = false;
5766 let mut display_options: Option<CompletionDisplayOptions> = None;
5767 if let Some(provider_responses) = provider_responses.await.log_err()
5768 && !provider_responses.is_empty()
5769 {
5770 for response in provider_responses {
5771 completions.extend(response.completions);
5772 is_incomplete = is_incomplete || response.is_incomplete;
5773 match display_options.as_mut() {
5774 None => {
5775 display_options = Some(response.display_options);
5776 }
5777 Some(options) => options.merge(&response.display_options),
5778 }
5779 }
5780 if completion_settings.words == WordsCompletionMode::Fallback {
5781 words = Task::ready(BTreeMap::default());
5782 }
5783 }
5784 let display_options = display_options.unwrap_or_default();
5785
5786 let mut words = words.await;
5787 if let Some(word_to_exclude) = &word_to_exclude {
5788 words.remove(word_to_exclude);
5789 }
5790 for lsp_completion in &completions {
5791 words.remove(&lsp_completion.new_text);
5792 }
5793 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5794 replace_range: word_replace_range.clone(),
5795 new_text: word.clone(),
5796 label: CodeLabel::plain(word, None),
5797 icon_path: None,
5798 documentation: None,
5799 source: CompletionSource::BufferWord {
5800 word_range,
5801 resolved: false,
5802 },
5803 insert_text_mode: Some(InsertTextMode::AS_IS),
5804 confirm: None,
5805 }));
5806
5807 let menu = if completions.is_empty() {
5808 None
5809 } else {
5810 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5811 let languages = editor
5812 .workspace
5813 .as_ref()
5814 .and_then(|(workspace, _)| workspace.upgrade())
5815 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5816 let menu = CompletionsMenu::new(
5817 id,
5818 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5819 sort_completions,
5820 show_completion_documentation,
5821 position,
5822 query.clone(),
5823 is_incomplete,
5824 buffer.clone(),
5825 completions.into(),
5826 display_options,
5827 snippet_sort_order,
5828 languages,
5829 language,
5830 cx,
5831 );
5832
5833 let query = if filter_completions { query } else { None };
5834 let matches_task = if let Some(query) = query {
5835 menu.do_async_filtering(query, cx)
5836 } else {
5837 Task::ready(menu.unfiltered_matches())
5838 };
5839 (menu, matches_task)
5840 }) else {
5841 return;
5842 };
5843
5844 let matches = matches_task.await;
5845
5846 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5847 // Newer menu already set, so exit.
5848 if let Some(CodeContextMenu::Completions(prev_menu)) =
5849 editor.context_menu.borrow().as_ref()
5850 && prev_menu.id > id
5851 {
5852 return;
5853 };
5854
5855 // Only valid to take prev_menu because it the new menu is immediately set
5856 // below, or the menu is hidden.
5857 if let Some(CodeContextMenu::Completions(prev_menu)) =
5858 editor.context_menu.borrow_mut().take()
5859 {
5860 let position_matches =
5861 if prev_menu.initial_position == menu.initial_position {
5862 true
5863 } else {
5864 let snapshot = editor.buffer.read(cx).read(cx);
5865 prev_menu.initial_position.to_offset(&snapshot)
5866 == menu.initial_position.to_offset(&snapshot)
5867 };
5868 if position_matches {
5869 // Preserve markdown cache before `set_filter_results` because it will
5870 // try to populate the documentation cache.
5871 menu.preserve_markdown_cache(prev_menu);
5872 }
5873 };
5874
5875 menu.set_filter_results(matches, provider, window, cx);
5876 }) else {
5877 return;
5878 };
5879
5880 menu.visible().then_some(menu)
5881 };
5882
5883 editor
5884 .update_in(cx, |editor, window, cx| {
5885 if editor.focus_handle.is_focused(window)
5886 && let Some(menu) = menu
5887 {
5888 *editor.context_menu.borrow_mut() =
5889 Some(CodeContextMenu::Completions(menu));
5890
5891 crate::hover_popover::hide_hover(editor, cx);
5892 if editor.show_edit_predictions_in_menu() {
5893 editor.update_visible_edit_prediction(window, cx);
5894 } else {
5895 editor.discard_edit_prediction(false, cx);
5896 }
5897
5898 cx.notify();
5899 return;
5900 }
5901
5902 if editor.completion_tasks.len() <= 1 {
5903 // If there are no more completion tasks and the last menu was empty, we should hide it.
5904 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5905 // If it was already hidden and we don't show edit predictions in the menu,
5906 // we should also show the edit prediction when available.
5907 if was_hidden && editor.show_edit_predictions_in_menu() {
5908 editor.update_visible_edit_prediction(window, cx);
5909 }
5910 }
5911 })
5912 .ok();
5913 });
5914
5915 self.completion_tasks.push((id, task));
5916 }
5917
5918 #[cfg(feature = "test-support")]
5919 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5920 let menu = self.context_menu.borrow();
5921 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5922 let completions = menu.completions.borrow();
5923 Some(completions.to_vec())
5924 } else {
5925 None
5926 }
5927 }
5928
5929 pub fn with_completions_menu_matching_id<R>(
5930 &self,
5931 id: CompletionId,
5932 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5933 ) -> R {
5934 let mut context_menu = self.context_menu.borrow_mut();
5935 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5936 return f(None);
5937 };
5938 if completions_menu.id != id {
5939 return f(None);
5940 }
5941 f(Some(completions_menu))
5942 }
5943
5944 pub fn confirm_completion(
5945 &mut self,
5946 action: &ConfirmCompletion,
5947 window: &mut Window,
5948 cx: &mut Context<Self>,
5949 ) -> Option<Task<Result<()>>> {
5950 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5951 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5952 }
5953
5954 pub fn confirm_completion_insert(
5955 &mut self,
5956 _: &ConfirmCompletionInsert,
5957 window: &mut Window,
5958 cx: &mut Context<Self>,
5959 ) -> Option<Task<Result<()>>> {
5960 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5961 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5962 }
5963
5964 pub fn confirm_completion_replace(
5965 &mut self,
5966 _: &ConfirmCompletionReplace,
5967 window: &mut Window,
5968 cx: &mut Context<Self>,
5969 ) -> Option<Task<Result<()>>> {
5970 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5971 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5972 }
5973
5974 pub fn compose_completion(
5975 &mut self,
5976 action: &ComposeCompletion,
5977 window: &mut Window,
5978 cx: &mut Context<Self>,
5979 ) -> Option<Task<Result<()>>> {
5980 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5981 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5982 }
5983
5984 fn do_completion(
5985 &mut self,
5986 item_ix: Option<usize>,
5987 intent: CompletionIntent,
5988 window: &mut Window,
5989 cx: &mut Context<Editor>,
5990 ) -> Option<Task<Result<()>>> {
5991 use language::ToOffset as _;
5992
5993 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5994 else {
5995 return None;
5996 };
5997
5998 let candidate_id = {
5999 let entries = completions_menu.entries.borrow();
6000 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6001 if self.show_edit_predictions_in_menu() {
6002 self.discard_edit_prediction(true, cx);
6003 }
6004 mat.candidate_id
6005 };
6006
6007 let completion = completions_menu
6008 .completions
6009 .borrow()
6010 .get(candidate_id)?
6011 .clone();
6012 cx.stop_propagation();
6013
6014 let buffer_handle = completions_menu.buffer.clone();
6015
6016 let CompletionEdit {
6017 new_text,
6018 snippet,
6019 replace_range,
6020 } = process_completion_for_edit(
6021 &completion,
6022 intent,
6023 &buffer_handle,
6024 &completions_menu.initial_position.text_anchor,
6025 cx,
6026 );
6027
6028 let buffer = buffer_handle.read(cx);
6029 let snapshot = self.buffer.read(cx).snapshot(cx);
6030 let newest_anchor = self.selections.newest_anchor();
6031 let replace_range_multibuffer = {
6032 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6033 excerpt.map_range_from_buffer(replace_range.clone())
6034 };
6035 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6036 return None;
6037 }
6038
6039 let old_text = buffer
6040 .text_for_range(replace_range.clone())
6041 .collect::<String>();
6042 let lookbehind = newest_anchor
6043 .start
6044 .text_anchor
6045 .to_offset(buffer)
6046 .saturating_sub(replace_range.start);
6047 let lookahead = replace_range
6048 .end
6049 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6050 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6051 let suffix = &old_text[lookbehind.min(old_text.len())..];
6052
6053 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
6054 let mut ranges = Vec::new();
6055 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6056
6057 for selection in &selections {
6058 let range = if selection.id == newest_anchor.id {
6059 replace_range_multibuffer.clone()
6060 } else {
6061 let mut range = selection.range();
6062
6063 // if prefix is present, don't duplicate it
6064 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6065 range.start = range.start.saturating_sub(lookbehind);
6066
6067 // if suffix is also present, mimic the newest cursor and replace it
6068 if selection.id != newest_anchor.id
6069 && snapshot.contains_str_at(range.end, suffix)
6070 {
6071 range.end += lookahead;
6072 }
6073 }
6074 range
6075 };
6076
6077 ranges.push(range.clone());
6078
6079 if !self.linked_edit_ranges.is_empty() {
6080 let start_anchor = snapshot.anchor_before(range.start);
6081 let end_anchor = snapshot.anchor_after(range.end);
6082 if let Some(ranges) = self
6083 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6084 {
6085 for (buffer, edits) in ranges {
6086 linked_edits
6087 .entry(buffer.clone())
6088 .or_default()
6089 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6090 }
6091 }
6092 }
6093 }
6094
6095 let common_prefix_len = old_text
6096 .chars()
6097 .zip(new_text.chars())
6098 .take_while(|(a, b)| a == b)
6099 .map(|(a, _)| a.len_utf8())
6100 .sum::<usize>();
6101
6102 cx.emit(EditorEvent::InputHandled {
6103 utf16_range_to_replace: None,
6104 text: new_text[common_prefix_len..].into(),
6105 });
6106
6107 self.transact(window, cx, |editor, window, cx| {
6108 if let Some(mut snippet) = snippet {
6109 snippet.text = new_text.to_string();
6110 editor
6111 .insert_snippet(&ranges, snippet, window, cx)
6112 .log_err();
6113 } else {
6114 editor.buffer.update(cx, |multi_buffer, cx| {
6115 let auto_indent = match completion.insert_text_mode {
6116 Some(InsertTextMode::AS_IS) => None,
6117 _ => editor.autoindent_mode.clone(),
6118 };
6119 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6120 multi_buffer.edit(edits, auto_indent, cx);
6121 });
6122 }
6123 for (buffer, edits) in linked_edits {
6124 buffer.update(cx, |buffer, cx| {
6125 let snapshot = buffer.snapshot();
6126 let edits = edits
6127 .into_iter()
6128 .map(|(range, text)| {
6129 use text::ToPoint as TP;
6130 let end_point = TP::to_point(&range.end, &snapshot);
6131 let start_point = TP::to_point(&range.start, &snapshot);
6132 (start_point..end_point, text)
6133 })
6134 .sorted_by_key(|(range, _)| range.start);
6135 buffer.edit(edits, None, cx);
6136 })
6137 }
6138
6139 editor.refresh_edit_prediction(true, false, window, cx);
6140 });
6141 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6142
6143 let show_new_completions_on_confirm = completion
6144 .confirm
6145 .as_ref()
6146 .is_some_and(|confirm| confirm(intent, window, cx));
6147 if show_new_completions_on_confirm {
6148 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6149 }
6150
6151 let provider = self.completion_provider.as_ref()?;
6152 drop(completion);
6153 let apply_edits = provider.apply_additional_edits_for_completion(
6154 buffer_handle,
6155 completions_menu.completions.clone(),
6156 candidate_id,
6157 true,
6158 cx,
6159 );
6160
6161 let editor_settings = EditorSettings::get_global(cx);
6162 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6163 // After the code completion is finished, users often want to know what signatures are needed.
6164 // so we should automatically call signature_help
6165 self.show_signature_help(&ShowSignatureHelp, window, cx);
6166 }
6167
6168 Some(cx.foreground_executor().spawn(async move {
6169 apply_edits.await?;
6170 Ok(())
6171 }))
6172 }
6173
6174 pub fn toggle_code_actions(
6175 &mut self,
6176 action: &ToggleCodeActions,
6177 window: &mut Window,
6178 cx: &mut Context<Self>,
6179 ) {
6180 let quick_launch = action.quick_launch;
6181 let mut context_menu = self.context_menu.borrow_mut();
6182 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6183 if code_actions.deployed_from == action.deployed_from {
6184 // Toggle if we're selecting the same one
6185 *context_menu = None;
6186 cx.notify();
6187 return;
6188 } else {
6189 // Otherwise, clear it and start a new one
6190 *context_menu = None;
6191 cx.notify();
6192 }
6193 }
6194 drop(context_menu);
6195 let snapshot = self.snapshot(window, cx);
6196 let deployed_from = action.deployed_from.clone();
6197 let action = action.clone();
6198 self.completion_tasks.clear();
6199 self.discard_edit_prediction(false, cx);
6200
6201 let multibuffer_point = match &action.deployed_from {
6202 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6203 DisplayPoint::new(*row, 0).to_point(&snapshot)
6204 }
6205 _ => self
6206 .selections
6207 .newest::<Point>(&snapshot.display_snapshot)
6208 .head(),
6209 };
6210 let Some((buffer, buffer_row)) = snapshot
6211 .buffer_snapshot()
6212 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6213 .and_then(|(buffer_snapshot, range)| {
6214 self.buffer()
6215 .read(cx)
6216 .buffer(buffer_snapshot.remote_id())
6217 .map(|buffer| (buffer, range.start.row))
6218 })
6219 else {
6220 return;
6221 };
6222 let buffer_id = buffer.read(cx).remote_id();
6223 let tasks = self
6224 .tasks
6225 .get(&(buffer_id, buffer_row))
6226 .map(|t| Arc::new(t.to_owned()));
6227
6228 if !self.focus_handle.is_focused(window) {
6229 return;
6230 }
6231 let project = self.project.clone();
6232
6233 let code_actions_task = match deployed_from {
6234 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6235 _ => self.code_actions(buffer_row, window, cx),
6236 };
6237
6238 let runnable_task = match deployed_from {
6239 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6240 _ => {
6241 let mut task_context_task = Task::ready(None);
6242 if let Some(tasks) = &tasks
6243 && let Some(project) = project
6244 {
6245 task_context_task =
6246 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6247 }
6248
6249 cx.spawn_in(window, {
6250 let buffer = buffer.clone();
6251 async move |editor, cx| {
6252 let task_context = task_context_task.await;
6253
6254 let resolved_tasks =
6255 tasks
6256 .zip(task_context.clone())
6257 .map(|(tasks, task_context)| ResolvedTasks {
6258 templates: tasks.resolve(&task_context).collect(),
6259 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6260 multibuffer_point.row,
6261 tasks.column,
6262 )),
6263 });
6264 let debug_scenarios = editor
6265 .update(cx, |editor, cx| {
6266 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6267 })?
6268 .await;
6269 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6270 }
6271 })
6272 }
6273 };
6274
6275 cx.spawn_in(window, async move |editor, cx| {
6276 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6277 let code_actions = code_actions_task.await;
6278 let spawn_straight_away = quick_launch
6279 && resolved_tasks
6280 .as_ref()
6281 .is_some_and(|tasks| tasks.templates.len() == 1)
6282 && code_actions
6283 .as_ref()
6284 .is_none_or(|actions| actions.is_empty())
6285 && debug_scenarios.is_empty();
6286
6287 editor.update_in(cx, |editor, window, cx| {
6288 crate::hover_popover::hide_hover(editor, cx);
6289 let actions = CodeActionContents::new(
6290 resolved_tasks,
6291 code_actions,
6292 debug_scenarios,
6293 task_context.unwrap_or_default(),
6294 );
6295
6296 // Don't show the menu if there are no actions available
6297 if actions.is_empty() {
6298 cx.notify();
6299 return Task::ready(Ok(()));
6300 }
6301
6302 *editor.context_menu.borrow_mut() =
6303 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6304 buffer,
6305 actions,
6306 selected_item: Default::default(),
6307 scroll_handle: UniformListScrollHandle::default(),
6308 deployed_from,
6309 }));
6310 cx.notify();
6311 if spawn_straight_away
6312 && let Some(task) = editor.confirm_code_action(
6313 &ConfirmCodeAction { item_ix: Some(0) },
6314 window,
6315 cx,
6316 )
6317 {
6318 return task;
6319 }
6320
6321 Task::ready(Ok(()))
6322 })
6323 })
6324 .detach_and_log_err(cx);
6325 }
6326
6327 fn debug_scenarios(
6328 &mut self,
6329 resolved_tasks: &Option<ResolvedTasks>,
6330 buffer: &Entity<Buffer>,
6331 cx: &mut App,
6332 ) -> Task<Vec<task::DebugScenario>> {
6333 maybe!({
6334 let project = self.project()?;
6335 let dap_store = project.read(cx).dap_store();
6336 let mut scenarios = vec![];
6337 let resolved_tasks = resolved_tasks.as_ref()?;
6338 let buffer = buffer.read(cx);
6339 let language = buffer.language()?;
6340 let file = buffer.file();
6341 let debug_adapter = language_settings(language.name().into(), file, cx)
6342 .debuggers
6343 .first()
6344 .map(SharedString::from)
6345 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6346
6347 dap_store.update(cx, |dap_store, cx| {
6348 for (_, task) in &resolved_tasks.templates {
6349 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6350 task.original_task().clone(),
6351 debug_adapter.clone().into(),
6352 task.display_label().to_owned().into(),
6353 cx,
6354 );
6355 scenarios.push(maybe_scenario);
6356 }
6357 });
6358 Some(cx.background_spawn(async move {
6359 futures::future::join_all(scenarios)
6360 .await
6361 .into_iter()
6362 .flatten()
6363 .collect::<Vec<_>>()
6364 }))
6365 })
6366 .unwrap_or_else(|| Task::ready(vec![]))
6367 }
6368
6369 fn code_actions(
6370 &mut self,
6371 buffer_row: u32,
6372 window: &mut Window,
6373 cx: &mut Context<Self>,
6374 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6375 let mut task = self.code_actions_task.take();
6376 cx.spawn_in(window, async move |editor, cx| {
6377 while let Some(prev_task) = task {
6378 prev_task.await.log_err();
6379 task = editor
6380 .update(cx, |this, _| this.code_actions_task.take())
6381 .ok()?;
6382 }
6383
6384 editor
6385 .update(cx, |editor, cx| {
6386 editor
6387 .available_code_actions
6388 .clone()
6389 .and_then(|(location, code_actions)| {
6390 let snapshot = location.buffer.read(cx).snapshot();
6391 let point_range = location.range.to_point(&snapshot);
6392 let point_range = point_range.start.row..=point_range.end.row;
6393 if point_range.contains(&buffer_row) {
6394 Some(code_actions)
6395 } else {
6396 None
6397 }
6398 })
6399 })
6400 .ok()
6401 .flatten()
6402 })
6403 }
6404
6405 pub fn confirm_code_action(
6406 &mut self,
6407 action: &ConfirmCodeAction,
6408 window: &mut Window,
6409 cx: &mut Context<Self>,
6410 ) -> Option<Task<Result<()>>> {
6411 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6412
6413 let actions_menu =
6414 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6415 menu
6416 } else {
6417 return None;
6418 };
6419
6420 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6421 let action = actions_menu.actions.get(action_ix)?;
6422 let title = action.label();
6423 let buffer = actions_menu.buffer;
6424 let workspace = self.workspace()?;
6425
6426 match action {
6427 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6428 workspace.update(cx, |workspace, cx| {
6429 workspace.schedule_resolved_task(
6430 task_source_kind,
6431 resolved_task,
6432 false,
6433 window,
6434 cx,
6435 );
6436
6437 Some(Task::ready(Ok(())))
6438 })
6439 }
6440 CodeActionsItem::CodeAction {
6441 excerpt_id,
6442 action,
6443 provider,
6444 } => {
6445 let apply_code_action =
6446 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6447 let workspace = workspace.downgrade();
6448 Some(cx.spawn_in(window, async move |editor, cx| {
6449 let project_transaction = apply_code_action.await?;
6450 Self::open_project_transaction(
6451 &editor,
6452 workspace,
6453 project_transaction,
6454 title,
6455 cx,
6456 )
6457 .await
6458 }))
6459 }
6460 CodeActionsItem::DebugScenario(scenario) => {
6461 let context = actions_menu.actions.context;
6462
6463 workspace.update(cx, |workspace, cx| {
6464 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6465 workspace.start_debug_session(
6466 scenario,
6467 context,
6468 Some(buffer),
6469 None,
6470 window,
6471 cx,
6472 );
6473 });
6474 Some(Task::ready(Ok(())))
6475 }
6476 }
6477 }
6478
6479 pub async fn open_project_transaction(
6480 editor: &WeakEntity<Editor>,
6481 workspace: WeakEntity<Workspace>,
6482 transaction: ProjectTransaction,
6483 title: String,
6484 cx: &mut AsyncWindowContext,
6485 ) -> Result<()> {
6486 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6487 cx.update(|_, cx| {
6488 entries.sort_unstable_by_key(|(buffer, _)| {
6489 buffer.read(cx).file().map(|f| f.path().clone())
6490 });
6491 })?;
6492 if entries.is_empty() {
6493 return Ok(());
6494 }
6495
6496 // If the project transaction's edits are all contained within this editor, then
6497 // avoid opening a new editor to display them.
6498
6499 if let [(buffer, transaction)] = &*entries {
6500 let excerpt = editor.update(cx, |editor, cx| {
6501 editor
6502 .buffer()
6503 .read(cx)
6504 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6505 })?;
6506 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6507 && excerpted_buffer == *buffer
6508 {
6509 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6510 let excerpt_range = excerpt_range.to_offset(buffer);
6511 buffer
6512 .edited_ranges_for_transaction::<usize>(transaction)
6513 .all(|range| {
6514 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6515 })
6516 })?;
6517
6518 if all_edits_within_excerpt {
6519 return Ok(());
6520 }
6521 }
6522 }
6523
6524 let mut ranges_to_highlight = Vec::new();
6525 let excerpt_buffer = cx.new(|cx| {
6526 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6527 for (buffer_handle, transaction) in &entries {
6528 let edited_ranges = buffer_handle
6529 .read(cx)
6530 .edited_ranges_for_transaction::<Point>(transaction)
6531 .collect::<Vec<_>>();
6532 let (ranges, _) = multibuffer.set_excerpts_for_path(
6533 PathKey::for_buffer(buffer_handle, cx),
6534 buffer_handle.clone(),
6535 edited_ranges,
6536 multibuffer_context_lines(cx),
6537 cx,
6538 );
6539
6540 ranges_to_highlight.extend(ranges);
6541 }
6542 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6543 multibuffer
6544 })?;
6545
6546 workspace.update_in(cx, |workspace, window, cx| {
6547 let project = workspace.project().clone();
6548 let editor =
6549 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6550 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6551 editor.update(cx, |editor, cx| {
6552 editor.highlight_background::<Self>(
6553 &ranges_to_highlight,
6554 |theme| theme.colors().editor_highlighted_line_background,
6555 cx,
6556 );
6557 });
6558 })?;
6559
6560 Ok(())
6561 }
6562
6563 pub fn clear_code_action_providers(&mut self) {
6564 self.code_action_providers.clear();
6565 self.available_code_actions.take();
6566 }
6567
6568 pub fn add_code_action_provider(
6569 &mut self,
6570 provider: Rc<dyn CodeActionProvider>,
6571 window: &mut Window,
6572 cx: &mut Context<Self>,
6573 ) {
6574 if self
6575 .code_action_providers
6576 .iter()
6577 .any(|existing_provider| existing_provider.id() == provider.id())
6578 {
6579 return;
6580 }
6581
6582 self.code_action_providers.push(provider);
6583 self.refresh_code_actions(window, cx);
6584 }
6585
6586 pub fn remove_code_action_provider(
6587 &mut self,
6588 id: Arc<str>,
6589 window: &mut Window,
6590 cx: &mut Context<Self>,
6591 ) {
6592 self.code_action_providers
6593 .retain(|provider| provider.id() != id);
6594 self.refresh_code_actions(window, cx);
6595 }
6596
6597 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6598 !self.code_action_providers.is_empty()
6599 && EditorSettings::get_global(cx).toolbar.code_actions
6600 }
6601
6602 pub fn has_available_code_actions(&self) -> bool {
6603 self.available_code_actions
6604 .as_ref()
6605 .is_some_and(|(_, actions)| !actions.is_empty())
6606 }
6607
6608 fn render_inline_code_actions(
6609 &self,
6610 icon_size: ui::IconSize,
6611 display_row: DisplayRow,
6612 is_active: bool,
6613 cx: &mut Context<Self>,
6614 ) -> AnyElement {
6615 let show_tooltip = !self.context_menu_visible();
6616 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6617 .icon_size(icon_size)
6618 .shape(ui::IconButtonShape::Square)
6619 .icon_color(ui::Color::Hidden)
6620 .toggle_state(is_active)
6621 .when(show_tooltip, |this| {
6622 this.tooltip({
6623 let focus_handle = self.focus_handle.clone();
6624 move |window, cx| {
6625 Tooltip::for_action_in(
6626 "Toggle Code Actions",
6627 &ToggleCodeActions {
6628 deployed_from: None,
6629 quick_launch: false,
6630 },
6631 &focus_handle,
6632 window,
6633 cx,
6634 )
6635 }
6636 })
6637 })
6638 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6639 window.focus(&editor.focus_handle(cx));
6640 editor.toggle_code_actions(
6641 &crate::actions::ToggleCodeActions {
6642 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6643 display_row,
6644 )),
6645 quick_launch: false,
6646 },
6647 window,
6648 cx,
6649 );
6650 }))
6651 .into_any_element()
6652 }
6653
6654 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6655 &self.context_menu
6656 }
6657
6658 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6659 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6660 cx.background_executor()
6661 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6662 .await;
6663
6664 let (start_buffer, start, _, end, newest_selection) = this
6665 .update(cx, |this, cx| {
6666 let newest_selection = this.selections.newest_anchor().clone();
6667 if newest_selection.head().diff_base_anchor.is_some() {
6668 return None;
6669 }
6670 let display_snapshot = this.display_snapshot(cx);
6671 let newest_selection_adjusted =
6672 this.selections.newest_adjusted(&display_snapshot);
6673 let buffer = this.buffer.read(cx);
6674
6675 let (start_buffer, start) =
6676 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6677 let (end_buffer, end) =
6678 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6679
6680 Some((start_buffer, start, end_buffer, end, newest_selection))
6681 })?
6682 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6683 .context(
6684 "Expected selection to lie in a single buffer when refreshing code actions",
6685 )?;
6686 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6687 let providers = this.code_action_providers.clone();
6688 let tasks = this
6689 .code_action_providers
6690 .iter()
6691 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6692 .collect::<Vec<_>>();
6693 (providers, tasks)
6694 })?;
6695
6696 let mut actions = Vec::new();
6697 for (provider, provider_actions) in
6698 providers.into_iter().zip(future::join_all(tasks).await)
6699 {
6700 if let Some(provider_actions) = provider_actions.log_err() {
6701 actions.extend(provider_actions.into_iter().map(|action| {
6702 AvailableCodeAction {
6703 excerpt_id: newest_selection.start.excerpt_id,
6704 action,
6705 provider: provider.clone(),
6706 }
6707 }));
6708 }
6709 }
6710
6711 this.update(cx, |this, cx| {
6712 this.available_code_actions = if actions.is_empty() {
6713 None
6714 } else {
6715 Some((
6716 Location {
6717 buffer: start_buffer,
6718 range: start..end,
6719 },
6720 actions.into(),
6721 ))
6722 };
6723 cx.notify();
6724 })
6725 }));
6726 }
6727
6728 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6729 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6730 self.show_git_blame_inline = false;
6731
6732 self.show_git_blame_inline_delay_task =
6733 Some(cx.spawn_in(window, async move |this, cx| {
6734 cx.background_executor().timer(delay).await;
6735
6736 this.update(cx, |this, cx| {
6737 this.show_git_blame_inline = true;
6738 cx.notify();
6739 })
6740 .log_err();
6741 }));
6742 }
6743 }
6744
6745 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6746 let snapshot = self.snapshot(window, cx);
6747 let cursor = self
6748 .selections
6749 .newest::<Point>(&snapshot.display_snapshot)
6750 .head();
6751 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6752 else {
6753 return;
6754 };
6755
6756 let Some(blame) = self.blame.as_ref() else {
6757 return;
6758 };
6759
6760 let row_info = RowInfo {
6761 buffer_id: Some(buffer.remote_id()),
6762 buffer_row: Some(point.row),
6763 ..Default::default()
6764 };
6765 let Some((buffer, blame_entry)) = blame
6766 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6767 .flatten()
6768 else {
6769 return;
6770 };
6771
6772 let anchor = self.selections.newest_anchor().head();
6773 let position = self.to_pixel_point(anchor, &snapshot, window);
6774 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6775 self.show_blame_popover(
6776 buffer,
6777 &blame_entry,
6778 position + last_bounds.origin,
6779 true,
6780 cx,
6781 );
6782 };
6783 }
6784
6785 fn show_blame_popover(
6786 &mut self,
6787 buffer: BufferId,
6788 blame_entry: &BlameEntry,
6789 position: gpui::Point<Pixels>,
6790 ignore_timeout: bool,
6791 cx: &mut Context<Self>,
6792 ) {
6793 if let Some(state) = &mut self.inline_blame_popover {
6794 state.hide_task.take();
6795 } else {
6796 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6797 let blame_entry = blame_entry.clone();
6798 let show_task = cx.spawn(async move |editor, cx| {
6799 if !ignore_timeout {
6800 cx.background_executor()
6801 .timer(std::time::Duration::from_millis(blame_popover_delay))
6802 .await;
6803 }
6804 editor
6805 .update(cx, |editor, cx| {
6806 editor.inline_blame_popover_show_task.take();
6807 let Some(blame) = editor.blame.as_ref() else {
6808 return;
6809 };
6810 let blame = blame.read(cx);
6811 let details = blame.details_for_entry(buffer, &blame_entry);
6812 let markdown = cx.new(|cx| {
6813 Markdown::new(
6814 details
6815 .as_ref()
6816 .map(|message| message.message.clone())
6817 .unwrap_or_default(),
6818 None,
6819 None,
6820 cx,
6821 )
6822 });
6823 editor.inline_blame_popover = Some(InlineBlamePopover {
6824 position,
6825 hide_task: None,
6826 popover_bounds: None,
6827 popover_state: InlineBlamePopoverState {
6828 scroll_handle: ScrollHandle::new(),
6829 commit_message: details,
6830 markdown,
6831 },
6832 keyboard_grace: ignore_timeout,
6833 });
6834 cx.notify();
6835 })
6836 .ok();
6837 });
6838 self.inline_blame_popover_show_task = Some(show_task);
6839 }
6840 }
6841
6842 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6843 self.inline_blame_popover_show_task.take();
6844 if let Some(state) = &mut self.inline_blame_popover {
6845 let hide_task = cx.spawn(async move |editor, cx| {
6846 cx.background_executor()
6847 .timer(std::time::Duration::from_millis(100))
6848 .await;
6849 editor
6850 .update(cx, |editor, cx| {
6851 editor.inline_blame_popover.take();
6852 cx.notify();
6853 })
6854 .ok();
6855 });
6856 state.hide_task = Some(hide_task);
6857 }
6858 }
6859
6860 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6861 if self.pending_rename.is_some() {
6862 return None;
6863 }
6864
6865 let provider = self.semantics_provider.clone()?;
6866 let buffer = self.buffer.read(cx);
6867 let newest_selection = self.selections.newest_anchor().clone();
6868 let cursor_position = newest_selection.head();
6869 let (cursor_buffer, cursor_buffer_position) =
6870 buffer.text_anchor_for_position(cursor_position, cx)?;
6871 let (tail_buffer, tail_buffer_position) =
6872 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6873 if cursor_buffer != tail_buffer {
6874 return None;
6875 }
6876
6877 let snapshot = cursor_buffer.read(cx).snapshot();
6878 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6879 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6880 if start_word_range != end_word_range {
6881 self.document_highlights_task.take();
6882 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6883 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6884 return None;
6885 }
6886
6887 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6888 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6889 cx.background_executor()
6890 .timer(Duration::from_millis(debounce))
6891 .await;
6892
6893 let highlights = if let Some(highlights) = cx
6894 .update(|cx| {
6895 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6896 })
6897 .ok()
6898 .flatten()
6899 {
6900 highlights.await.log_err()
6901 } else {
6902 None
6903 };
6904
6905 if let Some(highlights) = highlights {
6906 this.update(cx, |this, cx| {
6907 if this.pending_rename.is_some() {
6908 return;
6909 }
6910
6911 let buffer = this.buffer.read(cx);
6912 if buffer
6913 .text_anchor_for_position(cursor_position, cx)
6914 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6915 {
6916 return;
6917 }
6918
6919 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6920 let mut write_ranges = Vec::new();
6921 let mut read_ranges = Vec::new();
6922 for highlight in highlights {
6923 let buffer_id = cursor_buffer.read(cx).remote_id();
6924 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6925 {
6926 let start = highlight
6927 .range
6928 .start
6929 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6930 let end = highlight
6931 .range
6932 .end
6933 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6934 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6935 continue;
6936 }
6937
6938 let range =
6939 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6940 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6941 write_ranges.push(range);
6942 } else {
6943 read_ranges.push(range);
6944 }
6945 }
6946 }
6947
6948 this.highlight_background::<DocumentHighlightRead>(
6949 &read_ranges,
6950 |theme| theme.colors().editor_document_highlight_read_background,
6951 cx,
6952 );
6953 this.highlight_background::<DocumentHighlightWrite>(
6954 &write_ranges,
6955 |theme| theme.colors().editor_document_highlight_write_background,
6956 cx,
6957 );
6958 cx.notify();
6959 })
6960 .log_err();
6961 }
6962 }));
6963 None
6964 }
6965
6966 fn prepare_highlight_query_from_selection(
6967 &mut self,
6968 cx: &mut Context<Editor>,
6969 ) -> Option<(String, Range<Anchor>)> {
6970 if matches!(self.mode, EditorMode::SingleLine) {
6971 return None;
6972 }
6973 if !EditorSettings::get_global(cx).selection_highlight {
6974 return None;
6975 }
6976 if self.selections.count() != 1 || self.selections.line_mode() {
6977 return None;
6978 }
6979 let selection = self.selections.newest_anchor();
6980 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6981 let selection_point_range = selection.start.to_point(&multi_buffer_snapshot)
6982 ..selection.end.to_point(&multi_buffer_snapshot);
6983 // If the selection spans multiple rows OR it is empty
6984 if selection_point_range.start.row != selection_point_range.end.row
6985 || selection_point_range.start.column == selection_point_range.end.column
6986 {
6987 return None;
6988 }
6989
6990 let query = multi_buffer_snapshot
6991 .text_for_range(selection.range())
6992 .collect::<String>();
6993 if query.trim().is_empty() {
6994 return None;
6995 }
6996 Some((query, selection.range()))
6997 }
6998
6999 fn update_selection_occurrence_highlights(
7000 &mut self,
7001 query_text: String,
7002 query_range: Range<Anchor>,
7003 multi_buffer_range_to_query: Range<Point>,
7004 use_debounce: bool,
7005 window: &mut Window,
7006 cx: &mut Context<Editor>,
7007 ) -> Task<()> {
7008 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7009 cx.spawn_in(window, async move |editor, cx| {
7010 if use_debounce {
7011 cx.background_executor()
7012 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7013 .await;
7014 }
7015 let match_task = cx.background_spawn(async move {
7016 let buffer_ranges = multi_buffer_snapshot
7017 .range_to_buffer_ranges(multi_buffer_range_to_query)
7018 .into_iter()
7019 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7020 let mut match_ranges = Vec::new();
7021 let Ok(regex) = project::search::SearchQuery::text(
7022 query_text.clone(),
7023 false,
7024 false,
7025 false,
7026 Default::default(),
7027 Default::default(),
7028 false,
7029 None,
7030 ) else {
7031 return Vec::default();
7032 };
7033 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7034 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7035 match_ranges.extend(
7036 regex
7037 .search(buffer_snapshot, Some(search_range.clone()))
7038 .await
7039 .into_iter()
7040 .filter_map(|match_range| {
7041 let match_start = buffer_snapshot
7042 .anchor_after(search_range.start + match_range.start);
7043 let match_end = buffer_snapshot
7044 .anchor_before(search_range.start + match_range.end);
7045 let match_anchor_range = Anchor::range_in_buffer(
7046 excerpt_id,
7047 buffer_snapshot.remote_id(),
7048 match_start..match_end,
7049 );
7050 (match_anchor_range != query_range).then_some(match_anchor_range)
7051 }),
7052 );
7053 }
7054 match_ranges
7055 });
7056 let match_ranges = match_task.await;
7057 editor
7058 .update_in(cx, |editor, _, cx| {
7059 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7060 if !match_ranges.is_empty() {
7061 editor.highlight_background::<SelectedTextHighlight>(
7062 &match_ranges,
7063 |theme| theme.colors().editor_document_highlight_bracket_background,
7064 cx,
7065 )
7066 }
7067 })
7068 .log_err();
7069 })
7070 }
7071
7072 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7073 struct NewlineFold;
7074 let type_id = std::any::TypeId::of::<NewlineFold>();
7075 if !self.mode.is_single_line() {
7076 return;
7077 }
7078 let snapshot = self.snapshot(window, cx);
7079 if snapshot.buffer_snapshot().max_point().row == 0 {
7080 return;
7081 }
7082 let task = cx.background_spawn(async move {
7083 let new_newlines = snapshot
7084 .buffer_chars_at(0)
7085 .filter_map(|(c, i)| {
7086 if c == '\n' {
7087 Some(
7088 snapshot.buffer_snapshot().anchor_after(i)
7089 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7090 )
7091 } else {
7092 None
7093 }
7094 })
7095 .collect::<Vec<_>>();
7096 let existing_newlines = snapshot
7097 .folds_in_range(0..snapshot.buffer_snapshot().len())
7098 .filter_map(|fold| {
7099 if fold.placeholder.type_tag == Some(type_id) {
7100 Some(fold.range.start..fold.range.end)
7101 } else {
7102 None
7103 }
7104 })
7105 .collect::<Vec<_>>();
7106
7107 (new_newlines, existing_newlines)
7108 });
7109 self.folding_newlines = cx.spawn(async move |this, cx| {
7110 let (new_newlines, existing_newlines) = task.await;
7111 if new_newlines == existing_newlines {
7112 return;
7113 }
7114 let placeholder = FoldPlaceholder {
7115 render: Arc::new(move |_, _, cx| {
7116 div()
7117 .bg(cx.theme().status().hint_background)
7118 .border_b_1()
7119 .size_full()
7120 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7121 .border_color(cx.theme().status().hint)
7122 .child("\\n")
7123 .into_any()
7124 }),
7125 constrain_width: false,
7126 merge_adjacent: false,
7127 type_tag: Some(type_id),
7128 };
7129 let creases = new_newlines
7130 .into_iter()
7131 .map(|range| Crease::simple(range, placeholder.clone()))
7132 .collect();
7133 this.update(cx, |this, cx| {
7134 this.display_map.update(cx, |display_map, cx| {
7135 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7136 display_map.fold(creases, cx);
7137 });
7138 })
7139 .ok();
7140 });
7141 }
7142
7143 fn refresh_selected_text_highlights(
7144 &mut self,
7145 on_buffer_edit: bool,
7146 window: &mut Window,
7147 cx: &mut Context<Editor>,
7148 ) {
7149 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7150 else {
7151 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7152 self.quick_selection_highlight_task.take();
7153 self.debounced_selection_highlight_task.take();
7154 return;
7155 };
7156 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7157 if on_buffer_edit
7158 || self
7159 .quick_selection_highlight_task
7160 .as_ref()
7161 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7162 {
7163 let multi_buffer_visible_start = self
7164 .scroll_manager
7165 .anchor()
7166 .anchor
7167 .to_point(&multi_buffer_snapshot);
7168 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7169 multi_buffer_visible_start
7170 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7171 Bias::Left,
7172 );
7173 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7174 self.quick_selection_highlight_task = Some((
7175 query_range.clone(),
7176 self.update_selection_occurrence_highlights(
7177 query_text.clone(),
7178 query_range.clone(),
7179 multi_buffer_visible_range,
7180 false,
7181 window,
7182 cx,
7183 ),
7184 ));
7185 }
7186 if on_buffer_edit
7187 || self
7188 .debounced_selection_highlight_task
7189 .as_ref()
7190 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7191 {
7192 let multi_buffer_start = multi_buffer_snapshot
7193 .anchor_before(0)
7194 .to_point(&multi_buffer_snapshot);
7195 let multi_buffer_end = multi_buffer_snapshot
7196 .anchor_after(multi_buffer_snapshot.len())
7197 .to_point(&multi_buffer_snapshot);
7198 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7199 self.debounced_selection_highlight_task = Some((
7200 query_range.clone(),
7201 self.update_selection_occurrence_highlights(
7202 query_text,
7203 query_range,
7204 multi_buffer_full_range,
7205 true,
7206 window,
7207 cx,
7208 ),
7209 ));
7210 }
7211 }
7212
7213 pub fn refresh_edit_prediction(
7214 &mut self,
7215 debounce: bool,
7216 user_requested: bool,
7217 window: &mut Window,
7218 cx: &mut Context<Self>,
7219 ) -> Option<()> {
7220 if DisableAiSettings::get_global(cx).disable_ai {
7221 return None;
7222 }
7223
7224 let provider = self.edit_prediction_provider()?;
7225 let cursor = self.selections.newest_anchor().head();
7226 let (buffer, cursor_buffer_position) =
7227 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7228
7229 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7230 self.discard_edit_prediction(false, cx);
7231 return None;
7232 }
7233
7234 self.update_visible_edit_prediction(window, cx);
7235
7236 if !user_requested
7237 && (!self.should_show_edit_predictions()
7238 || !self.is_focused(window)
7239 || buffer.read(cx).is_empty())
7240 {
7241 self.discard_edit_prediction(false, cx);
7242 return None;
7243 }
7244
7245 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7246 Some(())
7247 }
7248
7249 fn show_edit_predictions_in_menu(&self) -> bool {
7250 match self.edit_prediction_settings {
7251 EditPredictionSettings::Disabled => false,
7252 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7253 }
7254 }
7255
7256 pub fn edit_predictions_enabled(&self) -> bool {
7257 match self.edit_prediction_settings {
7258 EditPredictionSettings::Disabled => false,
7259 EditPredictionSettings::Enabled { .. } => true,
7260 }
7261 }
7262
7263 fn edit_prediction_requires_modifier(&self) -> bool {
7264 match self.edit_prediction_settings {
7265 EditPredictionSettings::Disabled => false,
7266 EditPredictionSettings::Enabled {
7267 preview_requires_modifier,
7268 ..
7269 } => preview_requires_modifier,
7270 }
7271 }
7272
7273 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7274 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7275 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7276 self.discard_edit_prediction(false, cx);
7277 } else {
7278 let selection = self.selections.newest_anchor();
7279 let cursor = selection.head();
7280
7281 if let Some((buffer, cursor_buffer_position)) =
7282 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7283 {
7284 self.edit_prediction_settings =
7285 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7286 }
7287 }
7288 }
7289
7290 fn edit_prediction_settings_at_position(
7291 &self,
7292 buffer: &Entity<Buffer>,
7293 buffer_position: language::Anchor,
7294 cx: &App,
7295 ) -> EditPredictionSettings {
7296 if !self.mode.is_full()
7297 || !self.show_edit_predictions_override.unwrap_or(true)
7298 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7299 {
7300 return EditPredictionSettings::Disabled;
7301 }
7302
7303 let buffer = buffer.read(cx);
7304
7305 let file = buffer.file();
7306
7307 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7308 return EditPredictionSettings::Disabled;
7309 };
7310
7311 let by_provider = matches!(
7312 self.menu_edit_predictions_policy,
7313 MenuEditPredictionsPolicy::ByProvider
7314 );
7315
7316 let show_in_menu = by_provider
7317 && self
7318 .edit_prediction_provider
7319 .as_ref()
7320 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7321
7322 let preview_requires_modifier =
7323 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7324
7325 EditPredictionSettings::Enabled {
7326 show_in_menu,
7327 preview_requires_modifier,
7328 }
7329 }
7330
7331 fn should_show_edit_predictions(&self) -> bool {
7332 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7333 }
7334
7335 pub fn edit_prediction_preview_is_active(&self) -> bool {
7336 matches!(
7337 self.edit_prediction_preview,
7338 EditPredictionPreview::Active { .. }
7339 )
7340 }
7341
7342 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7343 let cursor = self.selections.newest_anchor().head();
7344 if let Some((buffer, cursor_position)) =
7345 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7346 {
7347 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7348 } else {
7349 false
7350 }
7351 }
7352
7353 pub fn supports_minimap(&self, cx: &App) -> bool {
7354 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7355 }
7356
7357 fn edit_predictions_enabled_in_buffer(
7358 &self,
7359 buffer: &Entity<Buffer>,
7360 buffer_position: language::Anchor,
7361 cx: &App,
7362 ) -> bool {
7363 maybe!({
7364 if self.read_only(cx) {
7365 return Some(false);
7366 }
7367 let provider = self.edit_prediction_provider()?;
7368 if !provider.is_enabled(buffer, buffer_position, cx) {
7369 return Some(false);
7370 }
7371 let buffer = buffer.read(cx);
7372 let Some(file) = buffer.file() else {
7373 return Some(true);
7374 };
7375 let settings = all_language_settings(Some(file), cx);
7376 Some(settings.edit_predictions_enabled_for_file(file, cx))
7377 })
7378 .unwrap_or(false)
7379 }
7380
7381 fn cycle_edit_prediction(
7382 &mut self,
7383 direction: Direction,
7384 window: &mut Window,
7385 cx: &mut Context<Self>,
7386 ) -> Option<()> {
7387 let provider = self.edit_prediction_provider()?;
7388 let cursor = self.selections.newest_anchor().head();
7389 let (buffer, cursor_buffer_position) =
7390 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7391 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7392 return None;
7393 }
7394
7395 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7396 self.update_visible_edit_prediction(window, cx);
7397
7398 Some(())
7399 }
7400
7401 pub fn show_edit_prediction(
7402 &mut self,
7403 _: &ShowEditPrediction,
7404 window: &mut Window,
7405 cx: &mut Context<Self>,
7406 ) {
7407 if !self.has_active_edit_prediction() {
7408 self.refresh_edit_prediction(false, true, window, cx);
7409 return;
7410 }
7411
7412 self.update_visible_edit_prediction(window, cx);
7413 }
7414
7415 pub fn display_cursor_names(
7416 &mut self,
7417 _: &DisplayCursorNames,
7418 window: &mut Window,
7419 cx: &mut Context<Self>,
7420 ) {
7421 self.show_cursor_names(window, cx);
7422 }
7423
7424 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7425 self.show_cursor_names = true;
7426 cx.notify();
7427 cx.spawn_in(window, async move |this, cx| {
7428 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7429 this.update(cx, |this, cx| {
7430 this.show_cursor_names = false;
7431 cx.notify()
7432 })
7433 .ok()
7434 })
7435 .detach();
7436 }
7437
7438 pub fn next_edit_prediction(
7439 &mut self,
7440 _: &NextEditPrediction,
7441 window: &mut Window,
7442 cx: &mut Context<Self>,
7443 ) {
7444 if self.has_active_edit_prediction() {
7445 self.cycle_edit_prediction(Direction::Next, window, cx);
7446 } else {
7447 let is_copilot_disabled = self
7448 .refresh_edit_prediction(false, true, window, cx)
7449 .is_none();
7450 if is_copilot_disabled {
7451 cx.propagate();
7452 }
7453 }
7454 }
7455
7456 pub fn previous_edit_prediction(
7457 &mut self,
7458 _: &PreviousEditPrediction,
7459 window: &mut Window,
7460 cx: &mut Context<Self>,
7461 ) {
7462 if self.has_active_edit_prediction() {
7463 self.cycle_edit_prediction(Direction::Prev, window, cx);
7464 } else {
7465 let is_copilot_disabled = self
7466 .refresh_edit_prediction(false, true, window, cx)
7467 .is_none();
7468 if is_copilot_disabled {
7469 cx.propagate();
7470 }
7471 }
7472 }
7473
7474 pub fn accept_edit_prediction(
7475 &mut self,
7476 _: &AcceptEditPrediction,
7477 window: &mut Window,
7478 cx: &mut Context<Self>,
7479 ) {
7480 if self.show_edit_predictions_in_menu() {
7481 self.hide_context_menu(window, cx);
7482 }
7483
7484 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7485 return;
7486 };
7487
7488 match &active_edit_prediction.completion {
7489 EditPrediction::MoveWithin { target, .. } => {
7490 let target = *target;
7491
7492 if let Some(position_map) = &self.last_position_map {
7493 if position_map
7494 .visible_row_range
7495 .contains(&target.to_display_point(&position_map.snapshot).row())
7496 || !self.edit_prediction_requires_modifier()
7497 {
7498 self.unfold_ranges(&[target..target], true, false, cx);
7499 // Note that this is also done in vim's handler of the Tab action.
7500 self.change_selections(
7501 SelectionEffects::scroll(Autoscroll::newest()),
7502 window,
7503 cx,
7504 |selections| {
7505 selections.select_anchor_ranges([target..target]);
7506 },
7507 );
7508 self.clear_row_highlights::<EditPredictionPreview>();
7509
7510 self.edit_prediction_preview
7511 .set_previous_scroll_position(None);
7512 } else {
7513 self.edit_prediction_preview
7514 .set_previous_scroll_position(Some(
7515 position_map.snapshot.scroll_anchor,
7516 ));
7517
7518 self.highlight_rows::<EditPredictionPreview>(
7519 target..target,
7520 cx.theme().colors().editor_highlighted_line_background,
7521 RowHighlightOptions {
7522 autoscroll: true,
7523 ..Default::default()
7524 },
7525 cx,
7526 );
7527 self.request_autoscroll(Autoscroll::fit(), cx);
7528 }
7529 }
7530 }
7531 EditPrediction::MoveOutside { snapshot, target } => {
7532 if let Some(workspace) = self.workspace() {
7533 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7534 .detach_and_log_err(cx);
7535 }
7536 }
7537 EditPrediction::Edit { edits, .. } => {
7538 self.report_edit_prediction_event(
7539 active_edit_prediction.completion_id.clone(),
7540 true,
7541 cx,
7542 );
7543
7544 if let Some(provider) = self.edit_prediction_provider() {
7545 provider.accept(cx);
7546 }
7547
7548 // Store the transaction ID and selections before applying the edit
7549 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7550
7551 let snapshot = self.buffer.read(cx).snapshot(cx);
7552 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7553
7554 self.buffer.update(cx, |buffer, cx| {
7555 buffer.edit(edits.iter().cloned(), None, cx)
7556 });
7557
7558 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7559 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7560 });
7561
7562 let selections = self.selections.disjoint_anchors_arc();
7563 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7564 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7565 if has_new_transaction {
7566 self.selection_history
7567 .insert_transaction(transaction_id_now, selections);
7568 }
7569 }
7570
7571 self.update_visible_edit_prediction(window, cx);
7572 if self.active_edit_prediction.is_none() {
7573 self.refresh_edit_prediction(true, true, window, cx);
7574 }
7575
7576 cx.notify();
7577 }
7578 }
7579
7580 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7581 }
7582
7583 pub fn accept_partial_edit_prediction(
7584 &mut self,
7585 _: &AcceptPartialEditPrediction,
7586 window: &mut Window,
7587 cx: &mut Context<Self>,
7588 ) {
7589 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7590 return;
7591 };
7592 if self.selections.count() != 1 {
7593 return;
7594 }
7595
7596 match &active_edit_prediction.completion {
7597 EditPrediction::MoveWithin { target, .. } => {
7598 let target = *target;
7599 self.change_selections(
7600 SelectionEffects::scroll(Autoscroll::newest()),
7601 window,
7602 cx,
7603 |selections| {
7604 selections.select_anchor_ranges([target..target]);
7605 },
7606 );
7607 }
7608 EditPrediction::MoveOutside { snapshot, target } => {
7609 if let Some(workspace) = self.workspace() {
7610 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7611 .detach_and_log_err(cx);
7612 }
7613 }
7614 EditPrediction::Edit { edits, .. } => {
7615 self.report_edit_prediction_event(
7616 active_edit_prediction.completion_id.clone(),
7617 true,
7618 cx,
7619 );
7620
7621 // Find an insertion that starts at the cursor position.
7622 let snapshot = self.buffer.read(cx).snapshot(cx);
7623 let cursor_offset = self
7624 .selections
7625 .newest::<usize>(&self.display_snapshot(cx))
7626 .head();
7627 let insertion = edits.iter().find_map(|(range, text)| {
7628 let range = range.to_offset(&snapshot);
7629 if range.is_empty() && range.start == cursor_offset {
7630 Some(text)
7631 } else {
7632 None
7633 }
7634 });
7635
7636 if let Some(text) = insertion {
7637 let mut partial_completion = text
7638 .chars()
7639 .by_ref()
7640 .take_while(|c| c.is_alphabetic())
7641 .collect::<String>();
7642 if partial_completion.is_empty() {
7643 partial_completion = text
7644 .chars()
7645 .by_ref()
7646 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7647 .collect::<String>();
7648 }
7649
7650 cx.emit(EditorEvent::InputHandled {
7651 utf16_range_to_replace: None,
7652 text: partial_completion.clone().into(),
7653 });
7654
7655 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7656
7657 self.refresh_edit_prediction(true, true, window, cx);
7658 cx.notify();
7659 } else {
7660 self.accept_edit_prediction(&Default::default(), window, cx);
7661 }
7662 }
7663 }
7664 }
7665
7666 fn discard_edit_prediction(
7667 &mut self,
7668 should_report_edit_prediction_event: bool,
7669 cx: &mut Context<Self>,
7670 ) -> bool {
7671 if should_report_edit_prediction_event {
7672 let completion_id = self
7673 .active_edit_prediction
7674 .as_ref()
7675 .and_then(|active_completion| active_completion.completion_id.clone());
7676
7677 self.report_edit_prediction_event(completion_id, false, cx);
7678 }
7679
7680 if let Some(provider) = self.edit_prediction_provider() {
7681 provider.discard(cx);
7682 }
7683
7684 self.take_active_edit_prediction(cx)
7685 }
7686
7687 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7688 let Some(provider) = self.edit_prediction_provider() else {
7689 return;
7690 };
7691
7692 let Some((_, buffer, _)) = self
7693 .buffer
7694 .read(cx)
7695 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7696 else {
7697 return;
7698 };
7699
7700 let extension = buffer
7701 .read(cx)
7702 .file()
7703 .and_then(|file| Some(file.path().extension()?.to_string()));
7704
7705 let event_type = match accepted {
7706 true => "Edit Prediction Accepted",
7707 false => "Edit Prediction Discarded",
7708 };
7709 telemetry::event!(
7710 event_type,
7711 provider = provider.name(),
7712 prediction_id = id,
7713 suggestion_accepted = accepted,
7714 file_extension = extension,
7715 );
7716 }
7717
7718 fn open_editor_at_anchor(
7719 snapshot: &language::BufferSnapshot,
7720 target: language::Anchor,
7721 workspace: &Entity<Workspace>,
7722 window: &mut Window,
7723 cx: &mut App,
7724 ) -> Task<Result<()>> {
7725 workspace.update(cx, |workspace, cx| {
7726 let path = snapshot.file().map(|file| file.full_path(cx));
7727 let Some(path) =
7728 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7729 else {
7730 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7731 };
7732 let target = text::ToPoint::to_point(&target, snapshot);
7733 let item = workspace.open_path(path, None, true, window, cx);
7734 window.spawn(cx, async move |cx| {
7735 let Some(editor) = item.await?.downcast::<Editor>() else {
7736 return Ok(());
7737 };
7738 editor
7739 .update_in(cx, |editor, window, cx| {
7740 editor.go_to_singleton_buffer_point(target, window, cx);
7741 })
7742 .ok();
7743 anyhow::Ok(())
7744 })
7745 })
7746 }
7747
7748 pub fn has_active_edit_prediction(&self) -> bool {
7749 self.active_edit_prediction.is_some()
7750 }
7751
7752 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7753 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7754 return false;
7755 };
7756
7757 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7758 self.clear_highlights::<EditPredictionHighlight>(cx);
7759 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7760 true
7761 }
7762
7763 /// Returns true when we're displaying the edit prediction popover below the cursor
7764 /// like we are not previewing and the LSP autocomplete menu is visible
7765 /// or we are in `when_holding_modifier` mode.
7766 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7767 if self.edit_prediction_preview_is_active()
7768 || !self.show_edit_predictions_in_menu()
7769 || !self.edit_predictions_enabled()
7770 {
7771 return false;
7772 }
7773
7774 if self.has_visible_completions_menu() {
7775 return true;
7776 }
7777
7778 has_completion && self.edit_prediction_requires_modifier()
7779 }
7780
7781 fn handle_modifiers_changed(
7782 &mut self,
7783 modifiers: Modifiers,
7784 position_map: &PositionMap,
7785 window: &mut Window,
7786 cx: &mut Context<Self>,
7787 ) {
7788 if self.show_edit_predictions_in_menu() {
7789 self.update_edit_prediction_preview(&modifiers, window, cx);
7790 }
7791
7792 self.update_selection_mode(&modifiers, position_map, window, cx);
7793
7794 let mouse_position = window.mouse_position();
7795 if !position_map.text_hitbox.is_hovered(window) {
7796 return;
7797 }
7798
7799 self.update_hovered_link(
7800 position_map.point_for_position(mouse_position),
7801 &position_map.snapshot,
7802 modifiers,
7803 window,
7804 cx,
7805 )
7806 }
7807
7808 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7809 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7810 if invert {
7811 match multi_cursor_setting {
7812 MultiCursorModifier::Alt => modifiers.alt,
7813 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7814 }
7815 } else {
7816 match multi_cursor_setting {
7817 MultiCursorModifier::Alt => modifiers.secondary(),
7818 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7819 }
7820 }
7821 }
7822
7823 fn columnar_selection_mode(
7824 modifiers: &Modifiers,
7825 cx: &mut Context<Self>,
7826 ) -> Option<ColumnarMode> {
7827 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7828 if Self::multi_cursor_modifier(false, modifiers, cx) {
7829 Some(ColumnarMode::FromMouse)
7830 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7831 Some(ColumnarMode::FromSelection)
7832 } else {
7833 None
7834 }
7835 } else {
7836 None
7837 }
7838 }
7839
7840 fn update_selection_mode(
7841 &mut self,
7842 modifiers: &Modifiers,
7843 position_map: &PositionMap,
7844 window: &mut Window,
7845 cx: &mut Context<Self>,
7846 ) {
7847 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7848 return;
7849 };
7850 if self.selections.pending_anchor().is_none() {
7851 return;
7852 }
7853
7854 let mouse_position = window.mouse_position();
7855 let point_for_position = position_map.point_for_position(mouse_position);
7856 let position = point_for_position.previous_valid;
7857
7858 self.select(
7859 SelectPhase::BeginColumnar {
7860 position,
7861 reset: false,
7862 mode,
7863 goal_column: point_for_position.exact_unclipped.column(),
7864 },
7865 window,
7866 cx,
7867 );
7868 }
7869
7870 fn update_edit_prediction_preview(
7871 &mut self,
7872 modifiers: &Modifiers,
7873 window: &mut Window,
7874 cx: &mut Context<Self>,
7875 ) {
7876 let mut modifiers_held = false;
7877 if let Some(accept_keystroke) = self
7878 .accept_edit_prediction_keybind(false, window, cx)
7879 .keystroke()
7880 {
7881 modifiers_held = modifiers_held
7882 || (accept_keystroke.modifiers() == modifiers
7883 && accept_keystroke.modifiers().modified());
7884 };
7885 if let Some(accept_partial_keystroke) = self
7886 .accept_edit_prediction_keybind(true, window, cx)
7887 .keystroke()
7888 {
7889 modifiers_held = modifiers_held
7890 || (accept_partial_keystroke.modifiers() == modifiers
7891 && accept_partial_keystroke.modifiers().modified());
7892 }
7893
7894 if modifiers_held {
7895 if matches!(
7896 self.edit_prediction_preview,
7897 EditPredictionPreview::Inactive { .. }
7898 ) {
7899 self.edit_prediction_preview = EditPredictionPreview::Active {
7900 previous_scroll_position: None,
7901 since: Instant::now(),
7902 };
7903
7904 self.update_visible_edit_prediction(window, cx);
7905 cx.notify();
7906 }
7907 } else if let EditPredictionPreview::Active {
7908 previous_scroll_position,
7909 since,
7910 } = self.edit_prediction_preview
7911 {
7912 if let (Some(previous_scroll_position), Some(position_map)) =
7913 (previous_scroll_position, self.last_position_map.as_ref())
7914 {
7915 self.set_scroll_position(
7916 previous_scroll_position
7917 .scroll_position(&position_map.snapshot.display_snapshot),
7918 window,
7919 cx,
7920 );
7921 }
7922
7923 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7924 released_too_fast: since.elapsed() < Duration::from_millis(200),
7925 };
7926 self.clear_row_highlights::<EditPredictionPreview>();
7927 self.update_visible_edit_prediction(window, cx);
7928 cx.notify();
7929 }
7930 }
7931
7932 fn update_visible_edit_prediction(
7933 &mut self,
7934 _window: &mut Window,
7935 cx: &mut Context<Self>,
7936 ) -> Option<()> {
7937 if DisableAiSettings::get_global(cx).disable_ai {
7938 return None;
7939 }
7940
7941 if self.ime_transaction.is_some() {
7942 self.discard_edit_prediction(false, cx);
7943 return None;
7944 }
7945
7946 let selection = self.selections.newest_anchor();
7947 let cursor = selection.head();
7948 let multibuffer = self.buffer.read(cx).snapshot(cx);
7949 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7950 let excerpt_id = cursor.excerpt_id;
7951
7952 let show_in_menu = self.show_edit_predictions_in_menu();
7953 let completions_menu_has_precedence = !show_in_menu
7954 && (self.context_menu.borrow().is_some()
7955 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7956
7957 if completions_menu_has_precedence
7958 || !offset_selection.is_empty()
7959 || self
7960 .active_edit_prediction
7961 .as_ref()
7962 .is_some_and(|completion| {
7963 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7964 return false;
7965 };
7966 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7967 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7968 !invalidation_range.contains(&offset_selection.head())
7969 })
7970 {
7971 self.discard_edit_prediction(false, cx);
7972 return None;
7973 }
7974
7975 self.take_active_edit_prediction(cx);
7976 let Some(provider) = self.edit_prediction_provider() else {
7977 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7978 return None;
7979 };
7980
7981 let (buffer, cursor_buffer_position) =
7982 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7983
7984 self.edit_prediction_settings =
7985 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7986
7987 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7988
7989 if self.edit_prediction_indent_conflict {
7990 let cursor_point = cursor.to_point(&multibuffer);
7991
7992 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7993
7994 if let Some((_, indent)) = indents.iter().next()
7995 && indent.len == cursor_point.column
7996 {
7997 self.edit_prediction_indent_conflict = false;
7998 }
7999 }
8000
8001 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8002
8003 let (completion_id, edits, edit_preview) = match edit_prediction {
8004 edit_prediction::EditPrediction::Local {
8005 id,
8006 edits,
8007 edit_preview,
8008 } => (id, edits, edit_preview),
8009 edit_prediction::EditPrediction::Jump {
8010 id,
8011 snapshot,
8012 target,
8013 } => {
8014 self.stale_edit_prediction_in_menu = None;
8015 self.active_edit_prediction = Some(EditPredictionState {
8016 inlay_ids: vec![],
8017 completion: EditPrediction::MoveOutside { snapshot, target },
8018 completion_id: id,
8019 invalidation_range: None,
8020 });
8021 cx.notify();
8022 return Some(());
8023 }
8024 };
8025
8026 let edits = edits
8027 .into_iter()
8028 .flat_map(|(range, new_text)| {
8029 Some((
8030 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8031 new_text,
8032 ))
8033 })
8034 .collect::<Vec<_>>();
8035 if edits.is_empty() {
8036 return None;
8037 }
8038
8039 let first_edit_start = edits.first().unwrap().0.start;
8040 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8041 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8042
8043 let last_edit_end = edits.last().unwrap().0.end;
8044 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8045 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8046
8047 let cursor_row = cursor.to_point(&multibuffer).row;
8048
8049 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8050
8051 let mut inlay_ids = Vec::new();
8052 let invalidation_row_range;
8053 let move_invalidation_row_range = if cursor_row < edit_start_row {
8054 Some(cursor_row..edit_end_row)
8055 } else if cursor_row > edit_end_row {
8056 Some(edit_start_row..cursor_row)
8057 } else {
8058 None
8059 };
8060 let supports_jump = self
8061 .edit_prediction_provider
8062 .as_ref()
8063 .map(|provider| provider.provider.supports_jump_to_edit())
8064 .unwrap_or(true);
8065
8066 let is_move = supports_jump
8067 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8068 let completion = if is_move {
8069 invalidation_row_range =
8070 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8071 let target = first_edit_start;
8072 EditPrediction::MoveWithin { target, snapshot }
8073 } else {
8074 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8075 && !self.edit_predictions_hidden_for_vim_mode;
8076
8077 if show_completions_in_buffer {
8078 if edits
8079 .iter()
8080 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8081 {
8082 let mut inlays = Vec::new();
8083 for (range, new_text) in &edits {
8084 let inlay = Inlay::edit_prediction(
8085 post_inc(&mut self.next_inlay_id),
8086 range.start,
8087 new_text.as_str(),
8088 );
8089 inlay_ids.push(inlay.id);
8090 inlays.push(inlay);
8091 }
8092
8093 self.splice_inlays(&[], inlays, cx);
8094 } else {
8095 let background_color = cx.theme().status().deleted_background;
8096 self.highlight_text::<EditPredictionHighlight>(
8097 edits.iter().map(|(range, _)| range.clone()).collect(),
8098 HighlightStyle {
8099 background_color: Some(background_color),
8100 ..Default::default()
8101 },
8102 cx,
8103 );
8104 }
8105 }
8106
8107 invalidation_row_range = edit_start_row..edit_end_row;
8108
8109 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8110 if provider.show_tab_accept_marker() {
8111 EditDisplayMode::TabAccept
8112 } else {
8113 EditDisplayMode::Inline
8114 }
8115 } else {
8116 EditDisplayMode::DiffPopover
8117 };
8118
8119 EditPrediction::Edit {
8120 edits,
8121 edit_preview,
8122 display_mode,
8123 snapshot,
8124 }
8125 };
8126
8127 let invalidation_range = multibuffer
8128 .anchor_before(Point::new(invalidation_row_range.start, 0))
8129 ..multibuffer.anchor_after(Point::new(
8130 invalidation_row_range.end,
8131 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8132 ));
8133
8134 self.stale_edit_prediction_in_menu = None;
8135 self.active_edit_prediction = Some(EditPredictionState {
8136 inlay_ids,
8137 completion,
8138 completion_id,
8139 invalidation_range: Some(invalidation_range),
8140 });
8141
8142 cx.notify();
8143
8144 Some(())
8145 }
8146
8147 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8148 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8149 }
8150
8151 fn clear_tasks(&mut self) {
8152 self.tasks.clear()
8153 }
8154
8155 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8156 if self.tasks.insert(key, value).is_some() {
8157 // This case should hopefully be rare, but just in case...
8158 log::error!(
8159 "multiple different run targets found on a single line, only the last target will be rendered"
8160 )
8161 }
8162 }
8163
8164 /// Get all display points of breakpoints that will be rendered within editor
8165 ///
8166 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8167 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8168 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8169 fn active_breakpoints(
8170 &self,
8171 range: Range<DisplayRow>,
8172 window: &mut Window,
8173 cx: &mut Context<Self>,
8174 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8175 let mut breakpoint_display_points = HashMap::default();
8176
8177 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8178 return breakpoint_display_points;
8179 };
8180
8181 let snapshot = self.snapshot(window, cx);
8182
8183 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
8184 let Some(project) = self.project() else {
8185 return breakpoint_display_points;
8186 };
8187
8188 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8189 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8190
8191 for (buffer_snapshot, range, excerpt_id) in
8192 multi_buffer_snapshot.range_to_buffer_ranges(range)
8193 {
8194 let Some(buffer) = project
8195 .read(cx)
8196 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8197 else {
8198 continue;
8199 };
8200 let breakpoints = breakpoint_store.read(cx).breakpoints(
8201 &buffer,
8202 Some(
8203 buffer_snapshot.anchor_before(range.start)
8204 ..buffer_snapshot.anchor_after(range.end),
8205 ),
8206 buffer_snapshot,
8207 cx,
8208 );
8209 for (breakpoint, state) in breakpoints {
8210 let multi_buffer_anchor =
8211 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8212 let position = multi_buffer_anchor
8213 .to_point(multi_buffer_snapshot)
8214 .to_display_point(&snapshot);
8215
8216 breakpoint_display_points.insert(
8217 position.row(),
8218 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8219 );
8220 }
8221 }
8222
8223 breakpoint_display_points
8224 }
8225
8226 fn breakpoint_context_menu(
8227 &self,
8228 anchor: Anchor,
8229 window: &mut Window,
8230 cx: &mut Context<Self>,
8231 ) -> Entity<ui::ContextMenu> {
8232 let weak_editor = cx.weak_entity();
8233 let focus_handle = self.focus_handle(cx);
8234
8235 let row = self
8236 .buffer
8237 .read(cx)
8238 .snapshot(cx)
8239 .summary_for_anchor::<Point>(&anchor)
8240 .row;
8241
8242 let breakpoint = self
8243 .breakpoint_at_row(row, window, cx)
8244 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8245
8246 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8247 "Edit Log Breakpoint"
8248 } else {
8249 "Set Log Breakpoint"
8250 };
8251
8252 let condition_breakpoint_msg = if breakpoint
8253 .as_ref()
8254 .is_some_and(|bp| bp.1.condition.is_some())
8255 {
8256 "Edit Condition Breakpoint"
8257 } else {
8258 "Set Condition Breakpoint"
8259 };
8260
8261 let hit_condition_breakpoint_msg = if breakpoint
8262 .as_ref()
8263 .is_some_and(|bp| bp.1.hit_condition.is_some())
8264 {
8265 "Edit Hit Condition Breakpoint"
8266 } else {
8267 "Set Hit Condition Breakpoint"
8268 };
8269
8270 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8271 "Unset Breakpoint"
8272 } else {
8273 "Set Breakpoint"
8274 };
8275
8276 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8277
8278 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8279 BreakpointState::Enabled => Some("Disable"),
8280 BreakpointState::Disabled => Some("Enable"),
8281 });
8282
8283 let (anchor, breakpoint) =
8284 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8285
8286 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8287 menu.on_blur_subscription(Subscription::new(|| {}))
8288 .context(focus_handle)
8289 .when(run_to_cursor, |this| {
8290 let weak_editor = weak_editor.clone();
8291 this.entry("Run to cursor", None, move |window, cx| {
8292 weak_editor
8293 .update(cx, |editor, cx| {
8294 editor.change_selections(
8295 SelectionEffects::no_scroll(),
8296 window,
8297 cx,
8298 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8299 );
8300 })
8301 .ok();
8302
8303 window.dispatch_action(Box::new(RunToCursor), cx);
8304 })
8305 .separator()
8306 })
8307 .when_some(toggle_state_msg, |this, msg| {
8308 this.entry(msg, None, {
8309 let weak_editor = weak_editor.clone();
8310 let breakpoint = breakpoint.clone();
8311 move |_window, cx| {
8312 weak_editor
8313 .update(cx, |this, cx| {
8314 this.edit_breakpoint_at_anchor(
8315 anchor,
8316 breakpoint.as_ref().clone(),
8317 BreakpointEditAction::InvertState,
8318 cx,
8319 );
8320 })
8321 .log_err();
8322 }
8323 })
8324 })
8325 .entry(set_breakpoint_msg, None, {
8326 let weak_editor = weak_editor.clone();
8327 let breakpoint = breakpoint.clone();
8328 move |_window, cx| {
8329 weak_editor
8330 .update(cx, |this, cx| {
8331 this.edit_breakpoint_at_anchor(
8332 anchor,
8333 breakpoint.as_ref().clone(),
8334 BreakpointEditAction::Toggle,
8335 cx,
8336 );
8337 })
8338 .log_err();
8339 }
8340 })
8341 .entry(log_breakpoint_msg, None, {
8342 let breakpoint = breakpoint.clone();
8343 let weak_editor = weak_editor.clone();
8344 move |window, cx| {
8345 weak_editor
8346 .update(cx, |this, cx| {
8347 this.add_edit_breakpoint_block(
8348 anchor,
8349 breakpoint.as_ref(),
8350 BreakpointPromptEditAction::Log,
8351 window,
8352 cx,
8353 );
8354 })
8355 .log_err();
8356 }
8357 })
8358 .entry(condition_breakpoint_msg, None, {
8359 let breakpoint = breakpoint.clone();
8360 let weak_editor = weak_editor.clone();
8361 move |window, cx| {
8362 weak_editor
8363 .update(cx, |this, cx| {
8364 this.add_edit_breakpoint_block(
8365 anchor,
8366 breakpoint.as_ref(),
8367 BreakpointPromptEditAction::Condition,
8368 window,
8369 cx,
8370 );
8371 })
8372 .log_err();
8373 }
8374 })
8375 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8376 weak_editor
8377 .update(cx, |this, cx| {
8378 this.add_edit_breakpoint_block(
8379 anchor,
8380 breakpoint.as_ref(),
8381 BreakpointPromptEditAction::HitCondition,
8382 window,
8383 cx,
8384 );
8385 })
8386 .log_err();
8387 })
8388 })
8389 }
8390
8391 fn render_breakpoint(
8392 &self,
8393 position: Anchor,
8394 row: DisplayRow,
8395 breakpoint: &Breakpoint,
8396 state: Option<BreakpointSessionState>,
8397 cx: &mut Context<Self>,
8398 ) -> IconButton {
8399 let is_rejected = state.is_some_and(|s| !s.verified);
8400 // Is it a breakpoint that shows up when hovering over gutter?
8401 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8402 (false, false),
8403 |PhantomBreakpointIndicator {
8404 is_active,
8405 display_row,
8406 collides_with_existing_breakpoint,
8407 }| {
8408 (
8409 is_active && display_row == row,
8410 collides_with_existing_breakpoint,
8411 )
8412 },
8413 );
8414
8415 let (color, icon) = {
8416 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8417 (false, false) => ui::IconName::DebugBreakpoint,
8418 (true, false) => ui::IconName::DebugLogBreakpoint,
8419 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8420 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8421 };
8422
8423 let color = if is_phantom {
8424 Color::Hint
8425 } else if is_rejected {
8426 Color::Disabled
8427 } else {
8428 Color::Debugger
8429 };
8430
8431 (color, icon)
8432 };
8433
8434 let breakpoint = Arc::from(breakpoint.clone());
8435
8436 let alt_as_text = gpui::Keystroke {
8437 modifiers: Modifiers::secondary_key(),
8438 ..Default::default()
8439 };
8440 let primary_action_text = if breakpoint.is_disabled() {
8441 "Enable breakpoint"
8442 } else if is_phantom && !collides_with_existing {
8443 "Set breakpoint"
8444 } else {
8445 "Unset breakpoint"
8446 };
8447 let focus_handle = self.focus_handle.clone();
8448
8449 let meta = if is_rejected {
8450 SharedString::from("No executable code is associated with this line.")
8451 } else if collides_with_existing && !breakpoint.is_disabled() {
8452 SharedString::from(format!(
8453 "{alt_as_text}-click to disable,\nright-click for more options."
8454 ))
8455 } else {
8456 SharedString::from("Right-click for more options.")
8457 };
8458 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8459 .icon_size(IconSize::XSmall)
8460 .size(ui::ButtonSize::None)
8461 .when(is_rejected, |this| {
8462 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8463 })
8464 .icon_color(color)
8465 .style(ButtonStyle::Transparent)
8466 .on_click(cx.listener({
8467 move |editor, event: &ClickEvent, window, cx| {
8468 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8469 BreakpointEditAction::InvertState
8470 } else {
8471 BreakpointEditAction::Toggle
8472 };
8473
8474 window.focus(&editor.focus_handle(cx));
8475 editor.edit_breakpoint_at_anchor(
8476 position,
8477 breakpoint.as_ref().clone(),
8478 edit_action,
8479 cx,
8480 );
8481 }
8482 }))
8483 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8484 editor.set_breakpoint_context_menu(
8485 row,
8486 Some(position),
8487 event.position(),
8488 window,
8489 cx,
8490 );
8491 }))
8492 .tooltip(move |window, cx| {
8493 Tooltip::with_meta_in(
8494 primary_action_text,
8495 Some(&ToggleBreakpoint),
8496 meta.clone(),
8497 &focus_handle,
8498 window,
8499 cx,
8500 )
8501 })
8502 }
8503
8504 fn build_tasks_context(
8505 project: &Entity<Project>,
8506 buffer: &Entity<Buffer>,
8507 buffer_row: u32,
8508 tasks: &Arc<RunnableTasks>,
8509 cx: &mut Context<Self>,
8510 ) -> Task<Option<task::TaskContext>> {
8511 let position = Point::new(buffer_row, tasks.column);
8512 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8513 let location = Location {
8514 buffer: buffer.clone(),
8515 range: range_start..range_start,
8516 };
8517 // Fill in the environmental variables from the tree-sitter captures
8518 let mut captured_task_variables = TaskVariables::default();
8519 for (capture_name, value) in tasks.extra_variables.clone() {
8520 captured_task_variables.insert(
8521 task::VariableName::Custom(capture_name.into()),
8522 value.clone(),
8523 );
8524 }
8525 project.update(cx, |project, cx| {
8526 project.task_store().update(cx, |task_store, cx| {
8527 task_store.task_context_for_location(captured_task_variables, location, cx)
8528 })
8529 })
8530 }
8531
8532 pub fn spawn_nearest_task(
8533 &mut self,
8534 action: &SpawnNearestTask,
8535 window: &mut Window,
8536 cx: &mut Context<Self>,
8537 ) {
8538 let Some((workspace, _)) = self.workspace.clone() else {
8539 return;
8540 };
8541 let Some(project) = self.project.clone() else {
8542 return;
8543 };
8544
8545 // Try to find a closest, enclosing node using tree-sitter that has a task
8546 let Some((buffer, buffer_row, tasks)) = self
8547 .find_enclosing_node_task(cx)
8548 // Or find the task that's closest in row-distance.
8549 .or_else(|| self.find_closest_task(cx))
8550 else {
8551 return;
8552 };
8553
8554 let reveal_strategy = action.reveal;
8555 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8556 cx.spawn_in(window, async move |_, cx| {
8557 let context = task_context.await?;
8558 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8559
8560 let resolved = &mut resolved_task.resolved;
8561 resolved.reveal = reveal_strategy;
8562
8563 workspace
8564 .update_in(cx, |workspace, window, cx| {
8565 workspace.schedule_resolved_task(
8566 task_source_kind,
8567 resolved_task,
8568 false,
8569 window,
8570 cx,
8571 );
8572 })
8573 .ok()
8574 })
8575 .detach();
8576 }
8577
8578 fn find_closest_task(
8579 &mut self,
8580 cx: &mut Context<Self>,
8581 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8582 let cursor_row = self
8583 .selections
8584 .newest_adjusted(&self.display_snapshot(cx))
8585 .head()
8586 .row;
8587
8588 let ((buffer_id, row), tasks) = self
8589 .tasks
8590 .iter()
8591 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8592
8593 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8594 let tasks = Arc::new(tasks.to_owned());
8595 Some((buffer, *row, tasks))
8596 }
8597
8598 fn find_enclosing_node_task(
8599 &mut self,
8600 cx: &mut Context<Self>,
8601 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8602 let snapshot = self.buffer.read(cx).snapshot(cx);
8603 let offset = self
8604 .selections
8605 .newest::<usize>(&self.display_snapshot(cx))
8606 .head();
8607 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8608 let buffer_id = excerpt.buffer().remote_id();
8609
8610 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8611 let mut cursor = layer.node().walk();
8612
8613 while cursor.goto_first_child_for_byte(offset).is_some() {
8614 if cursor.node().end_byte() == offset {
8615 cursor.goto_next_sibling();
8616 }
8617 }
8618
8619 // Ascend to the smallest ancestor that contains the range and has a task.
8620 loop {
8621 let node = cursor.node();
8622 let node_range = node.byte_range();
8623 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8624
8625 // Check if this node contains our offset
8626 if node_range.start <= offset && node_range.end >= offset {
8627 // If it contains offset, check for task
8628 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8629 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8630 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8631 }
8632 }
8633
8634 if !cursor.goto_parent() {
8635 break;
8636 }
8637 }
8638 None
8639 }
8640
8641 fn render_run_indicator(
8642 &self,
8643 _style: &EditorStyle,
8644 is_active: bool,
8645 row: DisplayRow,
8646 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8647 cx: &mut Context<Self>,
8648 ) -> IconButton {
8649 let color = Color::Muted;
8650 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8651
8652 IconButton::new(
8653 ("run_indicator", row.0 as usize),
8654 ui::IconName::PlayOutlined,
8655 )
8656 .shape(ui::IconButtonShape::Square)
8657 .icon_size(IconSize::XSmall)
8658 .icon_color(color)
8659 .toggle_state(is_active)
8660 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8661 let quick_launch = match e {
8662 ClickEvent::Keyboard(_) => true,
8663 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8664 };
8665
8666 window.focus(&editor.focus_handle(cx));
8667 editor.toggle_code_actions(
8668 &ToggleCodeActions {
8669 deployed_from: Some(CodeActionSource::RunMenu(row)),
8670 quick_launch,
8671 },
8672 window,
8673 cx,
8674 );
8675 }))
8676 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8677 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8678 }))
8679 }
8680
8681 pub fn context_menu_visible(&self) -> bool {
8682 !self.edit_prediction_preview_is_active()
8683 && self
8684 .context_menu
8685 .borrow()
8686 .as_ref()
8687 .is_some_and(|menu| menu.visible())
8688 }
8689
8690 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8691 self.context_menu
8692 .borrow()
8693 .as_ref()
8694 .map(|menu| menu.origin())
8695 }
8696
8697 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8698 self.context_menu_options = Some(options);
8699 }
8700
8701 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8702 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8703
8704 fn render_edit_prediction_popover(
8705 &mut self,
8706 text_bounds: &Bounds<Pixels>,
8707 content_origin: gpui::Point<Pixels>,
8708 right_margin: Pixels,
8709 editor_snapshot: &EditorSnapshot,
8710 visible_row_range: Range<DisplayRow>,
8711 scroll_top: ScrollOffset,
8712 scroll_bottom: ScrollOffset,
8713 line_layouts: &[LineWithInvisibles],
8714 line_height: Pixels,
8715 scroll_position: gpui::Point<ScrollOffset>,
8716 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8717 newest_selection_head: Option<DisplayPoint>,
8718 editor_width: Pixels,
8719 style: &EditorStyle,
8720 window: &mut Window,
8721 cx: &mut App,
8722 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8723 if self.mode().is_minimap() {
8724 return None;
8725 }
8726 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8727
8728 if self.edit_prediction_visible_in_cursor_popover(true) {
8729 return None;
8730 }
8731
8732 match &active_edit_prediction.completion {
8733 EditPrediction::MoveWithin { target, .. } => {
8734 let target_display_point = target.to_display_point(editor_snapshot);
8735
8736 if self.edit_prediction_requires_modifier() {
8737 if !self.edit_prediction_preview_is_active() {
8738 return None;
8739 }
8740
8741 self.render_edit_prediction_modifier_jump_popover(
8742 text_bounds,
8743 content_origin,
8744 visible_row_range,
8745 line_layouts,
8746 line_height,
8747 scroll_pixel_position,
8748 newest_selection_head,
8749 target_display_point,
8750 window,
8751 cx,
8752 )
8753 } else {
8754 self.render_edit_prediction_eager_jump_popover(
8755 text_bounds,
8756 content_origin,
8757 editor_snapshot,
8758 visible_row_range,
8759 scroll_top,
8760 scroll_bottom,
8761 line_height,
8762 scroll_pixel_position,
8763 target_display_point,
8764 editor_width,
8765 window,
8766 cx,
8767 )
8768 }
8769 }
8770 EditPrediction::Edit {
8771 display_mode: EditDisplayMode::Inline,
8772 ..
8773 } => None,
8774 EditPrediction::Edit {
8775 display_mode: EditDisplayMode::TabAccept,
8776 edits,
8777 ..
8778 } => {
8779 let range = &edits.first()?.0;
8780 let target_display_point = range.end.to_display_point(editor_snapshot);
8781
8782 self.render_edit_prediction_end_of_line_popover(
8783 "Accept",
8784 editor_snapshot,
8785 visible_row_range,
8786 target_display_point,
8787 line_height,
8788 scroll_pixel_position,
8789 content_origin,
8790 editor_width,
8791 window,
8792 cx,
8793 )
8794 }
8795 EditPrediction::Edit {
8796 edits,
8797 edit_preview,
8798 display_mode: EditDisplayMode::DiffPopover,
8799 snapshot,
8800 } => self.render_edit_prediction_diff_popover(
8801 text_bounds,
8802 content_origin,
8803 right_margin,
8804 editor_snapshot,
8805 visible_row_range,
8806 line_layouts,
8807 line_height,
8808 scroll_position,
8809 scroll_pixel_position,
8810 newest_selection_head,
8811 editor_width,
8812 style,
8813 edits,
8814 edit_preview,
8815 snapshot,
8816 window,
8817 cx,
8818 ),
8819 EditPrediction::MoveOutside { snapshot, .. } => {
8820 let file_name = snapshot
8821 .file()
8822 .map(|file| file.file_name(cx))
8823 .unwrap_or("untitled");
8824 let mut element = self
8825 .render_edit_prediction_line_popover(
8826 format!("Jump to {file_name}"),
8827 Some(IconName::ZedPredict),
8828 window,
8829 cx,
8830 )
8831 .into_any();
8832
8833 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8834 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8835 let origin_y = text_bounds.size.height - size.height - px(30.);
8836 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8837 element.prepaint_at(origin, window, cx);
8838
8839 Some((element, origin))
8840 }
8841 }
8842 }
8843
8844 fn render_edit_prediction_modifier_jump_popover(
8845 &mut self,
8846 text_bounds: &Bounds<Pixels>,
8847 content_origin: gpui::Point<Pixels>,
8848 visible_row_range: Range<DisplayRow>,
8849 line_layouts: &[LineWithInvisibles],
8850 line_height: Pixels,
8851 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8852 newest_selection_head: Option<DisplayPoint>,
8853 target_display_point: DisplayPoint,
8854 window: &mut Window,
8855 cx: &mut App,
8856 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8857 let scrolled_content_origin =
8858 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8859
8860 const SCROLL_PADDING_Y: Pixels = px(12.);
8861
8862 if target_display_point.row() < visible_row_range.start {
8863 return self.render_edit_prediction_scroll_popover(
8864 |_| SCROLL_PADDING_Y,
8865 IconName::ArrowUp,
8866 visible_row_range,
8867 line_layouts,
8868 newest_selection_head,
8869 scrolled_content_origin,
8870 window,
8871 cx,
8872 );
8873 } else if target_display_point.row() >= visible_row_range.end {
8874 return self.render_edit_prediction_scroll_popover(
8875 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8876 IconName::ArrowDown,
8877 visible_row_range,
8878 line_layouts,
8879 newest_selection_head,
8880 scrolled_content_origin,
8881 window,
8882 cx,
8883 );
8884 }
8885
8886 const POLE_WIDTH: Pixels = px(2.);
8887
8888 let line_layout =
8889 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8890 let target_column = target_display_point.column() as usize;
8891
8892 let target_x = line_layout.x_for_index(target_column);
8893 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8894 - scroll_pixel_position.y;
8895
8896 let flag_on_right = target_x < text_bounds.size.width / 2.;
8897
8898 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8899 border_color.l += 0.001;
8900
8901 let mut element = v_flex()
8902 .items_end()
8903 .when(flag_on_right, |el| el.items_start())
8904 .child(if flag_on_right {
8905 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8906 .rounded_bl(px(0.))
8907 .rounded_tl(px(0.))
8908 .border_l_2()
8909 .border_color(border_color)
8910 } else {
8911 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8912 .rounded_br(px(0.))
8913 .rounded_tr(px(0.))
8914 .border_r_2()
8915 .border_color(border_color)
8916 })
8917 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8918 .into_any();
8919
8920 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8921
8922 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8923 - point(
8924 if flag_on_right {
8925 POLE_WIDTH
8926 } else {
8927 size.width - POLE_WIDTH
8928 },
8929 size.height - line_height,
8930 );
8931
8932 origin.x = origin.x.max(content_origin.x);
8933
8934 element.prepaint_at(origin, window, cx);
8935
8936 Some((element, origin))
8937 }
8938
8939 fn render_edit_prediction_scroll_popover(
8940 &mut self,
8941 to_y: impl Fn(Size<Pixels>) -> Pixels,
8942 scroll_icon: IconName,
8943 visible_row_range: Range<DisplayRow>,
8944 line_layouts: &[LineWithInvisibles],
8945 newest_selection_head: Option<DisplayPoint>,
8946 scrolled_content_origin: gpui::Point<Pixels>,
8947 window: &mut Window,
8948 cx: &mut App,
8949 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8950 let mut element = self
8951 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8952 .into_any();
8953
8954 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8955
8956 let cursor = newest_selection_head?;
8957 let cursor_row_layout =
8958 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8959 let cursor_column = cursor.column() as usize;
8960
8961 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8962
8963 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8964
8965 element.prepaint_at(origin, window, cx);
8966 Some((element, origin))
8967 }
8968
8969 fn render_edit_prediction_eager_jump_popover(
8970 &mut self,
8971 text_bounds: &Bounds<Pixels>,
8972 content_origin: gpui::Point<Pixels>,
8973 editor_snapshot: &EditorSnapshot,
8974 visible_row_range: Range<DisplayRow>,
8975 scroll_top: ScrollOffset,
8976 scroll_bottom: ScrollOffset,
8977 line_height: Pixels,
8978 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8979 target_display_point: DisplayPoint,
8980 editor_width: Pixels,
8981 window: &mut Window,
8982 cx: &mut App,
8983 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8984 if target_display_point.row().as_f64() < scroll_top {
8985 let mut element = self
8986 .render_edit_prediction_line_popover(
8987 "Jump to Edit",
8988 Some(IconName::ArrowUp),
8989 window,
8990 cx,
8991 )
8992 .into_any();
8993
8994 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8995 let offset = point(
8996 (text_bounds.size.width - size.width) / 2.,
8997 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8998 );
8999
9000 let origin = text_bounds.origin + offset;
9001 element.prepaint_at(origin, window, cx);
9002 Some((element, origin))
9003 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9004 let mut element = self
9005 .render_edit_prediction_line_popover(
9006 "Jump to Edit",
9007 Some(IconName::ArrowDown),
9008 window,
9009 cx,
9010 )
9011 .into_any();
9012
9013 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9014 let offset = point(
9015 (text_bounds.size.width - size.width) / 2.,
9016 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9017 );
9018
9019 let origin = text_bounds.origin + offset;
9020 element.prepaint_at(origin, window, cx);
9021 Some((element, origin))
9022 } else {
9023 self.render_edit_prediction_end_of_line_popover(
9024 "Jump to Edit",
9025 editor_snapshot,
9026 visible_row_range,
9027 target_display_point,
9028 line_height,
9029 scroll_pixel_position,
9030 content_origin,
9031 editor_width,
9032 window,
9033 cx,
9034 )
9035 }
9036 }
9037
9038 fn render_edit_prediction_end_of_line_popover(
9039 self: &mut Editor,
9040 label: &'static str,
9041 editor_snapshot: &EditorSnapshot,
9042 visible_row_range: Range<DisplayRow>,
9043 target_display_point: DisplayPoint,
9044 line_height: Pixels,
9045 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9046 content_origin: gpui::Point<Pixels>,
9047 editor_width: Pixels,
9048 window: &mut Window,
9049 cx: &mut App,
9050 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9051 let target_line_end = DisplayPoint::new(
9052 target_display_point.row(),
9053 editor_snapshot.line_len(target_display_point.row()),
9054 );
9055
9056 let mut element = self
9057 .render_edit_prediction_line_popover(label, None, window, cx)
9058 .into_any();
9059
9060 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9061
9062 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9063
9064 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9065 let mut origin = start_point
9066 + line_origin
9067 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9068 origin.x = origin.x.max(content_origin.x);
9069
9070 let max_x = content_origin.x + editor_width - size.width;
9071
9072 if origin.x > max_x {
9073 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9074
9075 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9076 origin.y += offset;
9077 IconName::ArrowUp
9078 } else {
9079 origin.y -= offset;
9080 IconName::ArrowDown
9081 };
9082
9083 element = self
9084 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9085 .into_any();
9086
9087 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9088
9089 origin.x = content_origin.x + editor_width - size.width - px(2.);
9090 }
9091
9092 element.prepaint_at(origin, window, cx);
9093 Some((element, origin))
9094 }
9095
9096 fn render_edit_prediction_diff_popover(
9097 self: &Editor,
9098 text_bounds: &Bounds<Pixels>,
9099 content_origin: gpui::Point<Pixels>,
9100 right_margin: Pixels,
9101 editor_snapshot: &EditorSnapshot,
9102 visible_row_range: Range<DisplayRow>,
9103 line_layouts: &[LineWithInvisibles],
9104 line_height: Pixels,
9105 scroll_position: gpui::Point<ScrollOffset>,
9106 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9107 newest_selection_head: Option<DisplayPoint>,
9108 editor_width: Pixels,
9109 style: &EditorStyle,
9110 edits: &Vec<(Range<Anchor>, String)>,
9111 edit_preview: &Option<language::EditPreview>,
9112 snapshot: &language::BufferSnapshot,
9113 window: &mut Window,
9114 cx: &mut App,
9115 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9116 let edit_start = edits
9117 .first()
9118 .unwrap()
9119 .0
9120 .start
9121 .to_display_point(editor_snapshot);
9122 let edit_end = edits
9123 .last()
9124 .unwrap()
9125 .0
9126 .end
9127 .to_display_point(editor_snapshot);
9128
9129 let is_visible = visible_row_range.contains(&edit_start.row())
9130 || visible_row_range.contains(&edit_end.row());
9131 if !is_visible {
9132 return None;
9133 }
9134
9135 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9136 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9137 } else {
9138 // Fallback for providers without edit_preview
9139 crate::edit_prediction_fallback_text(edits, cx)
9140 };
9141
9142 let styled_text = highlighted_edits.to_styled_text(&style.text);
9143 let line_count = highlighted_edits.text.lines().count();
9144
9145 const BORDER_WIDTH: Pixels = px(1.);
9146
9147 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9148 let has_keybind = keybind.is_some();
9149
9150 let mut element = h_flex()
9151 .items_start()
9152 .child(
9153 h_flex()
9154 .bg(cx.theme().colors().editor_background)
9155 .border(BORDER_WIDTH)
9156 .shadow_xs()
9157 .border_color(cx.theme().colors().border)
9158 .rounded_l_lg()
9159 .when(line_count > 1, |el| el.rounded_br_lg())
9160 .pr_1()
9161 .child(styled_text),
9162 )
9163 .child(
9164 h_flex()
9165 .h(line_height + BORDER_WIDTH * 2.)
9166 .px_1p5()
9167 .gap_1()
9168 // Workaround: For some reason, there's a gap if we don't do this
9169 .ml(-BORDER_WIDTH)
9170 .shadow(vec![gpui::BoxShadow {
9171 color: gpui::black().opacity(0.05),
9172 offset: point(px(1.), px(1.)),
9173 blur_radius: px(2.),
9174 spread_radius: px(0.),
9175 }])
9176 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9177 .border(BORDER_WIDTH)
9178 .border_color(cx.theme().colors().border)
9179 .rounded_r_lg()
9180 .id("edit_prediction_diff_popover_keybind")
9181 .when(!has_keybind, |el| {
9182 let status_colors = cx.theme().status();
9183
9184 el.bg(status_colors.error_background)
9185 .border_color(status_colors.error.opacity(0.6))
9186 .child(Icon::new(IconName::Info).color(Color::Error))
9187 .cursor_default()
9188 .hoverable_tooltip(move |_window, cx| {
9189 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9190 })
9191 })
9192 .children(keybind),
9193 )
9194 .into_any();
9195
9196 let longest_row =
9197 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9198 let longest_line_width = if visible_row_range.contains(&longest_row) {
9199 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9200 } else {
9201 layout_line(
9202 longest_row,
9203 editor_snapshot,
9204 style,
9205 editor_width,
9206 |_| false,
9207 window,
9208 cx,
9209 )
9210 .width
9211 };
9212
9213 let viewport_bounds =
9214 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9215 right: -right_margin,
9216 ..Default::default()
9217 });
9218
9219 let x_after_longest = Pixels::from(
9220 ScrollPixelOffset::from(
9221 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9222 ) - scroll_pixel_position.x,
9223 );
9224
9225 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9226
9227 // Fully visible if it can be displayed within the window (allow overlapping other
9228 // panes). However, this is only allowed if the popover starts within text_bounds.
9229 let can_position_to_the_right = x_after_longest < text_bounds.right()
9230 && x_after_longest + element_bounds.width < viewport_bounds.right();
9231
9232 let mut origin = if can_position_to_the_right {
9233 point(
9234 x_after_longest,
9235 text_bounds.origin.y
9236 + Pixels::from(
9237 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9238 - scroll_pixel_position.y,
9239 ),
9240 )
9241 } else {
9242 let cursor_row = newest_selection_head.map(|head| head.row());
9243 let above_edit = edit_start
9244 .row()
9245 .0
9246 .checked_sub(line_count as u32)
9247 .map(DisplayRow);
9248 let below_edit = Some(edit_end.row() + 1);
9249 let above_cursor =
9250 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9251 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9252
9253 // Place the edit popover adjacent to the edit if there is a location
9254 // available that is onscreen and does not obscure the cursor. Otherwise,
9255 // place it adjacent to the cursor.
9256 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9257 .into_iter()
9258 .flatten()
9259 .find(|&start_row| {
9260 let end_row = start_row + line_count as u32;
9261 visible_row_range.contains(&start_row)
9262 && visible_row_range.contains(&end_row)
9263 && cursor_row
9264 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9265 })?;
9266
9267 content_origin
9268 + point(
9269 Pixels::from(-scroll_pixel_position.x),
9270 Pixels::from(
9271 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9272 ),
9273 )
9274 };
9275
9276 origin.x -= BORDER_WIDTH;
9277
9278 window.defer_draw(element, origin, 1);
9279
9280 // Do not return an element, since it will already be drawn due to defer_draw.
9281 None
9282 }
9283
9284 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9285 px(30.)
9286 }
9287
9288 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9289 if self.read_only(cx) {
9290 cx.theme().players().read_only()
9291 } else {
9292 self.style.as_ref().unwrap().local_player
9293 }
9294 }
9295
9296 fn render_edit_prediction_accept_keybind(
9297 &self,
9298 window: &mut Window,
9299 cx: &mut App,
9300 ) -> Option<AnyElement> {
9301 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9302 let accept_keystroke = accept_binding.keystroke()?;
9303
9304 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9305
9306 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9307 Color::Accent
9308 } else {
9309 Color::Muted
9310 };
9311
9312 h_flex()
9313 .px_0p5()
9314 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9315 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9316 .text_size(TextSize::XSmall.rems(cx))
9317 .child(h_flex().children(ui::render_modifiers(
9318 accept_keystroke.modifiers(),
9319 PlatformStyle::platform(),
9320 Some(modifiers_color),
9321 Some(IconSize::XSmall.rems().into()),
9322 true,
9323 )))
9324 .when(is_platform_style_mac, |parent| {
9325 parent.child(accept_keystroke.key().to_string())
9326 })
9327 .when(!is_platform_style_mac, |parent| {
9328 parent.child(
9329 Key::new(
9330 util::capitalize(accept_keystroke.key()),
9331 Some(Color::Default),
9332 )
9333 .size(Some(IconSize::XSmall.rems().into())),
9334 )
9335 })
9336 .into_any()
9337 .into()
9338 }
9339
9340 fn render_edit_prediction_line_popover(
9341 &self,
9342 label: impl Into<SharedString>,
9343 icon: Option<IconName>,
9344 window: &mut Window,
9345 cx: &mut App,
9346 ) -> Stateful<Div> {
9347 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9348
9349 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9350 let has_keybind = keybind.is_some();
9351
9352 h_flex()
9353 .id("ep-line-popover")
9354 .py_0p5()
9355 .pl_1()
9356 .pr(padding_right)
9357 .gap_1()
9358 .rounded_md()
9359 .border_1()
9360 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9361 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9362 .shadow_xs()
9363 .when(!has_keybind, |el| {
9364 let status_colors = cx.theme().status();
9365
9366 el.bg(status_colors.error_background)
9367 .border_color(status_colors.error.opacity(0.6))
9368 .pl_2()
9369 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9370 .cursor_default()
9371 .hoverable_tooltip(move |_window, cx| {
9372 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9373 })
9374 })
9375 .children(keybind)
9376 .child(
9377 Label::new(label)
9378 .size(LabelSize::Small)
9379 .when(!has_keybind, |el| {
9380 el.color(cx.theme().status().error.into()).strikethrough()
9381 }),
9382 )
9383 .when(!has_keybind, |el| {
9384 el.child(
9385 h_flex().ml_1().child(
9386 Icon::new(IconName::Info)
9387 .size(IconSize::Small)
9388 .color(cx.theme().status().error.into()),
9389 ),
9390 )
9391 })
9392 .when_some(icon, |element, icon| {
9393 element.child(
9394 div()
9395 .mt(px(1.5))
9396 .child(Icon::new(icon).size(IconSize::Small)),
9397 )
9398 })
9399 }
9400
9401 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9402 let accent_color = cx.theme().colors().text_accent;
9403 let editor_bg_color = cx.theme().colors().editor_background;
9404 editor_bg_color.blend(accent_color.opacity(0.1))
9405 }
9406
9407 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9408 let accent_color = cx.theme().colors().text_accent;
9409 let editor_bg_color = cx.theme().colors().editor_background;
9410 editor_bg_color.blend(accent_color.opacity(0.6))
9411 }
9412 fn get_prediction_provider_icon_name(
9413 provider: &Option<RegisteredEditPredictionProvider>,
9414 ) -> IconName {
9415 match provider {
9416 Some(provider) => match provider.provider.name() {
9417 "copilot" => IconName::Copilot,
9418 "supermaven" => IconName::Supermaven,
9419 _ => IconName::ZedPredict,
9420 },
9421 None => IconName::ZedPredict,
9422 }
9423 }
9424
9425 fn render_edit_prediction_cursor_popover(
9426 &self,
9427 min_width: Pixels,
9428 max_width: Pixels,
9429 cursor_point: Point,
9430 style: &EditorStyle,
9431 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9432 _window: &Window,
9433 cx: &mut Context<Editor>,
9434 ) -> Option<AnyElement> {
9435 let provider = self.edit_prediction_provider.as_ref()?;
9436 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9437
9438 let is_refreshing = provider.provider.is_refreshing(cx);
9439
9440 fn pending_completion_container(icon: IconName) -> Div {
9441 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9442 }
9443
9444 let completion = match &self.active_edit_prediction {
9445 Some(prediction) => {
9446 if !self.has_visible_completions_menu() {
9447 const RADIUS: Pixels = px(6.);
9448 const BORDER_WIDTH: Pixels = px(1.);
9449
9450 return Some(
9451 h_flex()
9452 .elevation_2(cx)
9453 .border(BORDER_WIDTH)
9454 .border_color(cx.theme().colors().border)
9455 .when(accept_keystroke.is_none(), |el| {
9456 el.border_color(cx.theme().status().error)
9457 })
9458 .rounded(RADIUS)
9459 .rounded_tl(px(0.))
9460 .overflow_hidden()
9461 .child(div().px_1p5().child(match &prediction.completion {
9462 EditPrediction::MoveWithin { target, snapshot } => {
9463 use text::ToPoint as _;
9464 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9465 {
9466 Icon::new(IconName::ZedPredictDown)
9467 } else {
9468 Icon::new(IconName::ZedPredictUp)
9469 }
9470 }
9471 EditPrediction::MoveOutside { .. } => {
9472 // TODO [zeta2] custom icon for external jump?
9473 Icon::new(provider_icon)
9474 }
9475 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9476 }))
9477 .child(
9478 h_flex()
9479 .gap_1()
9480 .py_1()
9481 .px_2()
9482 .rounded_r(RADIUS - BORDER_WIDTH)
9483 .border_l_1()
9484 .border_color(cx.theme().colors().border)
9485 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9486 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9487 el.child(
9488 Label::new("Hold")
9489 .size(LabelSize::Small)
9490 .when(accept_keystroke.is_none(), |el| {
9491 el.strikethrough()
9492 })
9493 .line_height_style(LineHeightStyle::UiLabel),
9494 )
9495 })
9496 .id("edit_prediction_cursor_popover_keybind")
9497 .when(accept_keystroke.is_none(), |el| {
9498 let status_colors = cx.theme().status();
9499
9500 el.bg(status_colors.error_background)
9501 .border_color(status_colors.error.opacity(0.6))
9502 .child(Icon::new(IconName::Info).color(Color::Error))
9503 .cursor_default()
9504 .hoverable_tooltip(move |_window, cx| {
9505 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9506 .into()
9507 })
9508 })
9509 .when_some(
9510 accept_keystroke.as_ref(),
9511 |el, accept_keystroke| {
9512 el.child(h_flex().children(ui::render_modifiers(
9513 accept_keystroke.modifiers(),
9514 PlatformStyle::platform(),
9515 Some(Color::Default),
9516 Some(IconSize::XSmall.rems().into()),
9517 false,
9518 )))
9519 },
9520 ),
9521 )
9522 .into_any(),
9523 );
9524 }
9525
9526 self.render_edit_prediction_cursor_popover_preview(
9527 prediction,
9528 cursor_point,
9529 style,
9530 cx,
9531 )?
9532 }
9533
9534 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9535 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9536 stale_completion,
9537 cursor_point,
9538 style,
9539 cx,
9540 )?,
9541
9542 None => pending_completion_container(provider_icon)
9543 .child(Label::new("...").size(LabelSize::Small)),
9544 },
9545
9546 None => pending_completion_container(provider_icon)
9547 .child(Label::new("...").size(LabelSize::Small)),
9548 };
9549
9550 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9551 completion
9552 .with_animation(
9553 "loading-completion",
9554 Animation::new(Duration::from_secs(2))
9555 .repeat()
9556 .with_easing(pulsating_between(0.4, 0.8)),
9557 |label, delta| label.opacity(delta),
9558 )
9559 .into_any_element()
9560 } else {
9561 completion.into_any_element()
9562 };
9563
9564 let has_completion = self.active_edit_prediction.is_some();
9565
9566 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9567 Some(
9568 h_flex()
9569 .min_w(min_width)
9570 .max_w(max_width)
9571 .flex_1()
9572 .elevation_2(cx)
9573 .border_color(cx.theme().colors().border)
9574 .child(
9575 div()
9576 .flex_1()
9577 .py_1()
9578 .px_2()
9579 .overflow_hidden()
9580 .child(completion),
9581 )
9582 .when_some(accept_keystroke, |el, accept_keystroke| {
9583 if !accept_keystroke.modifiers().modified() {
9584 return el;
9585 }
9586
9587 el.child(
9588 h_flex()
9589 .h_full()
9590 .border_l_1()
9591 .rounded_r_lg()
9592 .border_color(cx.theme().colors().border)
9593 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9594 .gap_1()
9595 .py_1()
9596 .px_2()
9597 .child(
9598 h_flex()
9599 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9600 .when(is_platform_style_mac, |parent| parent.gap_1())
9601 .child(h_flex().children(ui::render_modifiers(
9602 accept_keystroke.modifiers(),
9603 PlatformStyle::platform(),
9604 Some(if !has_completion {
9605 Color::Muted
9606 } else {
9607 Color::Default
9608 }),
9609 None,
9610 false,
9611 ))),
9612 )
9613 .child(Label::new("Preview").into_any_element())
9614 .opacity(if has_completion { 1.0 } else { 0.4 }),
9615 )
9616 })
9617 .into_any(),
9618 )
9619 }
9620
9621 fn render_edit_prediction_cursor_popover_preview(
9622 &self,
9623 completion: &EditPredictionState,
9624 cursor_point: Point,
9625 style: &EditorStyle,
9626 cx: &mut Context<Editor>,
9627 ) -> Option<Div> {
9628 use text::ToPoint as _;
9629
9630 fn render_relative_row_jump(
9631 prefix: impl Into<String>,
9632 current_row: u32,
9633 target_row: u32,
9634 ) -> Div {
9635 let (row_diff, arrow) = if target_row < current_row {
9636 (current_row - target_row, IconName::ArrowUp)
9637 } else {
9638 (target_row - current_row, IconName::ArrowDown)
9639 };
9640
9641 h_flex()
9642 .child(
9643 Label::new(format!("{}{}", prefix.into(), row_diff))
9644 .color(Color::Muted)
9645 .size(LabelSize::Small),
9646 )
9647 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9648 }
9649
9650 let supports_jump = self
9651 .edit_prediction_provider
9652 .as_ref()
9653 .map(|provider| provider.provider.supports_jump_to_edit())
9654 .unwrap_or(true);
9655
9656 match &completion.completion {
9657 EditPrediction::MoveWithin {
9658 target, snapshot, ..
9659 } => {
9660 if !supports_jump {
9661 return None;
9662 }
9663
9664 Some(
9665 h_flex()
9666 .px_2()
9667 .gap_2()
9668 .flex_1()
9669 .child(
9670 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9671 Icon::new(IconName::ZedPredictDown)
9672 } else {
9673 Icon::new(IconName::ZedPredictUp)
9674 },
9675 )
9676 .child(Label::new("Jump to Edit")),
9677 )
9678 }
9679 EditPrediction::MoveOutside { snapshot, .. } => {
9680 let file_name = snapshot
9681 .file()
9682 .map(|file| file.file_name(cx))
9683 .unwrap_or("untitled");
9684 Some(
9685 h_flex()
9686 .px_2()
9687 .gap_2()
9688 .flex_1()
9689 .child(Icon::new(IconName::ZedPredict))
9690 .child(Label::new(format!("Jump to {file_name}"))),
9691 )
9692 }
9693 EditPrediction::Edit {
9694 edits,
9695 edit_preview,
9696 snapshot,
9697 display_mode: _,
9698 } => {
9699 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9700
9701 let (highlighted_edits, has_more_lines) =
9702 if let Some(edit_preview) = edit_preview.as_ref() {
9703 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9704 .first_line_preview()
9705 } else {
9706 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9707 };
9708
9709 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9710 .with_default_highlights(&style.text, highlighted_edits.highlights);
9711
9712 let preview = h_flex()
9713 .gap_1()
9714 .min_w_16()
9715 .child(styled_text)
9716 .when(has_more_lines, |parent| parent.child("…"));
9717
9718 let left = if supports_jump && first_edit_row != cursor_point.row {
9719 render_relative_row_jump("", cursor_point.row, first_edit_row)
9720 .into_any_element()
9721 } else {
9722 let icon_name =
9723 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9724 Icon::new(icon_name).into_any_element()
9725 };
9726
9727 Some(
9728 h_flex()
9729 .h_full()
9730 .flex_1()
9731 .gap_2()
9732 .pr_1()
9733 .overflow_x_hidden()
9734 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9735 .child(left)
9736 .child(preview),
9737 )
9738 }
9739 }
9740 }
9741
9742 pub fn render_context_menu(
9743 &self,
9744 style: &EditorStyle,
9745 max_height_in_lines: u32,
9746 window: &mut Window,
9747 cx: &mut Context<Editor>,
9748 ) -> Option<AnyElement> {
9749 let menu = self.context_menu.borrow();
9750 let menu = menu.as_ref()?;
9751 if !menu.visible() {
9752 return None;
9753 };
9754 Some(menu.render(style, max_height_in_lines, window, cx))
9755 }
9756
9757 fn render_context_menu_aside(
9758 &mut self,
9759 max_size: Size<Pixels>,
9760 window: &mut Window,
9761 cx: &mut Context<Editor>,
9762 ) -> Option<AnyElement> {
9763 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9764 if menu.visible() {
9765 menu.render_aside(max_size, window, cx)
9766 } else {
9767 None
9768 }
9769 })
9770 }
9771
9772 fn hide_context_menu(
9773 &mut self,
9774 window: &mut Window,
9775 cx: &mut Context<Self>,
9776 ) -> Option<CodeContextMenu> {
9777 cx.notify();
9778 self.completion_tasks.clear();
9779 let context_menu = self.context_menu.borrow_mut().take();
9780 self.stale_edit_prediction_in_menu.take();
9781 self.update_visible_edit_prediction(window, cx);
9782 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9783 && let Some(completion_provider) = &self.completion_provider
9784 {
9785 completion_provider.selection_changed(None, window, cx);
9786 }
9787 context_menu
9788 }
9789
9790 fn show_snippet_choices(
9791 &mut self,
9792 choices: &Vec<String>,
9793 selection: Range<Anchor>,
9794 cx: &mut Context<Self>,
9795 ) {
9796 let Some((_, buffer, _)) = self
9797 .buffer()
9798 .read(cx)
9799 .excerpt_containing(selection.start, cx)
9800 else {
9801 return;
9802 };
9803 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9804 else {
9805 return;
9806 };
9807 if buffer != end_buffer {
9808 log::error!("expected anchor range to have matching buffer IDs");
9809 return;
9810 }
9811
9812 let id = post_inc(&mut self.next_completion_id);
9813 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9814 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9815 CompletionsMenu::new_snippet_choices(
9816 id,
9817 true,
9818 choices,
9819 selection,
9820 buffer,
9821 snippet_sort_order,
9822 ),
9823 ));
9824 }
9825
9826 pub fn insert_snippet(
9827 &mut self,
9828 insertion_ranges: &[Range<usize>],
9829 snippet: Snippet,
9830 window: &mut Window,
9831 cx: &mut Context<Self>,
9832 ) -> Result<()> {
9833 struct Tabstop<T> {
9834 is_end_tabstop: bool,
9835 ranges: Vec<Range<T>>,
9836 choices: Option<Vec<String>>,
9837 }
9838
9839 let tabstops = self.buffer.update(cx, |buffer, cx| {
9840 let snippet_text: Arc<str> = snippet.text.clone().into();
9841 let edits = insertion_ranges
9842 .iter()
9843 .cloned()
9844 .map(|range| (range, snippet_text.clone()));
9845 let autoindent_mode = AutoindentMode::Block {
9846 original_indent_columns: Vec::new(),
9847 };
9848 buffer.edit(edits, Some(autoindent_mode), cx);
9849
9850 let snapshot = &*buffer.read(cx);
9851 let snippet = &snippet;
9852 snippet
9853 .tabstops
9854 .iter()
9855 .map(|tabstop| {
9856 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9857 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9858 });
9859 let mut tabstop_ranges = tabstop
9860 .ranges
9861 .iter()
9862 .flat_map(|tabstop_range| {
9863 let mut delta = 0_isize;
9864 insertion_ranges.iter().map(move |insertion_range| {
9865 let insertion_start = insertion_range.start as isize + delta;
9866 delta +=
9867 snippet.text.len() as isize - insertion_range.len() as isize;
9868
9869 let start = ((insertion_start + tabstop_range.start) as usize)
9870 .min(snapshot.len());
9871 let end = ((insertion_start + tabstop_range.end) as usize)
9872 .min(snapshot.len());
9873 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9874 })
9875 })
9876 .collect::<Vec<_>>();
9877 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9878
9879 Tabstop {
9880 is_end_tabstop,
9881 ranges: tabstop_ranges,
9882 choices: tabstop.choices.clone(),
9883 }
9884 })
9885 .collect::<Vec<_>>()
9886 });
9887 if let Some(tabstop) = tabstops.first() {
9888 self.change_selections(Default::default(), window, cx, |s| {
9889 // Reverse order so that the first range is the newest created selection.
9890 // Completions will use it and autoscroll will prioritize it.
9891 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9892 });
9893
9894 if let Some(choices) = &tabstop.choices
9895 && let Some(selection) = tabstop.ranges.first()
9896 {
9897 self.show_snippet_choices(choices, selection.clone(), cx)
9898 }
9899
9900 // If we're already at the last tabstop and it's at the end of the snippet,
9901 // we're done, we don't need to keep the state around.
9902 if !tabstop.is_end_tabstop {
9903 let choices = tabstops
9904 .iter()
9905 .map(|tabstop| tabstop.choices.clone())
9906 .collect();
9907
9908 let ranges = tabstops
9909 .into_iter()
9910 .map(|tabstop| tabstop.ranges)
9911 .collect::<Vec<_>>();
9912
9913 self.snippet_stack.push(SnippetState {
9914 active_index: 0,
9915 ranges,
9916 choices,
9917 });
9918 }
9919
9920 // Check whether the just-entered snippet ends with an auto-closable bracket.
9921 if self.autoclose_regions.is_empty() {
9922 let snapshot = self.buffer.read(cx).snapshot(cx);
9923 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9924 let selection_head = selection.head();
9925 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9926 continue;
9927 };
9928
9929 let mut bracket_pair = None;
9930 let max_lookup_length = scope
9931 .brackets()
9932 .map(|(pair, _)| {
9933 pair.start
9934 .as_str()
9935 .chars()
9936 .count()
9937 .max(pair.end.as_str().chars().count())
9938 })
9939 .max();
9940 if let Some(max_lookup_length) = max_lookup_length {
9941 let next_text = snapshot
9942 .chars_at(selection_head)
9943 .take(max_lookup_length)
9944 .collect::<String>();
9945 let prev_text = snapshot
9946 .reversed_chars_at(selection_head)
9947 .take(max_lookup_length)
9948 .collect::<String>();
9949
9950 for (pair, enabled) in scope.brackets() {
9951 if enabled
9952 && pair.close
9953 && prev_text.starts_with(pair.start.as_str())
9954 && next_text.starts_with(pair.end.as_str())
9955 {
9956 bracket_pair = Some(pair.clone());
9957 break;
9958 }
9959 }
9960 }
9961
9962 if let Some(pair) = bracket_pair {
9963 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9964 let autoclose_enabled =
9965 self.use_autoclose && snapshot_settings.use_autoclose;
9966 if autoclose_enabled {
9967 let start = snapshot.anchor_after(selection_head);
9968 let end = snapshot.anchor_after(selection_head);
9969 self.autoclose_regions.push(AutocloseRegion {
9970 selection_id: selection.id,
9971 range: start..end,
9972 pair,
9973 });
9974 }
9975 }
9976 }
9977 }
9978 }
9979 Ok(())
9980 }
9981
9982 pub fn move_to_next_snippet_tabstop(
9983 &mut self,
9984 window: &mut Window,
9985 cx: &mut Context<Self>,
9986 ) -> bool {
9987 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9988 }
9989
9990 pub fn move_to_prev_snippet_tabstop(
9991 &mut self,
9992 window: &mut Window,
9993 cx: &mut Context<Self>,
9994 ) -> bool {
9995 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9996 }
9997
9998 pub fn move_to_snippet_tabstop(
9999 &mut self,
10000 bias: Bias,
10001 window: &mut Window,
10002 cx: &mut Context<Self>,
10003 ) -> bool {
10004 if let Some(mut snippet) = self.snippet_stack.pop() {
10005 match bias {
10006 Bias::Left => {
10007 if snippet.active_index > 0 {
10008 snippet.active_index -= 1;
10009 } else {
10010 self.snippet_stack.push(snippet);
10011 return false;
10012 }
10013 }
10014 Bias::Right => {
10015 if snippet.active_index + 1 < snippet.ranges.len() {
10016 snippet.active_index += 1;
10017 } else {
10018 self.snippet_stack.push(snippet);
10019 return false;
10020 }
10021 }
10022 }
10023 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10024 self.change_selections(Default::default(), window, cx, |s| {
10025 // Reverse order so that the first range is the newest created selection.
10026 // Completions will use it and autoscroll will prioritize it.
10027 s.select_ranges(current_ranges.iter().rev().cloned())
10028 });
10029
10030 if let Some(choices) = &snippet.choices[snippet.active_index]
10031 && let Some(selection) = current_ranges.first()
10032 {
10033 self.show_snippet_choices(choices, selection.clone(), cx);
10034 }
10035
10036 // If snippet state is not at the last tabstop, push it back on the stack
10037 if snippet.active_index + 1 < snippet.ranges.len() {
10038 self.snippet_stack.push(snippet);
10039 }
10040 return true;
10041 }
10042 }
10043
10044 false
10045 }
10046
10047 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10048 self.transact(window, cx, |this, window, cx| {
10049 this.select_all(&SelectAll, window, cx);
10050 this.insert("", window, cx);
10051 });
10052 }
10053
10054 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10055 if self.read_only(cx) {
10056 return;
10057 }
10058 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10059 self.transact(window, cx, |this, window, cx| {
10060 this.select_autoclose_pair(window, cx);
10061
10062 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10063
10064 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10065 if !this.linked_edit_ranges.is_empty() {
10066 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10067 let snapshot = this.buffer.read(cx).snapshot(cx);
10068
10069 for selection in selections.iter() {
10070 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10071 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10072 if selection_start.buffer_id != selection_end.buffer_id {
10073 continue;
10074 }
10075 if let Some(ranges) =
10076 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10077 {
10078 for (buffer, entries) in ranges {
10079 linked_ranges.entry(buffer).or_default().extend(entries);
10080 }
10081 }
10082 }
10083 }
10084
10085 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10086 for selection in &mut selections {
10087 if selection.is_empty() {
10088 let old_head = selection.head();
10089 let mut new_head =
10090 movement::left(&display_map, old_head.to_display_point(&display_map))
10091 .to_point(&display_map);
10092 if let Some((buffer, line_buffer_range)) = display_map
10093 .buffer_snapshot()
10094 .buffer_line_for_row(MultiBufferRow(old_head.row))
10095 {
10096 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10097 let indent_len = match indent_size.kind {
10098 IndentKind::Space => {
10099 buffer.settings_at(line_buffer_range.start, cx).tab_size
10100 }
10101 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10102 };
10103 if old_head.column <= indent_size.len && old_head.column > 0 {
10104 let indent_len = indent_len.get();
10105 new_head = cmp::min(
10106 new_head,
10107 MultiBufferPoint::new(
10108 old_head.row,
10109 ((old_head.column - 1) / indent_len) * indent_len,
10110 ),
10111 );
10112 }
10113 }
10114
10115 selection.set_head(new_head, SelectionGoal::None);
10116 }
10117 }
10118
10119 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10120 this.insert("", window, cx);
10121 let empty_str: Arc<str> = Arc::from("");
10122 for (buffer, edits) in linked_ranges {
10123 let snapshot = buffer.read(cx).snapshot();
10124 use text::ToPoint as TP;
10125
10126 let edits = edits
10127 .into_iter()
10128 .map(|range| {
10129 let end_point = TP::to_point(&range.end, &snapshot);
10130 let mut start_point = TP::to_point(&range.start, &snapshot);
10131
10132 if end_point == start_point {
10133 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10134 .saturating_sub(1);
10135 start_point =
10136 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10137 };
10138
10139 (start_point..end_point, empty_str.clone())
10140 })
10141 .sorted_by_key(|(range, _)| range.start)
10142 .collect::<Vec<_>>();
10143 buffer.update(cx, |this, cx| {
10144 this.edit(edits, None, cx);
10145 })
10146 }
10147 this.refresh_edit_prediction(true, false, window, cx);
10148 refresh_linked_ranges(this, window, cx);
10149 });
10150 }
10151
10152 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10153 if self.read_only(cx) {
10154 return;
10155 }
10156 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10157 self.transact(window, cx, |this, window, cx| {
10158 this.change_selections(Default::default(), window, cx, |s| {
10159 s.move_with(|map, selection| {
10160 if selection.is_empty() {
10161 let cursor = movement::right(map, selection.head());
10162 selection.end = cursor;
10163 selection.reversed = true;
10164 selection.goal = SelectionGoal::None;
10165 }
10166 })
10167 });
10168 this.insert("", window, cx);
10169 this.refresh_edit_prediction(true, false, window, cx);
10170 });
10171 }
10172
10173 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10174 if self.mode.is_single_line() {
10175 cx.propagate();
10176 return;
10177 }
10178
10179 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10180 if self.move_to_prev_snippet_tabstop(window, cx) {
10181 return;
10182 }
10183 self.outdent(&Outdent, window, cx);
10184 }
10185
10186 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10187 if self.mode.is_single_line() {
10188 cx.propagate();
10189 return;
10190 }
10191
10192 if self.move_to_next_snippet_tabstop(window, cx) {
10193 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10194 return;
10195 }
10196 if self.read_only(cx) {
10197 return;
10198 }
10199 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10200 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10201 let buffer = self.buffer.read(cx);
10202 let snapshot = buffer.snapshot(cx);
10203 let rows_iter = selections.iter().map(|s| s.head().row);
10204 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10205
10206 let has_some_cursor_in_whitespace = selections
10207 .iter()
10208 .filter(|selection| selection.is_empty())
10209 .any(|selection| {
10210 let cursor = selection.head();
10211 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10212 cursor.column < current_indent.len
10213 });
10214
10215 let mut edits = Vec::new();
10216 let mut prev_edited_row = 0;
10217 let mut row_delta = 0;
10218 for selection in &mut selections {
10219 if selection.start.row != prev_edited_row {
10220 row_delta = 0;
10221 }
10222 prev_edited_row = selection.end.row;
10223
10224 // If the selection is non-empty, then increase the indentation of the selected lines.
10225 if !selection.is_empty() {
10226 row_delta =
10227 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10228 continue;
10229 }
10230
10231 let cursor = selection.head();
10232 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10233 if let Some(suggested_indent) =
10234 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10235 {
10236 // Don't do anything if already at suggested indent
10237 // and there is any other cursor which is not
10238 if has_some_cursor_in_whitespace
10239 && cursor.column == current_indent.len
10240 && current_indent.len == suggested_indent.len
10241 {
10242 continue;
10243 }
10244
10245 // Adjust line and move cursor to suggested indent
10246 // if cursor is not at suggested indent
10247 if cursor.column < suggested_indent.len
10248 && cursor.column <= current_indent.len
10249 && current_indent.len <= suggested_indent.len
10250 {
10251 selection.start = Point::new(cursor.row, suggested_indent.len);
10252 selection.end = selection.start;
10253 if row_delta == 0 {
10254 edits.extend(Buffer::edit_for_indent_size_adjustment(
10255 cursor.row,
10256 current_indent,
10257 suggested_indent,
10258 ));
10259 row_delta = suggested_indent.len - current_indent.len;
10260 }
10261 continue;
10262 }
10263
10264 // If current indent is more than suggested indent
10265 // only move cursor to current indent and skip indent
10266 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10267 selection.start = Point::new(cursor.row, current_indent.len);
10268 selection.end = selection.start;
10269 continue;
10270 }
10271 }
10272
10273 // Otherwise, insert a hard or soft tab.
10274 let settings = buffer.language_settings_at(cursor, cx);
10275 let tab_size = if settings.hard_tabs {
10276 IndentSize::tab()
10277 } else {
10278 let tab_size = settings.tab_size.get();
10279 let indent_remainder = snapshot
10280 .text_for_range(Point::new(cursor.row, 0)..cursor)
10281 .flat_map(str::chars)
10282 .fold(row_delta % tab_size, |counter: u32, c| {
10283 if c == '\t' {
10284 0
10285 } else {
10286 (counter + 1) % tab_size
10287 }
10288 });
10289
10290 let chars_to_next_tab_stop = tab_size - indent_remainder;
10291 IndentSize::spaces(chars_to_next_tab_stop)
10292 };
10293 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10294 selection.end = selection.start;
10295 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10296 row_delta += tab_size.len;
10297 }
10298
10299 self.transact(window, cx, |this, window, cx| {
10300 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10301 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10302 this.refresh_edit_prediction(true, false, window, cx);
10303 });
10304 }
10305
10306 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10307 if self.read_only(cx) {
10308 return;
10309 }
10310 if self.mode.is_single_line() {
10311 cx.propagate();
10312 return;
10313 }
10314
10315 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10316 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10317 let mut prev_edited_row = 0;
10318 let mut row_delta = 0;
10319 let mut edits = Vec::new();
10320 let buffer = self.buffer.read(cx);
10321 let snapshot = buffer.snapshot(cx);
10322 for selection in &mut selections {
10323 if selection.start.row != prev_edited_row {
10324 row_delta = 0;
10325 }
10326 prev_edited_row = selection.end.row;
10327
10328 row_delta =
10329 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10330 }
10331
10332 self.transact(window, cx, |this, window, cx| {
10333 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10334 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10335 });
10336 }
10337
10338 fn indent_selection(
10339 buffer: &MultiBuffer,
10340 snapshot: &MultiBufferSnapshot,
10341 selection: &mut Selection<Point>,
10342 edits: &mut Vec<(Range<Point>, String)>,
10343 delta_for_start_row: u32,
10344 cx: &App,
10345 ) -> u32 {
10346 let settings = buffer.language_settings_at(selection.start, cx);
10347 let tab_size = settings.tab_size.get();
10348 let indent_kind = if settings.hard_tabs {
10349 IndentKind::Tab
10350 } else {
10351 IndentKind::Space
10352 };
10353 let mut start_row = selection.start.row;
10354 let mut end_row = selection.end.row + 1;
10355
10356 // If a selection ends at the beginning of a line, don't indent
10357 // that last line.
10358 if selection.end.column == 0 && selection.end.row > selection.start.row {
10359 end_row -= 1;
10360 }
10361
10362 // Avoid re-indenting a row that has already been indented by a
10363 // previous selection, but still update this selection's column
10364 // to reflect that indentation.
10365 if delta_for_start_row > 0 {
10366 start_row += 1;
10367 selection.start.column += delta_for_start_row;
10368 if selection.end.row == selection.start.row {
10369 selection.end.column += delta_for_start_row;
10370 }
10371 }
10372
10373 let mut delta_for_end_row = 0;
10374 let has_multiple_rows = start_row + 1 != end_row;
10375 for row in start_row..end_row {
10376 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10377 let indent_delta = match (current_indent.kind, indent_kind) {
10378 (IndentKind::Space, IndentKind::Space) => {
10379 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10380 IndentSize::spaces(columns_to_next_tab_stop)
10381 }
10382 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10383 (_, IndentKind::Tab) => IndentSize::tab(),
10384 };
10385
10386 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10387 0
10388 } else {
10389 selection.start.column
10390 };
10391 let row_start = Point::new(row, start);
10392 edits.push((
10393 row_start..row_start,
10394 indent_delta.chars().collect::<String>(),
10395 ));
10396
10397 // Update this selection's endpoints to reflect the indentation.
10398 if row == selection.start.row {
10399 selection.start.column += indent_delta.len;
10400 }
10401 if row == selection.end.row {
10402 selection.end.column += indent_delta.len;
10403 delta_for_end_row = indent_delta.len;
10404 }
10405 }
10406
10407 if selection.start.row == selection.end.row {
10408 delta_for_start_row + delta_for_end_row
10409 } else {
10410 delta_for_end_row
10411 }
10412 }
10413
10414 pub fn outdent(&mut self, _: &Outdent, 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 display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10425 let selections = self.selections.all::<Point>(&display_map);
10426 let mut deletion_ranges = Vec::new();
10427 let mut last_outdent = None;
10428 {
10429 let buffer = self.buffer.read(cx);
10430 let snapshot = buffer.snapshot(cx);
10431 for selection in &selections {
10432 let settings = buffer.language_settings_at(selection.start, cx);
10433 let tab_size = settings.tab_size.get();
10434 let mut rows = selection.spanned_rows(false, &display_map);
10435
10436 // Avoid re-outdenting a row that has already been outdented by a
10437 // previous selection.
10438 if let Some(last_row) = last_outdent
10439 && last_row == rows.start
10440 {
10441 rows.start = rows.start.next_row();
10442 }
10443 let has_multiple_rows = rows.len() > 1;
10444 for row in rows.iter_rows() {
10445 let indent_size = snapshot.indent_size_for_line(row);
10446 if indent_size.len > 0 {
10447 let deletion_len = match indent_size.kind {
10448 IndentKind::Space => {
10449 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10450 if columns_to_prev_tab_stop == 0 {
10451 tab_size
10452 } else {
10453 columns_to_prev_tab_stop
10454 }
10455 }
10456 IndentKind::Tab => 1,
10457 };
10458 let start = if has_multiple_rows
10459 || deletion_len > selection.start.column
10460 || indent_size.len < selection.start.column
10461 {
10462 0
10463 } else {
10464 selection.start.column - deletion_len
10465 };
10466 deletion_ranges.push(
10467 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10468 );
10469 last_outdent = Some(row);
10470 }
10471 }
10472 }
10473 }
10474
10475 self.transact(window, cx, |this, window, cx| {
10476 this.buffer.update(cx, |buffer, cx| {
10477 let empty_str: Arc<str> = Arc::default();
10478 buffer.edit(
10479 deletion_ranges
10480 .into_iter()
10481 .map(|range| (range, empty_str.clone())),
10482 None,
10483 cx,
10484 );
10485 });
10486 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10487 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10488 });
10489 }
10490
10491 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10492 if self.read_only(cx) {
10493 return;
10494 }
10495 if self.mode.is_single_line() {
10496 cx.propagate();
10497 return;
10498 }
10499
10500 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10501 let selections = self
10502 .selections
10503 .all::<usize>(&self.display_snapshot(cx))
10504 .into_iter()
10505 .map(|s| s.range());
10506
10507 self.transact(window, cx, |this, window, cx| {
10508 this.buffer.update(cx, |buffer, cx| {
10509 buffer.autoindent_ranges(selections, cx);
10510 });
10511 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10512 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10513 });
10514 }
10515
10516 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10517 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10518 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10519 let selections = self.selections.all::<Point>(&display_map);
10520
10521 let mut new_cursors = Vec::new();
10522 let mut edit_ranges = Vec::new();
10523 let mut selections = selections.iter().peekable();
10524 while let Some(selection) = selections.next() {
10525 let mut rows = selection.spanned_rows(false, &display_map);
10526
10527 // Accumulate contiguous regions of rows that we want to delete.
10528 while let Some(next_selection) = selections.peek() {
10529 let next_rows = next_selection.spanned_rows(false, &display_map);
10530 if next_rows.start <= rows.end {
10531 rows.end = next_rows.end;
10532 selections.next().unwrap();
10533 } else {
10534 break;
10535 }
10536 }
10537
10538 let buffer = display_map.buffer_snapshot();
10539 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10540 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10541 // If there's a line after the range, delete the \n from the end of the row range
10542 (
10543 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10544 rows.end,
10545 )
10546 } else {
10547 // If there isn't a line after the range, delete the \n from the line before the
10548 // start of the row range
10549 edit_start = edit_start.saturating_sub(1);
10550 (buffer.len(), rows.start.previous_row())
10551 };
10552
10553 let text_layout_details = self.text_layout_details(window);
10554 let x = display_map.x_for_display_point(
10555 selection.head().to_display_point(&display_map),
10556 &text_layout_details,
10557 );
10558 let row = Point::new(target_row.0, 0)
10559 .to_display_point(&display_map)
10560 .row();
10561 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10562
10563 new_cursors.push((
10564 selection.id,
10565 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10566 SelectionGoal::None,
10567 ));
10568 edit_ranges.push(edit_start..edit_end);
10569 }
10570
10571 self.transact(window, cx, |this, window, cx| {
10572 let buffer = this.buffer.update(cx, |buffer, cx| {
10573 let empty_str: Arc<str> = Arc::default();
10574 buffer.edit(
10575 edit_ranges
10576 .into_iter()
10577 .map(|range| (range, empty_str.clone())),
10578 None,
10579 cx,
10580 );
10581 buffer.snapshot(cx)
10582 });
10583 let new_selections = new_cursors
10584 .into_iter()
10585 .map(|(id, cursor, goal)| {
10586 let cursor = cursor.to_point(&buffer);
10587 Selection {
10588 id,
10589 start: cursor,
10590 end: cursor,
10591 reversed: false,
10592 goal,
10593 }
10594 })
10595 .collect();
10596
10597 this.change_selections(Default::default(), window, cx, |s| {
10598 s.select(new_selections);
10599 });
10600 });
10601 }
10602
10603 pub fn join_lines_impl(
10604 &mut self,
10605 insert_whitespace: bool,
10606 window: &mut Window,
10607 cx: &mut Context<Self>,
10608 ) {
10609 if self.read_only(cx) {
10610 return;
10611 }
10612 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10613 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10614 let start = MultiBufferRow(selection.start.row);
10615 // Treat single line selections as if they include the next line. Otherwise this action
10616 // would do nothing for single line selections individual cursors.
10617 let end = if selection.start.row == selection.end.row {
10618 MultiBufferRow(selection.start.row + 1)
10619 } else {
10620 MultiBufferRow(selection.end.row)
10621 };
10622
10623 if let Some(last_row_range) = row_ranges.last_mut()
10624 && start <= last_row_range.end
10625 {
10626 last_row_range.end = end;
10627 continue;
10628 }
10629 row_ranges.push(start..end);
10630 }
10631
10632 let snapshot = self.buffer.read(cx).snapshot(cx);
10633 let mut cursor_positions = Vec::new();
10634 for row_range in &row_ranges {
10635 let anchor = snapshot.anchor_before(Point::new(
10636 row_range.end.previous_row().0,
10637 snapshot.line_len(row_range.end.previous_row()),
10638 ));
10639 cursor_positions.push(anchor..anchor);
10640 }
10641
10642 self.transact(window, cx, |this, window, cx| {
10643 for row_range in row_ranges.into_iter().rev() {
10644 for row in row_range.iter_rows().rev() {
10645 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10646 let next_line_row = row.next_row();
10647 let indent = snapshot.indent_size_for_line(next_line_row);
10648 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10649
10650 let replace =
10651 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10652 " "
10653 } else {
10654 ""
10655 };
10656
10657 this.buffer.update(cx, |buffer, cx| {
10658 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10659 });
10660 }
10661 }
10662
10663 this.change_selections(Default::default(), window, cx, |s| {
10664 s.select_anchor_ranges(cursor_positions)
10665 });
10666 });
10667 }
10668
10669 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10670 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10671 self.join_lines_impl(true, window, cx);
10672 }
10673
10674 pub fn sort_lines_case_sensitive(
10675 &mut self,
10676 _: &SortLinesCaseSensitive,
10677 window: &mut Window,
10678 cx: &mut Context<Self>,
10679 ) {
10680 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10681 }
10682
10683 pub fn sort_lines_by_length(
10684 &mut self,
10685 _: &SortLinesByLength,
10686 window: &mut Window,
10687 cx: &mut Context<Self>,
10688 ) {
10689 self.manipulate_immutable_lines(window, cx, |lines| {
10690 lines.sort_by_key(|&line| line.chars().count())
10691 })
10692 }
10693
10694 pub fn sort_lines_case_insensitive(
10695 &mut self,
10696 _: &SortLinesCaseInsensitive,
10697 window: &mut Window,
10698 cx: &mut Context<Self>,
10699 ) {
10700 self.manipulate_immutable_lines(window, cx, |lines| {
10701 lines.sort_by_key(|line| line.to_lowercase())
10702 })
10703 }
10704
10705 pub fn unique_lines_case_insensitive(
10706 &mut self,
10707 _: &UniqueLinesCaseInsensitive,
10708 window: &mut Window,
10709 cx: &mut Context<Self>,
10710 ) {
10711 self.manipulate_immutable_lines(window, cx, |lines| {
10712 let mut seen = HashSet::default();
10713 lines.retain(|line| seen.insert(line.to_lowercase()));
10714 })
10715 }
10716
10717 pub fn unique_lines_case_sensitive(
10718 &mut self,
10719 _: &UniqueLinesCaseSensitive,
10720 window: &mut Window,
10721 cx: &mut Context<Self>,
10722 ) {
10723 self.manipulate_immutable_lines(window, cx, |lines| {
10724 let mut seen = HashSet::default();
10725 lines.retain(|line| seen.insert(*line));
10726 })
10727 }
10728
10729 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10730 let snapshot = self.buffer.read(cx).snapshot(cx);
10731 for selection in self.selections.disjoint_anchors_arc().iter() {
10732 if snapshot
10733 .language_at(selection.start)
10734 .and_then(|lang| lang.config().wrap_characters.as_ref())
10735 .is_some()
10736 {
10737 return true;
10738 }
10739 }
10740 false
10741 }
10742
10743 fn wrap_selections_in_tag(
10744 &mut self,
10745 _: &WrapSelectionsInTag,
10746 window: &mut Window,
10747 cx: &mut Context<Self>,
10748 ) {
10749 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10750
10751 let snapshot = self.buffer.read(cx).snapshot(cx);
10752
10753 let mut edits = Vec::new();
10754 let mut boundaries = Vec::new();
10755
10756 for selection in self
10757 .selections
10758 .all::<Point>(&self.display_snapshot(cx))
10759 .iter()
10760 {
10761 let Some(wrap_config) = snapshot
10762 .language_at(selection.start)
10763 .and_then(|lang| lang.config().wrap_characters.clone())
10764 else {
10765 continue;
10766 };
10767
10768 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10769 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10770
10771 let start_before = snapshot.anchor_before(selection.start);
10772 let end_after = snapshot.anchor_after(selection.end);
10773
10774 edits.push((start_before..start_before, open_tag));
10775 edits.push((end_after..end_after, close_tag));
10776
10777 boundaries.push((
10778 start_before,
10779 end_after,
10780 wrap_config.start_prefix.len(),
10781 wrap_config.end_suffix.len(),
10782 ));
10783 }
10784
10785 if edits.is_empty() {
10786 return;
10787 }
10788
10789 self.transact(window, cx, |this, window, cx| {
10790 let buffer = this.buffer.update(cx, |buffer, cx| {
10791 buffer.edit(edits, None, cx);
10792 buffer.snapshot(cx)
10793 });
10794
10795 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10796 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10797 boundaries.into_iter()
10798 {
10799 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10800 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10801 new_selections.push(open_offset..open_offset);
10802 new_selections.push(close_offset..close_offset);
10803 }
10804
10805 this.change_selections(Default::default(), window, cx, |s| {
10806 s.select_ranges(new_selections);
10807 });
10808
10809 this.request_autoscroll(Autoscroll::fit(), cx);
10810 });
10811 }
10812
10813 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10814 let Some(project) = self.project.clone() else {
10815 return;
10816 };
10817 self.reload(project, window, cx)
10818 .detach_and_notify_err(window, cx);
10819 }
10820
10821 pub fn restore_file(
10822 &mut self,
10823 _: &::git::RestoreFile,
10824 window: &mut Window,
10825 cx: &mut Context<Self>,
10826 ) {
10827 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10828 let mut buffer_ids = HashSet::default();
10829 let snapshot = self.buffer().read(cx).snapshot(cx);
10830 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10831 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10832 }
10833
10834 let buffer = self.buffer().read(cx);
10835 let ranges = buffer_ids
10836 .into_iter()
10837 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10838 .collect::<Vec<_>>();
10839
10840 self.restore_hunks_in_ranges(ranges, window, cx);
10841 }
10842
10843 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10845 let selections = self
10846 .selections
10847 .all(&self.display_snapshot(cx))
10848 .into_iter()
10849 .map(|s| s.range())
10850 .collect();
10851 self.restore_hunks_in_ranges(selections, window, cx);
10852 }
10853
10854 pub fn restore_hunks_in_ranges(
10855 &mut self,
10856 ranges: Vec<Range<Point>>,
10857 window: &mut Window,
10858 cx: &mut Context<Editor>,
10859 ) {
10860 let mut revert_changes = HashMap::default();
10861 let chunk_by = self
10862 .snapshot(window, cx)
10863 .hunks_for_ranges(ranges)
10864 .into_iter()
10865 .chunk_by(|hunk| hunk.buffer_id);
10866 for (buffer_id, hunks) in &chunk_by {
10867 let hunks = hunks.collect::<Vec<_>>();
10868 for hunk in &hunks {
10869 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10870 }
10871 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10872 }
10873 drop(chunk_by);
10874 if !revert_changes.is_empty() {
10875 self.transact(window, cx, |editor, window, cx| {
10876 editor.restore(revert_changes, window, cx);
10877 });
10878 }
10879 }
10880
10881 pub fn open_active_item_in_terminal(
10882 &mut self,
10883 _: &OpenInTerminal,
10884 window: &mut Window,
10885 cx: &mut Context<Self>,
10886 ) {
10887 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10888 let project_path = buffer.read(cx).project_path(cx)?;
10889 let project = self.project()?.read(cx);
10890 let entry = project.entry_for_path(&project_path, cx)?;
10891 let parent = match &entry.canonical_path {
10892 Some(canonical_path) => canonical_path.to_path_buf(),
10893 None => project.absolute_path(&project_path, cx)?,
10894 }
10895 .parent()?
10896 .to_path_buf();
10897 Some(parent)
10898 }) {
10899 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10900 }
10901 }
10902
10903 fn set_breakpoint_context_menu(
10904 &mut self,
10905 display_row: DisplayRow,
10906 position: Option<Anchor>,
10907 clicked_point: gpui::Point<Pixels>,
10908 window: &mut Window,
10909 cx: &mut Context<Self>,
10910 ) {
10911 let source = self
10912 .buffer
10913 .read(cx)
10914 .snapshot(cx)
10915 .anchor_before(Point::new(display_row.0, 0u32));
10916
10917 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10918
10919 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10920 self,
10921 source,
10922 clicked_point,
10923 context_menu,
10924 window,
10925 cx,
10926 );
10927 }
10928
10929 fn add_edit_breakpoint_block(
10930 &mut self,
10931 anchor: Anchor,
10932 breakpoint: &Breakpoint,
10933 edit_action: BreakpointPromptEditAction,
10934 window: &mut Window,
10935 cx: &mut Context<Self>,
10936 ) {
10937 let weak_editor = cx.weak_entity();
10938 let bp_prompt = cx.new(|cx| {
10939 BreakpointPromptEditor::new(
10940 weak_editor,
10941 anchor,
10942 breakpoint.clone(),
10943 edit_action,
10944 window,
10945 cx,
10946 )
10947 });
10948
10949 let height = bp_prompt.update(cx, |this, cx| {
10950 this.prompt
10951 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10952 });
10953 let cloned_prompt = bp_prompt.clone();
10954 let blocks = vec![BlockProperties {
10955 style: BlockStyle::Sticky,
10956 placement: BlockPlacement::Above(anchor),
10957 height: Some(height),
10958 render: Arc::new(move |cx| {
10959 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10960 cloned_prompt.clone().into_any_element()
10961 }),
10962 priority: 0,
10963 }];
10964
10965 let focus_handle = bp_prompt.focus_handle(cx);
10966 window.focus(&focus_handle);
10967
10968 let block_ids = self.insert_blocks(blocks, None, cx);
10969 bp_prompt.update(cx, |prompt, _| {
10970 prompt.add_block_ids(block_ids);
10971 });
10972 }
10973
10974 pub(crate) fn breakpoint_at_row(
10975 &self,
10976 row: u32,
10977 window: &mut Window,
10978 cx: &mut Context<Self>,
10979 ) -> Option<(Anchor, Breakpoint)> {
10980 let snapshot = self.snapshot(window, cx);
10981 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10982
10983 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10984 }
10985
10986 pub(crate) fn breakpoint_at_anchor(
10987 &self,
10988 breakpoint_position: Anchor,
10989 snapshot: &EditorSnapshot,
10990 cx: &mut Context<Self>,
10991 ) -> Option<(Anchor, Breakpoint)> {
10992 let buffer = self
10993 .buffer
10994 .read(cx)
10995 .buffer_for_anchor(breakpoint_position, cx)?;
10996
10997 let enclosing_excerpt = breakpoint_position.excerpt_id;
10998 let buffer_snapshot = buffer.read(cx).snapshot();
10999
11000 let row = buffer_snapshot
11001 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11002 .row;
11003
11004 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11005 let anchor_end = snapshot
11006 .buffer_snapshot()
11007 .anchor_after(Point::new(row, line_len));
11008
11009 self.breakpoint_store
11010 .as_ref()?
11011 .read_with(cx, |breakpoint_store, cx| {
11012 breakpoint_store
11013 .breakpoints(
11014 &buffer,
11015 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11016 &buffer_snapshot,
11017 cx,
11018 )
11019 .next()
11020 .and_then(|(bp, _)| {
11021 let breakpoint_row = buffer_snapshot
11022 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11023 .row;
11024
11025 if breakpoint_row == row {
11026 snapshot
11027 .buffer_snapshot()
11028 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11029 .map(|position| (position, bp.bp.clone()))
11030 } else {
11031 None
11032 }
11033 })
11034 })
11035 }
11036
11037 pub fn edit_log_breakpoint(
11038 &mut self,
11039 _: &EditLogBreakpoint,
11040 window: &mut Window,
11041 cx: &mut Context<Self>,
11042 ) {
11043 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11044 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11045 message: None,
11046 state: BreakpointState::Enabled,
11047 condition: None,
11048 hit_condition: None,
11049 });
11050
11051 self.add_edit_breakpoint_block(
11052 anchor,
11053 &breakpoint,
11054 BreakpointPromptEditAction::Log,
11055 window,
11056 cx,
11057 );
11058 }
11059 }
11060
11061 fn breakpoints_at_cursors(
11062 &self,
11063 window: &mut Window,
11064 cx: &mut Context<Self>,
11065 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11066 let snapshot = self.snapshot(window, cx);
11067 let cursors = self
11068 .selections
11069 .disjoint_anchors_arc()
11070 .iter()
11071 .map(|selection| {
11072 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11073
11074 let breakpoint_position = self
11075 .breakpoint_at_row(cursor_position.row, window, cx)
11076 .map(|bp| bp.0)
11077 .unwrap_or_else(|| {
11078 snapshot
11079 .display_snapshot
11080 .buffer_snapshot()
11081 .anchor_after(Point::new(cursor_position.row, 0))
11082 });
11083
11084 let breakpoint = self
11085 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11086 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11087
11088 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11089 })
11090 // 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.
11091 .collect::<HashMap<Anchor, _>>();
11092
11093 cursors.into_iter().collect()
11094 }
11095
11096 pub fn enable_breakpoint(
11097 &mut self,
11098 _: &crate::actions::EnableBreakpoint,
11099 window: &mut Window,
11100 cx: &mut Context<Self>,
11101 ) {
11102 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11103 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11104 continue;
11105 };
11106 self.edit_breakpoint_at_anchor(
11107 anchor,
11108 breakpoint,
11109 BreakpointEditAction::InvertState,
11110 cx,
11111 );
11112 }
11113 }
11114
11115 pub fn disable_breakpoint(
11116 &mut self,
11117 _: &crate::actions::DisableBreakpoint,
11118 window: &mut Window,
11119 cx: &mut Context<Self>,
11120 ) {
11121 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11122 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11123 continue;
11124 };
11125 self.edit_breakpoint_at_anchor(
11126 anchor,
11127 breakpoint,
11128 BreakpointEditAction::InvertState,
11129 cx,
11130 );
11131 }
11132 }
11133
11134 pub fn toggle_breakpoint(
11135 &mut self,
11136 _: &crate::actions::ToggleBreakpoint,
11137 window: &mut Window,
11138 cx: &mut Context<Self>,
11139 ) {
11140 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11141 if let Some(breakpoint) = breakpoint {
11142 self.edit_breakpoint_at_anchor(
11143 anchor,
11144 breakpoint,
11145 BreakpointEditAction::Toggle,
11146 cx,
11147 );
11148 } else {
11149 self.edit_breakpoint_at_anchor(
11150 anchor,
11151 Breakpoint::new_standard(),
11152 BreakpointEditAction::Toggle,
11153 cx,
11154 );
11155 }
11156 }
11157 }
11158
11159 pub fn edit_breakpoint_at_anchor(
11160 &mut self,
11161 breakpoint_position: Anchor,
11162 breakpoint: Breakpoint,
11163 edit_action: BreakpointEditAction,
11164 cx: &mut Context<Self>,
11165 ) {
11166 let Some(breakpoint_store) = &self.breakpoint_store else {
11167 return;
11168 };
11169
11170 let Some(buffer) = self
11171 .buffer
11172 .read(cx)
11173 .buffer_for_anchor(breakpoint_position, cx)
11174 else {
11175 return;
11176 };
11177
11178 breakpoint_store.update(cx, |breakpoint_store, cx| {
11179 breakpoint_store.toggle_breakpoint(
11180 buffer,
11181 BreakpointWithPosition {
11182 position: breakpoint_position.text_anchor,
11183 bp: breakpoint,
11184 },
11185 edit_action,
11186 cx,
11187 );
11188 });
11189
11190 cx.notify();
11191 }
11192
11193 #[cfg(any(test, feature = "test-support"))]
11194 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11195 self.breakpoint_store.clone()
11196 }
11197
11198 pub fn prepare_restore_change(
11199 &self,
11200 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11201 hunk: &MultiBufferDiffHunk,
11202 cx: &mut App,
11203 ) -> Option<()> {
11204 if hunk.is_created_file() {
11205 return None;
11206 }
11207 let buffer = self.buffer.read(cx);
11208 let diff = buffer.diff_for(hunk.buffer_id)?;
11209 let buffer = buffer.buffer(hunk.buffer_id)?;
11210 let buffer = buffer.read(cx);
11211 let original_text = diff
11212 .read(cx)
11213 .base_text()
11214 .as_rope()
11215 .slice(hunk.diff_base_byte_range.clone());
11216 let buffer_snapshot = buffer.snapshot();
11217 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11218 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11219 probe
11220 .0
11221 .start
11222 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11223 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11224 }) {
11225 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11226 Some(())
11227 } else {
11228 None
11229 }
11230 }
11231
11232 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11233 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11234 }
11235
11236 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11237 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11238 }
11239
11240 fn manipulate_lines<M>(
11241 &mut self,
11242 window: &mut Window,
11243 cx: &mut Context<Self>,
11244 mut manipulate: M,
11245 ) where
11246 M: FnMut(&str) -> LineManipulationResult,
11247 {
11248 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11249
11250 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11251 let buffer = self.buffer.read(cx).snapshot(cx);
11252
11253 let mut edits = Vec::new();
11254
11255 let selections = self.selections.all::<Point>(&display_map);
11256 let mut selections = selections.iter().peekable();
11257 let mut contiguous_row_selections = Vec::new();
11258 let mut new_selections = Vec::new();
11259 let mut added_lines = 0;
11260 let mut removed_lines = 0;
11261
11262 while let Some(selection) = selections.next() {
11263 let (start_row, end_row) = consume_contiguous_rows(
11264 &mut contiguous_row_selections,
11265 selection,
11266 &display_map,
11267 &mut selections,
11268 );
11269
11270 let start_point = Point::new(start_row.0, 0);
11271 let end_point = Point::new(
11272 end_row.previous_row().0,
11273 buffer.line_len(end_row.previous_row()),
11274 );
11275 let text = buffer
11276 .text_for_range(start_point..end_point)
11277 .collect::<String>();
11278
11279 let LineManipulationResult {
11280 new_text,
11281 line_count_before,
11282 line_count_after,
11283 } = manipulate(&text);
11284
11285 edits.push((start_point..end_point, new_text));
11286
11287 // Selections must change based on added and removed line count
11288 let start_row =
11289 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11290 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11291 new_selections.push(Selection {
11292 id: selection.id,
11293 start: start_row,
11294 end: end_row,
11295 goal: SelectionGoal::None,
11296 reversed: selection.reversed,
11297 });
11298
11299 if line_count_after > line_count_before {
11300 added_lines += line_count_after - line_count_before;
11301 } else if line_count_before > line_count_after {
11302 removed_lines += line_count_before - line_count_after;
11303 }
11304 }
11305
11306 self.transact(window, cx, |this, window, cx| {
11307 let buffer = this.buffer.update(cx, |buffer, cx| {
11308 buffer.edit(edits, None, cx);
11309 buffer.snapshot(cx)
11310 });
11311
11312 // Recalculate offsets on newly edited buffer
11313 let new_selections = new_selections
11314 .iter()
11315 .map(|s| {
11316 let start_point = Point::new(s.start.0, 0);
11317 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11318 Selection {
11319 id: s.id,
11320 start: buffer.point_to_offset(start_point),
11321 end: buffer.point_to_offset(end_point),
11322 goal: s.goal,
11323 reversed: s.reversed,
11324 }
11325 })
11326 .collect();
11327
11328 this.change_selections(Default::default(), window, cx, |s| {
11329 s.select(new_selections);
11330 });
11331
11332 this.request_autoscroll(Autoscroll::fit(), cx);
11333 });
11334 }
11335
11336 fn manipulate_immutable_lines<Fn>(
11337 &mut self,
11338 window: &mut Window,
11339 cx: &mut Context<Self>,
11340 mut callback: Fn,
11341 ) where
11342 Fn: FnMut(&mut Vec<&str>),
11343 {
11344 self.manipulate_lines(window, cx, |text| {
11345 let mut lines: Vec<&str> = text.split('\n').collect();
11346 let line_count_before = lines.len();
11347
11348 callback(&mut lines);
11349
11350 LineManipulationResult {
11351 new_text: lines.join("\n"),
11352 line_count_before,
11353 line_count_after: lines.len(),
11354 }
11355 });
11356 }
11357
11358 fn manipulate_mutable_lines<Fn>(
11359 &mut self,
11360 window: &mut Window,
11361 cx: &mut Context<Self>,
11362 mut callback: Fn,
11363 ) where
11364 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11365 {
11366 self.manipulate_lines(window, cx, |text| {
11367 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11368 let line_count_before = lines.len();
11369
11370 callback(&mut lines);
11371
11372 LineManipulationResult {
11373 new_text: lines.join("\n"),
11374 line_count_before,
11375 line_count_after: lines.len(),
11376 }
11377 });
11378 }
11379
11380 pub fn convert_indentation_to_spaces(
11381 &mut self,
11382 _: &ConvertIndentationToSpaces,
11383 window: &mut Window,
11384 cx: &mut Context<Self>,
11385 ) {
11386 let settings = self.buffer.read(cx).language_settings(cx);
11387 let tab_size = settings.tab_size.get() as usize;
11388
11389 self.manipulate_mutable_lines(window, cx, |lines| {
11390 // Allocates a reasonably sized scratch buffer once for the whole loop
11391 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11392 // Avoids recomputing spaces that could be inserted many times
11393 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11394 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11395 .collect();
11396
11397 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11398 let mut chars = line.as_ref().chars();
11399 let mut col = 0;
11400 let mut changed = false;
11401
11402 for ch in chars.by_ref() {
11403 match ch {
11404 ' ' => {
11405 reindented_line.push(' ');
11406 col += 1;
11407 }
11408 '\t' => {
11409 // \t are converted to spaces depending on the current column
11410 let spaces_len = tab_size - (col % tab_size);
11411 reindented_line.extend(&space_cache[spaces_len - 1]);
11412 col += spaces_len;
11413 changed = true;
11414 }
11415 _ => {
11416 // If we dont append before break, the character is consumed
11417 reindented_line.push(ch);
11418 break;
11419 }
11420 }
11421 }
11422
11423 if !changed {
11424 reindented_line.clear();
11425 continue;
11426 }
11427 // Append the rest of the line and replace old reference with new one
11428 reindented_line.extend(chars);
11429 *line = Cow::Owned(reindented_line.clone());
11430 reindented_line.clear();
11431 }
11432 });
11433 }
11434
11435 pub fn convert_indentation_to_tabs(
11436 &mut self,
11437 _: &ConvertIndentationToTabs,
11438 window: &mut Window,
11439 cx: &mut Context<Self>,
11440 ) {
11441 let settings = self.buffer.read(cx).language_settings(cx);
11442 let tab_size = settings.tab_size.get() as usize;
11443
11444 self.manipulate_mutable_lines(window, cx, |lines| {
11445 // Allocates a reasonably sized buffer once for the whole loop
11446 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11447 // Avoids recomputing spaces that could be inserted many times
11448 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11449 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11450 .collect();
11451
11452 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11453 let mut chars = line.chars();
11454 let mut spaces_count = 0;
11455 let mut first_non_indent_char = None;
11456 let mut changed = false;
11457
11458 for ch in chars.by_ref() {
11459 match ch {
11460 ' ' => {
11461 // Keep track of spaces. Append \t when we reach tab_size
11462 spaces_count += 1;
11463 changed = true;
11464 if spaces_count == tab_size {
11465 reindented_line.push('\t');
11466 spaces_count = 0;
11467 }
11468 }
11469 '\t' => {
11470 reindented_line.push('\t');
11471 spaces_count = 0;
11472 }
11473 _ => {
11474 // Dont append it yet, we might have remaining spaces
11475 first_non_indent_char = Some(ch);
11476 break;
11477 }
11478 }
11479 }
11480
11481 if !changed {
11482 reindented_line.clear();
11483 continue;
11484 }
11485 // Remaining spaces that didn't make a full tab stop
11486 if spaces_count > 0 {
11487 reindented_line.extend(&space_cache[spaces_count - 1]);
11488 }
11489 // If we consume an extra character that was not indentation, add it back
11490 if let Some(extra_char) = first_non_indent_char {
11491 reindented_line.push(extra_char);
11492 }
11493 // Append the rest of the line and replace old reference with new one
11494 reindented_line.extend(chars);
11495 *line = Cow::Owned(reindented_line.clone());
11496 reindented_line.clear();
11497 }
11498 });
11499 }
11500
11501 pub fn convert_to_upper_case(
11502 &mut self,
11503 _: &ConvertToUpperCase,
11504 window: &mut Window,
11505 cx: &mut Context<Self>,
11506 ) {
11507 self.manipulate_text(window, cx, |text| text.to_uppercase())
11508 }
11509
11510 pub fn convert_to_lower_case(
11511 &mut self,
11512 _: &ConvertToLowerCase,
11513 window: &mut Window,
11514 cx: &mut Context<Self>,
11515 ) {
11516 self.manipulate_text(window, cx, |text| text.to_lowercase())
11517 }
11518
11519 pub fn convert_to_title_case(
11520 &mut self,
11521 _: &ConvertToTitleCase,
11522 window: &mut Window,
11523 cx: &mut Context<Self>,
11524 ) {
11525 self.manipulate_text(window, cx, |text| {
11526 text.split('\n')
11527 .map(|line| line.to_case(Case::Title))
11528 .join("\n")
11529 })
11530 }
11531
11532 pub fn convert_to_snake_case(
11533 &mut self,
11534 _: &ConvertToSnakeCase,
11535 window: &mut Window,
11536 cx: &mut Context<Self>,
11537 ) {
11538 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11539 }
11540
11541 pub fn convert_to_kebab_case(
11542 &mut self,
11543 _: &ConvertToKebabCase,
11544 window: &mut Window,
11545 cx: &mut Context<Self>,
11546 ) {
11547 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11548 }
11549
11550 pub fn convert_to_upper_camel_case(
11551 &mut self,
11552 _: &ConvertToUpperCamelCase,
11553 window: &mut Window,
11554 cx: &mut Context<Self>,
11555 ) {
11556 self.manipulate_text(window, cx, |text| {
11557 text.split('\n')
11558 .map(|line| line.to_case(Case::UpperCamel))
11559 .join("\n")
11560 })
11561 }
11562
11563 pub fn convert_to_lower_camel_case(
11564 &mut self,
11565 _: &ConvertToLowerCamelCase,
11566 window: &mut Window,
11567 cx: &mut Context<Self>,
11568 ) {
11569 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11570 }
11571
11572 pub fn convert_to_opposite_case(
11573 &mut self,
11574 _: &ConvertToOppositeCase,
11575 window: &mut Window,
11576 cx: &mut Context<Self>,
11577 ) {
11578 self.manipulate_text(window, cx, |text| {
11579 text.chars()
11580 .fold(String::with_capacity(text.len()), |mut t, c| {
11581 if c.is_uppercase() {
11582 t.extend(c.to_lowercase());
11583 } else {
11584 t.extend(c.to_uppercase());
11585 }
11586 t
11587 })
11588 })
11589 }
11590
11591 pub fn convert_to_sentence_case(
11592 &mut self,
11593 _: &ConvertToSentenceCase,
11594 window: &mut Window,
11595 cx: &mut Context<Self>,
11596 ) {
11597 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11598 }
11599
11600 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11601 self.manipulate_text(window, cx, |text| {
11602 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11603 if has_upper_case_characters {
11604 text.to_lowercase()
11605 } else {
11606 text.to_uppercase()
11607 }
11608 })
11609 }
11610
11611 pub fn convert_to_rot13(
11612 &mut self,
11613 _: &ConvertToRot13,
11614 window: &mut Window,
11615 cx: &mut Context<Self>,
11616 ) {
11617 self.manipulate_text(window, cx, |text| {
11618 text.chars()
11619 .map(|c| match c {
11620 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11621 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11622 _ => c,
11623 })
11624 .collect()
11625 })
11626 }
11627
11628 pub fn convert_to_rot47(
11629 &mut self,
11630 _: &ConvertToRot47,
11631 window: &mut Window,
11632 cx: &mut Context<Self>,
11633 ) {
11634 self.manipulate_text(window, cx, |text| {
11635 text.chars()
11636 .map(|c| {
11637 let code_point = c as u32;
11638 if code_point >= 33 && code_point <= 126 {
11639 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11640 }
11641 c
11642 })
11643 .collect()
11644 })
11645 }
11646
11647 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11648 where
11649 Fn: FnMut(&str) -> String,
11650 {
11651 let buffer = self.buffer.read(cx).snapshot(cx);
11652
11653 let mut new_selections = Vec::new();
11654 let mut edits = Vec::new();
11655 let mut selection_adjustment = 0i32;
11656
11657 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11658 let selection_is_empty = selection.is_empty();
11659
11660 let (start, end) = if selection_is_empty {
11661 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11662 (word_range.start, word_range.end)
11663 } else {
11664 (
11665 buffer.point_to_offset(selection.start),
11666 buffer.point_to_offset(selection.end),
11667 )
11668 };
11669
11670 let text = buffer.text_for_range(start..end).collect::<String>();
11671 let old_length = text.len() as i32;
11672 let text = callback(&text);
11673
11674 new_selections.push(Selection {
11675 start: (start as i32 - selection_adjustment) as usize,
11676 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11677 goal: SelectionGoal::None,
11678 id: selection.id,
11679 reversed: selection.reversed,
11680 });
11681
11682 selection_adjustment += old_length - text.len() as i32;
11683
11684 edits.push((start..end, text));
11685 }
11686
11687 self.transact(window, cx, |this, window, cx| {
11688 this.buffer.update(cx, |buffer, cx| {
11689 buffer.edit(edits, None, cx);
11690 });
11691
11692 this.change_selections(Default::default(), window, cx, |s| {
11693 s.select(new_selections);
11694 });
11695
11696 this.request_autoscroll(Autoscroll::fit(), cx);
11697 });
11698 }
11699
11700 pub fn move_selection_on_drop(
11701 &mut self,
11702 selection: &Selection<Anchor>,
11703 target: DisplayPoint,
11704 is_cut: bool,
11705 window: &mut Window,
11706 cx: &mut Context<Self>,
11707 ) {
11708 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11709 let buffer = display_map.buffer_snapshot();
11710 let mut edits = Vec::new();
11711 let insert_point = display_map
11712 .clip_point(target, Bias::Left)
11713 .to_point(&display_map);
11714 let text = buffer
11715 .text_for_range(selection.start..selection.end)
11716 .collect::<String>();
11717 if is_cut {
11718 edits.push(((selection.start..selection.end), String::new()));
11719 }
11720 let insert_anchor = buffer.anchor_before(insert_point);
11721 edits.push(((insert_anchor..insert_anchor), text));
11722 let last_edit_start = insert_anchor.bias_left(buffer);
11723 let last_edit_end = insert_anchor.bias_right(buffer);
11724 self.transact(window, cx, |this, window, cx| {
11725 this.buffer.update(cx, |buffer, cx| {
11726 buffer.edit(edits, None, cx);
11727 });
11728 this.change_selections(Default::default(), window, cx, |s| {
11729 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11730 });
11731 });
11732 }
11733
11734 pub fn clear_selection_drag_state(&mut self) {
11735 self.selection_drag_state = SelectionDragState::None;
11736 }
11737
11738 pub fn duplicate(
11739 &mut self,
11740 upwards: bool,
11741 whole_lines: bool,
11742 window: &mut Window,
11743 cx: &mut Context<Self>,
11744 ) {
11745 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11746
11747 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11748 let buffer = display_map.buffer_snapshot();
11749 let selections = self.selections.all::<Point>(&display_map);
11750
11751 let mut edits = Vec::new();
11752 let mut selections_iter = selections.iter().peekable();
11753 while let Some(selection) = selections_iter.next() {
11754 let mut rows = selection.spanned_rows(false, &display_map);
11755 // duplicate line-wise
11756 if whole_lines || selection.start == selection.end {
11757 // Avoid duplicating the same lines twice.
11758 while let Some(next_selection) = selections_iter.peek() {
11759 let next_rows = next_selection.spanned_rows(false, &display_map);
11760 if next_rows.start < rows.end {
11761 rows.end = next_rows.end;
11762 selections_iter.next().unwrap();
11763 } else {
11764 break;
11765 }
11766 }
11767
11768 // Copy the text from the selected row region and splice it either at the start
11769 // or end of the region.
11770 let start = Point::new(rows.start.0, 0);
11771 let end = Point::new(
11772 rows.end.previous_row().0,
11773 buffer.line_len(rows.end.previous_row()),
11774 );
11775
11776 let mut text = buffer.text_for_range(start..end).collect::<String>();
11777
11778 let insert_location = if upwards {
11779 // When duplicating upward, we need to insert before the current line.
11780 // If we're on the last line and it doesn't end with a newline,
11781 // we need to add a newline before the duplicated content.
11782 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11783 && buffer.max_point().column > 0
11784 && !text.ends_with('\n');
11785
11786 if needs_leading_newline {
11787 text.insert(0, '\n');
11788 end
11789 } else {
11790 text.push('\n');
11791 Point::new(rows.end.0, 0)
11792 }
11793 } else {
11794 text.push('\n');
11795 start
11796 };
11797 edits.push((insert_location..insert_location, text));
11798 } else {
11799 // duplicate character-wise
11800 let start = selection.start;
11801 let end = selection.end;
11802 let text = buffer.text_for_range(start..end).collect::<String>();
11803 edits.push((selection.end..selection.end, text));
11804 }
11805 }
11806
11807 self.transact(window, cx, |this, _, cx| {
11808 this.buffer.update(cx, |buffer, cx| {
11809 buffer.edit(edits, None, cx);
11810 });
11811
11812 this.request_autoscroll(Autoscroll::fit(), cx);
11813 });
11814 }
11815
11816 pub fn duplicate_line_up(
11817 &mut self,
11818 _: &DuplicateLineUp,
11819 window: &mut Window,
11820 cx: &mut Context<Self>,
11821 ) {
11822 self.duplicate(true, true, window, cx);
11823 }
11824
11825 pub fn duplicate_line_down(
11826 &mut self,
11827 _: &DuplicateLineDown,
11828 window: &mut Window,
11829 cx: &mut Context<Self>,
11830 ) {
11831 self.duplicate(false, true, window, cx);
11832 }
11833
11834 pub fn duplicate_selection(
11835 &mut self,
11836 _: &DuplicateSelection,
11837 window: &mut Window,
11838 cx: &mut Context<Self>,
11839 ) {
11840 self.duplicate(false, false, window, cx);
11841 }
11842
11843 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11845 if self.mode.is_single_line() {
11846 cx.propagate();
11847 return;
11848 }
11849
11850 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11851 let buffer = self.buffer.read(cx).snapshot(cx);
11852
11853 let mut edits = Vec::new();
11854 let mut unfold_ranges = Vec::new();
11855 let mut refold_creases = Vec::new();
11856
11857 let selections = self.selections.all::<Point>(&display_map);
11858 let mut selections = selections.iter().peekable();
11859 let mut contiguous_row_selections = Vec::new();
11860 let mut new_selections = Vec::new();
11861
11862 while let Some(selection) = selections.next() {
11863 // Find all the selections that span a contiguous row range
11864 let (start_row, end_row) = consume_contiguous_rows(
11865 &mut contiguous_row_selections,
11866 selection,
11867 &display_map,
11868 &mut selections,
11869 );
11870
11871 // Move the text spanned by the row range to be before the line preceding the row range
11872 if start_row.0 > 0 {
11873 let range_to_move = Point::new(
11874 start_row.previous_row().0,
11875 buffer.line_len(start_row.previous_row()),
11876 )
11877 ..Point::new(
11878 end_row.previous_row().0,
11879 buffer.line_len(end_row.previous_row()),
11880 );
11881 let insertion_point = display_map
11882 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11883 .0;
11884
11885 // Don't move lines across excerpts
11886 if buffer
11887 .excerpt_containing(insertion_point..range_to_move.end)
11888 .is_some()
11889 {
11890 let text = buffer
11891 .text_for_range(range_to_move.clone())
11892 .flat_map(|s| s.chars())
11893 .skip(1)
11894 .chain(['\n'])
11895 .collect::<String>();
11896
11897 edits.push((
11898 buffer.anchor_after(range_to_move.start)
11899 ..buffer.anchor_before(range_to_move.end),
11900 String::new(),
11901 ));
11902 let insertion_anchor = buffer.anchor_after(insertion_point);
11903 edits.push((insertion_anchor..insertion_anchor, text));
11904
11905 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11906
11907 // Move selections up
11908 new_selections.extend(contiguous_row_selections.drain(..).map(
11909 |mut selection| {
11910 selection.start.row -= row_delta;
11911 selection.end.row -= row_delta;
11912 selection
11913 },
11914 ));
11915
11916 // Move folds up
11917 unfold_ranges.push(range_to_move.clone());
11918 for fold in display_map.folds_in_range(
11919 buffer.anchor_before(range_to_move.start)
11920 ..buffer.anchor_after(range_to_move.end),
11921 ) {
11922 let mut start = fold.range.start.to_point(&buffer);
11923 let mut end = fold.range.end.to_point(&buffer);
11924 start.row -= row_delta;
11925 end.row -= row_delta;
11926 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11927 }
11928 }
11929 }
11930
11931 // If we didn't move line(s), preserve the existing selections
11932 new_selections.append(&mut contiguous_row_selections);
11933 }
11934
11935 self.transact(window, cx, |this, window, cx| {
11936 this.unfold_ranges(&unfold_ranges, true, true, cx);
11937 this.buffer.update(cx, |buffer, cx| {
11938 for (range, text) in edits {
11939 buffer.edit([(range, text)], None, cx);
11940 }
11941 });
11942 this.fold_creases(refold_creases, true, window, cx);
11943 this.change_selections(Default::default(), window, cx, |s| {
11944 s.select(new_selections);
11945 })
11946 });
11947 }
11948
11949 pub fn move_line_down(
11950 &mut self,
11951 _: &MoveLineDown,
11952 window: &mut Window,
11953 cx: &mut Context<Self>,
11954 ) {
11955 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11956 if self.mode.is_single_line() {
11957 cx.propagate();
11958 return;
11959 }
11960
11961 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11962 let buffer = self.buffer.read(cx).snapshot(cx);
11963
11964 let mut edits = Vec::new();
11965 let mut unfold_ranges = Vec::new();
11966 let mut refold_creases = Vec::new();
11967
11968 let selections = self.selections.all::<Point>(&display_map);
11969 let mut selections = selections.iter().peekable();
11970 let mut contiguous_row_selections = Vec::new();
11971 let mut new_selections = Vec::new();
11972
11973 while let Some(selection) = selections.next() {
11974 // Find all the selections that span a contiguous row range
11975 let (start_row, end_row) = consume_contiguous_rows(
11976 &mut contiguous_row_selections,
11977 selection,
11978 &display_map,
11979 &mut selections,
11980 );
11981
11982 // Move the text spanned by the row range to be after the last line of the row range
11983 if end_row.0 <= buffer.max_point().row {
11984 let range_to_move =
11985 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11986 let insertion_point = display_map
11987 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11988 .0;
11989
11990 // Don't move lines across excerpt boundaries
11991 if buffer
11992 .excerpt_containing(range_to_move.start..insertion_point)
11993 .is_some()
11994 {
11995 let mut text = String::from("\n");
11996 text.extend(buffer.text_for_range(range_to_move.clone()));
11997 text.pop(); // Drop trailing newline
11998 edits.push((
11999 buffer.anchor_after(range_to_move.start)
12000 ..buffer.anchor_before(range_to_move.end),
12001 String::new(),
12002 ));
12003 let insertion_anchor = buffer.anchor_after(insertion_point);
12004 edits.push((insertion_anchor..insertion_anchor, text));
12005
12006 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12007
12008 // Move selections down
12009 new_selections.extend(contiguous_row_selections.drain(..).map(
12010 |mut selection| {
12011 selection.start.row += row_delta;
12012 selection.end.row += row_delta;
12013 selection
12014 },
12015 ));
12016
12017 // Move folds down
12018 unfold_ranges.push(range_to_move.clone());
12019 for fold in display_map.folds_in_range(
12020 buffer.anchor_before(range_to_move.start)
12021 ..buffer.anchor_after(range_to_move.end),
12022 ) {
12023 let mut start = fold.range.start.to_point(&buffer);
12024 let mut end = fold.range.end.to_point(&buffer);
12025 start.row += row_delta;
12026 end.row += row_delta;
12027 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12028 }
12029 }
12030 }
12031
12032 // If we didn't move line(s), preserve the existing selections
12033 new_selections.append(&mut contiguous_row_selections);
12034 }
12035
12036 self.transact(window, cx, |this, window, cx| {
12037 this.unfold_ranges(&unfold_ranges, true, true, cx);
12038 this.buffer.update(cx, |buffer, cx| {
12039 for (range, text) in edits {
12040 buffer.edit([(range, text)], None, cx);
12041 }
12042 });
12043 this.fold_creases(refold_creases, true, window, cx);
12044 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12045 });
12046 }
12047
12048 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12049 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12050 let text_layout_details = &self.text_layout_details(window);
12051 self.transact(window, cx, |this, window, cx| {
12052 let edits = this.change_selections(Default::default(), window, cx, |s| {
12053 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12054 s.move_with(|display_map, selection| {
12055 if !selection.is_empty() {
12056 return;
12057 }
12058
12059 let mut head = selection.head();
12060 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12061 if head.column() == display_map.line_len(head.row()) {
12062 transpose_offset = display_map
12063 .buffer_snapshot()
12064 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12065 }
12066
12067 if transpose_offset == 0 {
12068 return;
12069 }
12070
12071 *head.column_mut() += 1;
12072 head = display_map.clip_point(head, Bias::Right);
12073 let goal = SelectionGoal::HorizontalPosition(
12074 display_map
12075 .x_for_display_point(head, text_layout_details)
12076 .into(),
12077 );
12078 selection.collapse_to(head, goal);
12079
12080 let transpose_start = display_map
12081 .buffer_snapshot()
12082 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12083 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12084 let transpose_end = display_map
12085 .buffer_snapshot()
12086 .clip_offset(transpose_offset + 1, Bias::Right);
12087 if let Some(ch) = display_map
12088 .buffer_snapshot()
12089 .chars_at(transpose_start)
12090 .next()
12091 {
12092 edits.push((transpose_start..transpose_offset, String::new()));
12093 edits.push((transpose_end..transpose_end, ch.to_string()));
12094 }
12095 }
12096 });
12097 edits
12098 });
12099 this.buffer
12100 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12101 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12102 this.change_selections(Default::default(), window, cx, |s| {
12103 s.select(selections);
12104 });
12105 });
12106 }
12107
12108 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12109 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12110 if self.mode.is_single_line() {
12111 cx.propagate();
12112 return;
12113 }
12114
12115 self.rewrap_impl(RewrapOptions::default(), cx)
12116 }
12117
12118 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12119 let buffer = self.buffer.read(cx).snapshot(cx);
12120 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12121
12122 #[derive(Clone, Debug, PartialEq)]
12123 enum CommentFormat {
12124 /// single line comment, with prefix for line
12125 Line(String),
12126 /// single line within a block comment, with prefix for line
12127 BlockLine(String),
12128 /// a single line of a block comment that includes the initial delimiter
12129 BlockCommentWithStart(BlockCommentConfig),
12130 /// a single line of a block comment that includes the ending delimiter
12131 BlockCommentWithEnd(BlockCommentConfig),
12132 }
12133
12134 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12135 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12136 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12137 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12138 .peekable();
12139
12140 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12141 row
12142 } else {
12143 return Vec::new();
12144 };
12145
12146 let language_settings = buffer.language_settings_at(selection.head(), cx);
12147 let language_scope = buffer.language_scope_at(selection.head());
12148
12149 let indent_and_prefix_for_row =
12150 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12151 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12152 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12153 &language_scope
12154 {
12155 let indent_end = Point::new(row, indent.len);
12156 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12157 let line_text_after_indent = buffer
12158 .text_for_range(indent_end..line_end)
12159 .collect::<String>();
12160
12161 let is_within_comment_override = buffer
12162 .language_scope_at(indent_end)
12163 .is_some_and(|scope| scope.override_name() == Some("comment"));
12164 let comment_delimiters = if is_within_comment_override {
12165 // we are within a comment syntax node, but we don't
12166 // yet know what kind of comment: block, doc or line
12167 match (
12168 language_scope.documentation_comment(),
12169 language_scope.block_comment(),
12170 ) {
12171 (Some(config), _) | (_, Some(config))
12172 if buffer.contains_str_at(indent_end, &config.start) =>
12173 {
12174 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12175 }
12176 (Some(config), _) | (_, Some(config))
12177 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12178 {
12179 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12180 }
12181 (Some(config), _) | (_, Some(config))
12182 if buffer.contains_str_at(indent_end, &config.prefix) =>
12183 {
12184 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12185 }
12186 (_, _) => language_scope
12187 .line_comment_prefixes()
12188 .iter()
12189 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12190 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12191 }
12192 } else {
12193 // we not in an overridden comment node, but we may
12194 // be within a non-overridden line comment node
12195 language_scope
12196 .line_comment_prefixes()
12197 .iter()
12198 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12199 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12200 };
12201
12202 let rewrap_prefix = language_scope
12203 .rewrap_prefixes()
12204 .iter()
12205 .find_map(|prefix_regex| {
12206 prefix_regex.find(&line_text_after_indent).map(|mat| {
12207 if mat.start() == 0 {
12208 Some(mat.as_str().to_string())
12209 } else {
12210 None
12211 }
12212 })
12213 })
12214 .flatten();
12215 (comment_delimiters, rewrap_prefix)
12216 } else {
12217 (None, None)
12218 };
12219 (indent, comment_prefix, rewrap_prefix)
12220 };
12221
12222 let mut ranges = Vec::new();
12223 let from_empty_selection = selection.is_empty();
12224
12225 let mut current_range_start = first_row;
12226 let mut prev_row = first_row;
12227 let (
12228 mut current_range_indent,
12229 mut current_range_comment_delimiters,
12230 mut current_range_rewrap_prefix,
12231 ) = indent_and_prefix_for_row(first_row);
12232
12233 for row in non_blank_rows_iter.skip(1) {
12234 let has_paragraph_break = row > prev_row + 1;
12235
12236 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12237 indent_and_prefix_for_row(row);
12238
12239 let has_indent_change = row_indent != current_range_indent;
12240 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12241
12242 let has_boundary_change = has_comment_change
12243 || row_rewrap_prefix.is_some()
12244 || (has_indent_change && current_range_comment_delimiters.is_some());
12245
12246 if has_paragraph_break || has_boundary_change {
12247 ranges.push((
12248 language_settings.clone(),
12249 Point::new(current_range_start, 0)
12250 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12251 current_range_indent,
12252 current_range_comment_delimiters.clone(),
12253 current_range_rewrap_prefix.clone(),
12254 from_empty_selection,
12255 ));
12256 current_range_start = row;
12257 current_range_indent = row_indent;
12258 current_range_comment_delimiters = row_comment_delimiters;
12259 current_range_rewrap_prefix = row_rewrap_prefix;
12260 }
12261 prev_row = row;
12262 }
12263
12264 ranges.push((
12265 language_settings.clone(),
12266 Point::new(current_range_start, 0)
12267 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12268 current_range_indent,
12269 current_range_comment_delimiters,
12270 current_range_rewrap_prefix,
12271 from_empty_selection,
12272 ));
12273
12274 ranges
12275 });
12276
12277 let mut edits = Vec::new();
12278 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12279
12280 for (
12281 language_settings,
12282 wrap_range,
12283 mut indent_size,
12284 comment_prefix,
12285 rewrap_prefix,
12286 from_empty_selection,
12287 ) in wrap_ranges
12288 {
12289 let mut start_row = wrap_range.start.row;
12290 let mut end_row = wrap_range.end.row;
12291
12292 // Skip selections that overlap with a range that has already been rewrapped.
12293 let selection_range = start_row..end_row;
12294 if rewrapped_row_ranges
12295 .iter()
12296 .any(|range| range.overlaps(&selection_range))
12297 {
12298 continue;
12299 }
12300
12301 let tab_size = language_settings.tab_size;
12302
12303 let (line_prefix, inside_comment) = match &comment_prefix {
12304 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12305 (Some(prefix.as_str()), true)
12306 }
12307 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12308 (Some(prefix.as_ref()), true)
12309 }
12310 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12311 start: _,
12312 end: _,
12313 prefix,
12314 tab_size,
12315 })) => {
12316 indent_size.len += tab_size;
12317 (Some(prefix.as_ref()), true)
12318 }
12319 None => (None, false),
12320 };
12321 let indent_prefix = indent_size.chars().collect::<String>();
12322 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12323
12324 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12325 RewrapBehavior::InComments => inside_comment,
12326 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12327 RewrapBehavior::Anywhere => true,
12328 };
12329
12330 let should_rewrap = options.override_language_settings
12331 || allow_rewrap_based_on_language
12332 || self.hard_wrap.is_some();
12333 if !should_rewrap {
12334 continue;
12335 }
12336
12337 if from_empty_selection {
12338 'expand_upwards: while start_row > 0 {
12339 let prev_row = start_row - 1;
12340 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12341 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12342 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12343 {
12344 start_row = prev_row;
12345 } else {
12346 break 'expand_upwards;
12347 }
12348 }
12349
12350 'expand_downwards: while end_row < buffer.max_point().row {
12351 let next_row = end_row + 1;
12352 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12353 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12354 && !buffer.is_line_blank(MultiBufferRow(next_row))
12355 {
12356 end_row = next_row;
12357 } else {
12358 break 'expand_downwards;
12359 }
12360 }
12361 }
12362
12363 let start = Point::new(start_row, 0);
12364 let start_offset = ToOffset::to_offset(&start, &buffer);
12365 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12366 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12367 let mut first_line_delimiter = None;
12368 let mut last_line_delimiter = None;
12369 let Some(lines_without_prefixes) = selection_text
12370 .lines()
12371 .enumerate()
12372 .map(|(ix, line)| {
12373 let line_trimmed = line.trim_start();
12374 if rewrap_prefix.is_some() && ix > 0 {
12375 Ok(line_trimmed)
12376 } else if let Some(
12377 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12378 start,
12379 prefix,
12380 end,
12381 tab_size,
12382 })
12383 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12384 start,
12385 prefix,
12386 end,
12387 tab_size,
12388 }),
12389 ) = &comment_prefix
12390 {
12391 let line_trimmed = line_trimmed
12392 .strip_prefix(start.as_ref())
12393 .map(|s| {
12394 let mut indent_size = indent_size;
12395 indent_size.len -= tab_size;
12396 let indent_prefix: String = indent_size.chars().collect();
12397 first_line_delimiter = Some((indent_prefix, start));
12398 s.trim_start()
12399 })
12400 .unwrap_or(line_trimmed);
12401 let line_trimmed = line_trimmed
12402 .strip_suffix(end.as_ref())
12403 .map(|s| {
12404 last_line_delimiter = Some(end);
12405 s.trim_end()
12406 })
12407 .unwrap_or(line_trimmed);
12408 let line_trimmed = line_trimmed
12409 .strip_prefix(prefix.as_ref())
12410 .unwrap_or(line_trimmed);
12411 Ok(line_trimmed)
12412 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12413 line_trimmed.strip_prefix(prefix).with_context(|| {
12414 format!("line did not start with prefix {prefix:?}: {line:?}")
12415 })
12416 } else {
12417 line_trimmed
12418 .strip_prefix(&line_prefix.trim_start())
12419 .with_context(|| {
12420 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12421 })
12422 }
12423 })
12424 .collect::<Result<Vec<_>, _>>()
12425 .log_err()
12426 else {
12427 continue;
12428 };
12429
12430 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12431 buffer
12432 .language_settings_at(Point::new(start_row, 0), cx)
12433 .preferred_line_length as usize
12434 });
12435
12436 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12437 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12438 } else {
12439 line_prefix.clone()
12440 };
12441
12442 let wrapped_text = {
12443 let mut wrapped_text = wrap_with_prefix(
12444 line_prefix,
12445 subsequent_lines_prefix,
12446 lines_without_prefixes.join("\n"),
12447 wrap_column,
12448 tab_size,
12449 options.preserve_existing_whitespace,
12450 );
12451
12452 if let Some((indent, delimiter)) = first_line_delimiter {
12453 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12454 }
12455 if let Some(last_line) = last_line_delimiter {
12456 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12457 }
12458
12459 wrapped_text
12460 };
12461
12462 // TODO: should always use char-based diff while still supporting cursor behavior that
12463 // matches vim.
12464 let mut diff_options = DiffOptions::default();
12465 if options.override_language_settings {
12466 diff_options.max_word_diff_len = 0;
12467 diff_options.max_word_diff_line_count = 0;
12468 } else {
12469 diff_options.max_word_diff_len = usize::MAX;
12470 diff_options.max_word_diff_line_count = usize::MAX;
12471 }
12472
12473 for (old_range, new_text) in
12474 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12475 {
12476 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12477 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12478 edits.push((edit_start..edit_end, new_text));
12479 }
12480
12481 rewrapped_row_ranges.push(start_row..=end_row);
12482 }
12483
12484 self.buffer
12485 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12486 }
12487
12488 pub fn cut_common(
12489 &mut self,
12490 cut_no_selection_line: bool,
12491 window: &mut Window,
12492 cx: &mut Context<Self>,
12493 ) -> ClipboardItem {
12494 let mut text = String::new();
12495 let buffer = self.buffer.read(cx).snapshot(cx);
12496 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12497 let mut clipboard_selections = Vec::with_capacity(selections.len());
12498 {
12499 let max_point = buffer.max_point();
12500 let mut is_first = true;
12501 for selection in &mut selections {
12502 let is_entire_line =
12503 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12504 if is_entire_line {
12505 selection.start = Point::new(selection.start.row, 0);
12506 if !selection.is_empty() && selection.end.column == 0 {
12507 selection.end = cmp::min(max_point, selection.end);
12508 } else {
12509 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12510 }
12511 selection.goal = SelectionGoal::None;
12512 }
12513 if is_first {
12514 is_first = false;
12515 } else {
12516 text += "\n";
12517 }
12518 let mut len = 0;
12519 for chunk in buffer.text_for_range(selection.start..selection.end) {
12520 text.push_str(chunk);
12521 len += chunk.len();
12522 }
12523 clipboard_selections.push(ClipboardSelection {
12524 len,
12525 is_entire_line,
12526 first_line_indent: buffer
12527 .indent_size_for_line(MultiBufferRow(selection.start.row))
12528 .len,
12529 });
12530 }
12531 }
12532
12533 self.transact(window, cx, |this, window, cx| {
12534 this.change_selections(Default::default(), window, cx, |s| {
12535 s.select(selections);
12536 });
12537 this.insert("", window, cx);
12538 });
12539 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12540 }
12541
12542 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12543 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12544 let item = self.cut_common(true, window, cx);
12545 cx.write_to_clipboard(item);
12546 }
12547
12548 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12549 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12550 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12551 s.move_with(|snapshot, sel| {
12552 if sel.is_empty() {
12553 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12554 }
12555 if sel.is_empty() {
12556 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12557 }
12558 });
12559 });
12560 let item = self.cut_common(false, window, cx);
12561 cx.set_global(KillRing(item))
12562 }
12563
12564 pub fn kill_ring_yank(
12565 &mut self,
12566 _: &KillRingYank,
12567 window: &mut Window,
12568 cx: &mut Context<Self>,
12569 ) {
12570 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12571 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12572 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12573 (kill_ring.text().to_string(), kill_ring.metadata_json())
12574 } else {
12575 return;
12576 }
12577 } else {
12578 return;
12579 };
12580 self.do_paste(&text, metadata, false, window, cx);
12581 }
12582
12583 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12584 self.do_copy(true, cx);
12585 }
12586
12587 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12588 self.do_copy(false, cx);
12589 }
12590
12591 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12592 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12593 let buffer = self.buffer.read(cx).read(cx);
12594 let mut text = String::new();
12595
12596 let mut clipboard_selections = Vec::with_capacity(selections.len());
12597 {
12598 let max_point = buffer.max_point();
12599 let mut is_first = true;
12600 for selection in &selections {
12601 let mut start = selection.start;
12602 let mut end = selection.end;
12603 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12604 let mut add_trailing_newline = false;
12605 if is_entire_line {
12606 start = Point::new(start.row, 0);
12607 let next_line_start = Point::new(end.row + 1, 0);
12608 if next_line_start <= max_point {
12609 end = next_line_start;
12610 } else {
12611 // We're on the last line without a trailing newline.
12612 // Copy to the end of the line and add a newline afterwards.
12613 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12614 add_trailing_newline = true;
12615 }
12616 }
12617
12618 let mut trimmed_selections = Vec::new();
12619 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12620 let row = MultiBufferRow(start.row);
12621 let first_indent = buffer.indent_size_for_line(row);
12622 if first_indent.len == 0 || start.column > first_indent.len {
12623 trimmed_selections.push(start..end);
12624 } else {
12625 trimmed_selections.push(
12626 Point::new(row.0, first_indent.len)
12627 ..Point::new(row.0, buffer.line_len(row)),
12628 );
12629 for row in start.row + 1..=end.row {
12630 let mut line_len = buffer.line_len(MultiBufferRow(row));
12631 if row == end.row {
12632 line_len = end.column;
12633 }
12634 if line_len == 0 {
12635 trimmed_selections
12636 .push(Point::new(row, 0)..Point::new(row, line_len));
12637 continue;
12638 }
12639 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12640 if row_indent_size.len >= first_indent.len {
12641 trimmed_selections.push(
12642 Point::new(row, first_indent.len)..Point::new(row, line_len),
12643 );
12644 } else {
12645 trimmed_selections.clear();
12646 trimmed_selections.push(start..end);
12647 break;
12648 }
12649 }
12650 }
12651 } else {
12652 trimmed_selections.push(start..end);
12653 }
12654
12655 for trimmed_range in trimmed_selections {
12656 if is_first {
12657 is_first = false;
12658 } else {
12659 text += "\n";
12660 }
12661 let mut len = 0;
12662 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12663 text.push_str(chunk);
12664 len += chunk.len();
12665 }
12666 if add_trailing_newline {
12667 text.push('\n');
12668 len += 1;
12669 }
12670 clipboard_selections.push(ClipboardSelection {
12671 len,
12672 is_entire_line,
12673 first_line_indent: buffer
12674 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12675 .len,
12676 });
12677 }
12678 }
12679 }
12680
12681 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12682 text,
12683 clipboard_selections,
12684 ));
12685 }
12686
12687 pub fn do_paste(
12688 &mut self,
12689 text: &String,
12690 clipboard_selections: Option<Vec<ClipboardSelection>>,
12691 handle_entire_lines: bool,
12692 window: &mut Window,
12693 cx: &mut Context<Self>,
12694 ) {
12695 if self.read_only(cx) {
12696 return;
12697 }
12698
12699 let clipboard_text = Cow::Borrowed(text.as_str());
12700
12701 self.transact(window, cx, |this, window, cx| {
12702 let had_active_edit_prediction = this.has_active_edit_prediction();
12703 let display_map = this.display_snapshot(cx);
12704 let old_selections = this.selections.all::<usize>(&display_map);
12705 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12706
12707 if let Some(mut clipboard_selections) = clipboard_selections {
12708 let all_selections_were_entire_line =
12709 clipboard_selections.iter().all(|s| s.is_entire_line);
12710 let first_selection_indent_column =
12711 clipboard_selections.first().map(|s| s.first_line_indent);
12712 if clipboard_selections.len() != old_selections.len() {
12713 clipboard_selections.drain(..);
12714 }
12715 let mut auto_indent_on_paste = true;
12716
12717 this.buffer.update(cx, |buffer, cx| {
12718 let snapshot = buffer.read(cx);
12719 auto_indent_on_paste = snapshot
12720 .language_settings_at(cursor_offset, cx)
12721 .auto_indent_on_paste;
12722
12723 let mut start_offset = 0;
12724 let mut edits = Vec::new();
12725 let mut original_indent_columns = Vec::new();
12726 for (ix, selection) in old_selections.iter().enumerate() {
12727 let to_insert;
12728 let entire_line;
12729 let original_indent_column;
12730 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12731 let end_offset = start_offset + clipboard_selection.len;
12732 to_insert = &clipboard_text[start_offset..end_offset];
12733 entire_line = clipboard_selection.is_entire_line;
12734 start_offset = end_offset + 1;
12735 original_indent_column = Some(clipboard_selection.first_line_indent);
12736 } else {
12737 to_insert = &*clipboard_text;
12738 entire_line = all_selections_were_entire_line;
12739 original_indent_column = first_selection_indent_column
12740 }
12741
12742 let (range, to_insert) =
12743 if selection.is_empty() && handle_entire_lines && entire_line {
12744 // If the corresponding selection was empty when this slice of the
12745 // clipboard text was written, then the entire line containing the
12746 // selection was copied. If this selection is also currently empty,
12747 // then paste the line before the current line of the buffer.
12748 let column = selection.start.to_point(&snapshot).column as usize;
12749 let line_start = selection.start - column;
12750 (line_start..line_start, Cow::Borrowed(to_insert))
12751 } else {
12752 let language = snapshot.language_at(selection.head());
12753 let range = selection.range();
12754 if let Some(language) = language
12755 && language.name() == "Markdown".into()
12756 {
12757 edit_for_markdown_paste(
12758 &snapshot,
12759 range,
12760 to_insert,
12761 url::Url::parse(to_insert).ok(),
12762 )
12763 } else {
12764 (range, Cow::Borrowed(to_insert))
12765 }
12766 };
12767
12768 edits.push((range, to_insert));
12769 original_indent_columns.push(original_indent_column);
12770 }
12771 drop(snapshot);
12772
12773 buffer.edit(
12774 edits,
12775 if auto_indent_on_paste {
12776 Some(AutoindentMode::Block {
12777 original_indent_columns,
12778 })
12779 } else {
12780 None
12781 },
12782 cx,
12783 );
12784 });
12785
12786 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12787 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12788 } else {
12789 let url = url::Url::parse(&clipboard_text).ok();
12790
12791 let auto_indent_mode = if !clipboard_text.is_empty() {
12792 Some(AutoindentMode::Block {
12793 original_indent_columns: Vec::new(),
12794 })
12795 } else {
12796 None
12797 };
12798
12799 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12800 let snapshot = buffer.snapshot(cx);
12801
12802 let anchors = old_selections
12803 .iter()
12804 .map(|s| {
12805 let anchor = snapshot.anchor_after(s.head());
12806 s.map(|_| anchor)
12807 })
12808 .collect::<Vec<_>>();
12809
12810 let mut edits = Vec::new();
12811
12812 for selection in old_selections.iter() {
12813 let language = snapshot.language_at(selection.head());
12814 let range = selection.range();
12815
12816 let (edit_range, edit_text) = if let Some(language) = language
12817 && language.name() == "Markdown".into()
12818 {
12819 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12820 } else {
12821 (range, clipboard_text.clone())
12822 };
12823
12824 edits.push((edit_range, edit_text));
12825 }
12826
12827 drop(snapshot);
12828 buffer.edit(edits, auto_indent_mode, cx);
12829
12830 anchors
12831 });
12832
12833 this.change_selections(Default::default(), window, cx, |s| {
12834 s.select_anchors(selection_anchors);
12835 });
12836 }
12837
12838 let trigger_in_words =
12839 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12840
12841 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12842 });
12843 }
12844
12845 pub fn diff_clipboard_with_selection(
12846 &mut self,
12847 _: &DiffClipboardWithSelection,
12848 window: &mut Window,
12849 cx: &mut Context<Self>,
12850 ) {
12851 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12852
12853 if selections.is_empty() {
12854 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12855 return;
12856 };
12857
12858 let clipboard_text = match cx.read_from_clipboard() {
12859 Some(item) => match item.entries().first() {
12860 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12861 _ => None,
12862 },
12863 None => None,
12864 };
12865
12866 let Some(clipboard_text) = clipboard_text else {
12867 log::warn!("Clipboard doesn't contain text.");
12868 return;
12869 };
12870
12871 window.dispatch_action(
12872 Box::new(DiffClipboardWithSelectionData {
12873 clipboard_text,
12874 editor: cx.entity(),
12875 }),
12876 cx,
12877 );
12878 }
12879
12880 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12881 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12882 if let Some(item) = cx.read_from_clipboard() {
12883 let entries = item.entries();
12884
12885 match entries.first() {
12886 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12887 // of all the pasted entries.
12888 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12889 .do_paste(
12890 clipboard_string.text(),
12891 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12892 true,
12893 window,
12894 cx,
12895 ),
12896 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12897 }
12898 }
12899 }
12900
12901 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12902 if self.read_only(cx) {
12903 return;
12904 }
12905
12906 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12907
12908 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12909 if let Some((selections, _)) =
12910 self.selection_history.transaction(transaction_id).cloned()
12911 {
12912 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12913 s.select_anchors(selections.to_vec());
12914 });
12915 } else {
12916 log::error!(
12917 "No entry in selection_history found for undo. \
12918 This may correspond to a bug where undo does not update the selection. \
12919 If this is occurring, please add details to \
12920 https://github.com/zed-industries/zed/issues/22692"
12921 );
12922 }
12923 self.request_autoscroll(Autoscroll::fit(), cx);
12924 self.unmark_text(window, cx);
12925 self.refresh_edit_prediction(true, false, window, cx);
12926 cx.emit(EditorEvent::Edited { transaction_id });
12927 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12928 }
12929 }
12930
12931 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12932 if self.read_only(cx) {
12933 return;
12934 }
12935
12936 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12937
12938 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12939 if let Some((_, Some(selections))) =
12940 self.selection_history.transaction(transaction_id).cloned()
12941 {
12942 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12943 s.select_anchors(selections.to_vec());
12944 });
12945 } else {
12946 log::error!(
12947 "No entry in selection_history found for redo. \
12948 This may correspond to a bug where undo does not update the selection. \
12949 If this is occurring, please add details to \
12950 https://github.com/zed-industries/zed/issues/22692"
12951 );
12952 }
12953 self.request_autoscroll(Autoscroll::fit(), cx);
12954 self.unmark_text(window, cx);
12955 self.refresh_edit_prediction(true, false, window, cx);
12956 cx.emit(EditorEvent::Edited { transaction_id });
12957 }
12958 }
12959
12960 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12961 self.buffer
12962 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12963 }
12964
12965 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12966 self.buffer
12967 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12968 }
12969
12970 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12971 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12972 self.change_selections(Default::default(), window, cx, |s| {
12973 s.move_with(|map, selection| {
12974 let cursor = if selection.is_empty() {
12975 movement::left(map, selection.start)
12976 } else {
12977 selection.start
12978 };
12979 selection.collapse_to(cursor, SelectionGoal::None);
12980 });
12981 })
12982 }
12983
12984 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12985 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12986 self.change_selections(Default::default(), window, cx, |s| {
12987 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12988 })
12989 }
12990
12991 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12992 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12993 self.change_selections(Default::default(), window, cx, |s| {
12994 s.move_with(|map, selection| {
12995 let cursor = if selection.is_empty() {
12996 movement::right(map, selection.end)
12997 } else {
12998 selection.end
12999 };
13000 selection.collapse_to(cursor, SelectionGoal::None)
13001 });
13002 })
13003 }
13004
13005 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13006 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13007 self.change_selections(Default::default(), window, cx, |s| {
13008 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13009 });
13010 }
13011
13012 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13013 if self.take_rename(true, window, cx).is_some() {
13014 return;
13015 }
13016
13017 if self.mode.is_single_line() {
13018 cx.propagate();
13019 return;
13020 }
13021
13022 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13023
13024 let text_layout_details = &self.text_layout_details(window);
13025 let selection_count = self.selections.count();
13026 let first_selection = self.selections.first_anchor();
13027
13028 self.change_selections(Default::default(), window, cx, |s| {
13029 s.move_with(|map, selection| {
13030 if !selection.is_empty() {
13031 selection.goal = SelectionGoal::None;
13032 }
13033 let (cursor, goal) = movement::up(
13034 map,
13035 selection.start,
13036 selection.goal,
13037 false,
13038 text_layout_details,
13039 );
13040 selection.collapse_to(cursor, goal);
13041 });
13042 });
13043
13044 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13045 {
13046 cx.propagate();
13047 }
13048 }
13049
13050 pub fn move_up_by_lines(
13051 &mut self,
13052 action: &MoveUpByLines,
13053 window: &mut Window,
13054 cx: &mut Context<Self>,
13055 ) {
13056 if self.take_rename(true, window, cx).is_some() {
13057 return;
13058 }
13059
13060 if self.mode.is_single_line() {
13061 cx.propagate();
13062 return;
13063 }
13064
13065 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13066
13067 let text_layout_details = &self.text_layout_details(window);
13068
13069 self.change_selections(Default::default(), window, cx, |s| {
13070 s.move_with(|map, selection| {
13071 if !selection.is_empty() {
13072 selection.goal = SelectionGoal::None;
13073 }
13074 let (cursor, goal) = movement::up_by_rows(
13075 map,
13076 selection.start,
13077 action.lines,
13078 selection.goal,
13079 false,
13080 text_layout_details,
13081 );
13082 selection.collapse_to(cursor, goal);
13083 });
13084 })
13085 }
13086
13087 pub fn move_down_by_lines(
13088 &mut self,
13089 action: &MoveDownByLines,
13090 window: &mut Window,
13091 cx: &mut Context<Self>,
13092 ) {
13093 if self.take_rename(true, window, cx).is_some() {
13094 return;
13095 }
13096
13097 if self.mode.is_single_line() {
13098 cx.propagate();
13099 return;
13100 }
13101
13102 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13103
13104 let text_layout_details = &self.text_layout_details(window);
13105
13106 self.change_selections(Default::default(), window, cx, |s| {
13107 s.move_with(|map, selection| {
13108 if !selection.is_empty() {
13109 selection.goal = SelectionGoal::None;
13110 }
13111 let (cursor, goal) = movement::down_by_rows(
13112 map,
13113 selection.start,
13114 action.lines,
13115 selection.goal,
13116 false,
13117 text_layout_details,
13118 );
13119 selection.collapse_to(cursor, goal);
13120 });
13121 })
13122 }
13123
13124 pub fn select_down_by_lines(
13125 &mut self,
13126 action: &SelectDownByLines,
13127 window: &mut Window,
13128 cx: &mut Context<Self>,
13129 ) {
13130 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13131 let text_layout_details = &self.text_layout_details(window);
13132 self.change_selections(Default::default(), window, cx, |s| {
13133 s.move_heads_with(|map, head, goal| {
13134 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13135 })
13136 })
13137 }
13138
13139 pub fn select_up_by_lines(
13140 &mut self,
13141 action: &SelectUpByLines,
13142 window: &mut Window,
13143 cx: &mut Context<Self>,
13144 ) {
13145 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13146 let text_layout_details = &self.text_layout_details(window);
13147 self.change_selections(Default::default(), window, cx, |s| {
13148 s.move_heads_with(|map, head, goal| {
13149 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13150 })
13151 })
13152 }
13153
13154 pub fn select_page_up(
13155 &mut self,
13156 _: &SelectPageUp,
13157 window: &mut Window,
13158 cx: &mut Context<Self>,
13159 ) {
13160 let Some(row_count) = self.visible_row_count() else {
13161 return;
13162 };
13163
13164 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13165
13166 let text_layout_details = &self.text_layout_details(window);
13167
13168 self.change_selections(Default::default(), window, cx, |s| {
13169 s.move_heads_with(|map, head, goal| {
13170 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13171 })
13172 })
13173 }
13174
13175 pub fn move_page_up(
13176 &mut self,
13177 action: &MovePageUp,
13178 window: &mut Window,
13179 cx: &mut Context<Self>,
13180 ) {
13181 if self.take_rename(true, window, cx).is_some() {
13182 return;
13183 }
13184
13185 if self
13186 .context_menu
13187 .borrow_mut()
13188 .as_mut()
13189 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13190 .unwrap_or(false)
13191 {
13192 return;
13193 }
13194
13195 if matches!(self.mode, EditorMode::SingleLine) {
13196 cx.propagate();
13197 return;
13198 }
13199
13200 let Some(row_count) = self.visible_row_count() else {
13201 return;
13202 };
13203
13204 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13205
13206 let effects = if action.center_cursor {
13207 SelectionEffects::scroll(Autoscroll::center())
13208 } else {
13209 SelectionEffects::default()
13210 };
13211
13212 let text_layout_details = &self.text_layout_details(window);
13213
13214 self.change_selections(effects, window, cx, |s| {
13215 s.move_with(|map, selection| {
13216 if !selection.is_empty() {
13217 selection.goal = SelectionGoal::None;
13218 }
13219 let (cursor, goal) = movement::up_by_rows(
13220 map,
13221 selection.end,
13222 row_count,
13223 selection.goal,
13224 false,
13225 text_layout_details,
13226 );
13227 selection.collapse_to(cursor, goal);
13228 });
13229 });
13230 }
13231
13232 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13233 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13234 let text_layout_details = &self.text_layout_details(window);
13235 self.change_selections(Default::default(), window, cx, |s| {
13236 s.move_heads_with(|map, head, goal| {
13237 movement::up(map, head, goal, false, text_layout_details)
13238 })
13239 })
13240 }
13241
13242 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13243 self.take_rename(true, window, cx);
13244
13245 if self.mode.is_single_line() {
13246 cx.propagate();
13247 return;
13248 }
13249
13250 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13251
13252 let text_layout_details = &self.text_layout_details(window);
13253 let selection_count = self.selections.count();
13254 let first_selection = self.selections.first_anchor();
13255
13256 self.change_selections(Default::default(), window, cx, |s| {
13257 s.move_with(|map, selection| {
13258 if !selection.is_empty() {
13259 selection.goal = SelectionGoal::None;
13260 }
13261 let (cursor, goal) = movement::down(
13262 map,
13263 selection.end,
13264 selection.goal,
13265 false,
13266 text_layout_details,
13267 );
13268 selection.collapse_to(cursor, goal);
13269 });
13270 });
13271
13272 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13273 {
13274 cx.propagate();
13275 }
13276 }
13277
13278 pub fn select_page_down(
13279 &mut self,
13280 _: &SelectPageDown,
13281 window: &mut Window,
13282 cx: &mut Context<Self>,
13283 ) {
13284 let Some(row_count) = self.visible_row_count() else {
13285 return;
13286 };
13287
13288 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13289
13290 let text_layout_details = &self.text_layout_details(window);
13291
13292 self.change_selections(Default::default(), window, cx, |s| {
13293 s.move_heads_with(|map, head, goal| {
13294 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13295 })
13296 })
13297 }
13298
13299 pub fn move_page_down(
13300 &mut self,
13301 action: &MovePageDown,
13302 window: &mut Window,
13303 cx: &mut Context<Self>,
13304 ) {
13305 if self.take_rename(true, window, cx).is_some() {
13306 return;
13307 }
13308
13309 if self
13310 .context_menu
13311 .borrow_mut()
13312 .as_mut()
13313 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13314 .unwrap_or(false)
13315 {
13316 return;
13317 }
13318
13319 if matches!(self.mode, EditorMode::SingleLine) {
13320 cx.propagate();
13321 return;
13322 }
13323
13324 let Some(row_count) = self.visible_row_count() else {
13325 return;
13326 };
13327
13328 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13329
13330 let effects = if action.center_cursor {
13331 SelectionEffects::scroll(Autoscroll::center())
13332 } else {
13333 SelectionEffects::default()
13334 };
13335
13336 let text_layout_details = &self.text_layout_details(window);
13337 self.change_selections(effects, window, cx, |s| {
13338 s.move_with(|map, selection| {
13339 if !selection.is_empty() {
13340 selection.goal = SelectionGoal::None;
13341 }
13342 let (cursor, goal) = movement::down_by_rows(
13343 map,
13344 selection.end,
13345 row_count,
13346 selection.goal,
13347 false,
13348 text_layout_details,
13349 );
13350 selection.collapse_to(cursor, goal);
13351 });
13352 });
13353 }
13354
13355 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13356 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13357 let text_layout_details = &self.text_layout_details(window);
13358 self.change_selections(Default::default(), window, cx, |s| {
13359 s.move_heads_with(|map, head, goal| {
13360 movement::down(map, head, goal, false, text_layout_details)
13361 })
13362 });
13363 }
13364
13365 pub fn context_menu_first(
13366 &mut self,
13367 _: &ContextMenuFirst,
13368 window: &mut Window,
13369 cx: &mut Context<Self>,
13370 ) {
13371 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13372 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13373 }
13374 }
13375
13376 pub fn context_menu_prev(
13377 &mut self,
13378 _: &ContextMenuPrevious,
13379 window: &mut Window,
13380 cx: &mut Context<Self>,
13381 ) {
13382 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13383 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13384 }
13385 }
13386
13387 pub fn context_menu_next(
13388 &mut self,
13389 _: &ContextMenuNext,
13390 window: &mut Window,
13391 cx: &mut Context<Self>,
13392 ) {
13393 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13394 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13395 }
13396 }
13397
13398 pub fn context_menu_last(
13399 &mut self,
13400 _: &ContextMenuLast,
13401 window: &mut Window,
13402 cx: &mut Context<Self>,
13403 ) {
13404 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13405 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13406 }
13407 }
13408
13409 pub fn signature_help_prev(
13410 &mut self,
13411 _: &SignatureHelpPrevious,
13412 _: &mut Window,
13413 cx: &mut Context<Self>,
13414 ) {
13415 if let Some(popover) = self.signature_help_state.popover_mut() {
13416 if popover.current_signature == 0 {
13417 popover.current_signature = popover.signatures.len() - 1;
13418 } else {
13419 popover.current_signature -= 1;
13420 }
13421 cx.notify();
13422 }
13423 }
13424
13425 pub fn signature_help_next(
13426 &mut self,
13427 _: &SignatureHelpNext,
13428 _: &mut Window,
13429 cx: &mut Context<Self>,
13430 ) {
13431 if let Some(popover) = self.signature_help_state.popover_mut() {
13432 if popover.current_signature + 1 == popover.signatures.len() {
13433 popover.current_signature = 0;
13434 } else {
13435 popover.current_signature += 1;
13436 }
13437 cx.notify();
13438 }
13439 }
13440
13441 pub fn move_to_previous_word_start(
13442 &mut self,
13443 _: &MoveToPreviousWordStart,
13444 window: &mut Window,
13445 cx: &mut Context<Self>,
13446 ) {
13447 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13448 self.change_selections(Default::default(), window, cx, |s| {
13449 s.move_cursors_with(|map, head, _| {
13450 (
13451 movement::previous_word_start(map, head),
13452 SelectionGoal::None,
13453 )
13454 });
13455 })
13456 }
13457
13458 pub fn move_to_previous_subword_start(
13459 &mut self,
13460 _: &MoveToPreviousSubwordStart,
13461 window: &mut Window,
13462 cx: &mut Context<Self>,
13463 ) {
13464 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13465 self.change_selections(Default::default(), window, cx, |s| {
13466 s.move_cursors_with(|map, head, _| {
13467 (
13468 movement::previous_subword_start(map, head),
13469 SelectionGoal::None,
13470 )
13471 });
13472 })
13473 }
13474
13475 pub fn select_to_previous_word_start(
13476 &mut self,
13477 _: &SelectToPreviousWordStart,
13478 window: &mut Window,
13479 cx: &mut Context<Self>,
13480 ) {
13481 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13482 self.change_selections(Default::default(), window, cx, |s| {
13483 s.move_heads_with(|map, head, _| {
13484 (
13485 movement::previous_word_start(map, head),
13486 SelectionGoal::None,
13487 )
13488 });
13489 })
13490 }
13491
13492 pub fn select_to_previous_subword_start(
13493 &mut self,
13494 _: &SelectToPreviousSubwordStart,
13495 window: &mut Window,
13496 cx: &mut Context<Self>,
13497 ) {
13498 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13499 self.change_selections(Default::default(), window, cx, |s| {
13500 s.move_heads_with(|map, head, _| {
13501 (
13502 movement::previous_subword_start(map, head),
13503 SelectionGoal::None,
13504 )
13505 });
13506 })
13507 }
13508
13509 pub fn delete_to_previous_word_start(
13510 &mut self,
13511 action: &DeleteToPreviousWordStart,
13512 window: &mut Window,
13513 cx: &mut Context<Self>,
13514 ) {
13515 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13516 self.transact(window, cx, |this, window, cx| {
13517 this.select_autoclose_pair(window, cx);
13518 this.change_selections(Default::default(), window, cx, |s| {
13519 s.move_with(|map, selection| {
13520 if selection.is_empty() {
13521 let mut cursor = if action.ignore_newlines {
13522 movement::previous_word_start(map, selection.head())
13523 } else {
13524 movement::previous_word_start_or_newline(map, selection.head())
13525 };
13526 cursor = movement::adjust_greedy_deletion(
13527 map,
13528 selection.head(),
13529 cursor,
13530 action.ignore_brackets,
13531 );
13532 selection.set_head(cursor, SelectionGoal::None);
13533 }
13534 });
13535 });
13536 this.insert("", window, cx);
13537 });
13538 }
13539
13540 pub fn delete_to_previous_subword_start(
13541 &mut self,
13542 _: &DeleteToPreviousSubwordStart,
13543 window: &mut Window,
13544 cx: &mut Context<Self>,
13545 ) {
13546 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13547 self.transact(window, cx, |this, window, cx| {
13548 this.select_autoclose_pair(window, cx);
13549 this.change_selections(Default::default(), window, cx, |s| {
13550 s.move_with(|map, selection| {
13551 if selection.is_empty() {
13552 let mut cursor = movement::previous_subword_start(map, selection.head());
13553 cursor =
13554 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13555 selection.set_head(cursor, SelectionGoal::None);
13556 }
13557 });
13558 });
13559 this.insert("", window, cx);
13560 });
13561 }
13562
13563 pub fn move_to_next_word_end(
13564 &mut self,
13565 _: &MoveToNextWordEnd,
13566 window: &mut Window,
13567 cx: &mut Context<Self>,
13568 ) {
13569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13570 self.change_selections(Default::default(), window, cx, |s| {
13571 s.move_cursors_with(|map, head, _| {
13572 (movement::next_word_end(map, head), SelectionGoal::None)
13573 });
13574 })
13575 }
13576
13577 pub fn move_to_next_subword_end(
13578 &mut self,
13579 _: &MoveToNextSubwordEnd,
13580 window: &mut Window,
13581 cx: &mut Context<Self>,
13582 ) {
13583 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13584 self.change_selections(Default::default(), window, cx, |s| {
13585 s.move_cursors_with(|map, head, _| {
13586 (movement::next_subword_end(map, head), SelectionGoal::None)
13587 });
13588 })
13589 }
13590
13591 pub fn select_to_next_word_end(
13592 &mut self,
13593 _: &SelectToNextWordEnd,
13594 window: &mut Window,
13595 cx: &mut Context<Self>,
13596 ) {
13597 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13598 self.change_selections(Default::default(), window, cx, |s| {
13599 s.move_heads_with(|map, head, _| {
13600 (movement::next_word_end(map, head), SelectionGoal::None)
13601 });
13602 })
13603 }
13604
13605 pub fn select_to_next_subword_end(
13606 &mut self,
13607 _: &SelectToNextSubwordEnd,
13608 window: &mut Window,
13609 cx: &mut Context<Self>,
13610 ) {
13611 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13612 self.change_selections(Default::default(), window, cx, |s| {
13613 s.move_heads_with(|map, head, _| {
13614 (movement::next_subword_end(map, head), SelectionGoal::None)
13615 });
13616 })
13617 }
13618
13619 pub fn delete_to_next_word_end(
13620 &mut self,
13621 action: &DeleteToNextWordEnd,
13622 window: &mut Window,
13623 cx: &mut Context<Self>,
13624 ) {
13625 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13626 self.transact(window, cx, |this, window, cx| {
13627 this.change_selections(Default::default(), window, cx, |s| {
13628 s.move_with(|map, selection| {
13629 if selection.is_empty() {
13630 let mut cursor = if action.ignore_newlines {
13631 movement::next_word_end(map, selection.head())
13632 } else {
13633 movement::next_word_end_or_newline(map, selection.head())
13634 };
13635 cursor = movement::adjust_greedy_deletion(
13636 map,
13637 selection.head(),
13638 cursor,
13639 action.ignore_brackets,
13640 );
13641 selection.set_head(cursor, SelectionGoal::None);
13642 }
13643 });
13644 });
13645 this.insert("", window, cx);
13646 });
13647 }
13648
13649 pub fn delete_to_next_subword_end(
13650 &mut self,
13651 _: &DeleteToNextSubwordEnd,
13652 window: &mut Window,
13653 cx: &mut Context<Self>,
13654 ) {
13655 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13656 self.transact(window, cx, |this, window, cx| {
13657 this.change_selections(Default::default(), window, cx, |s| {
13658 s.move_with(|map, selection| {
13659 if selection.is_empty() {
13660 let mut cursor = movement::next_subword_end(map, selection.head());
13661 cursor =
13662 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13663 selection.set_head(cursor, SelectionGoal::None);
13664 }
13665 });
13666 });
13667 this.insert("", window, cx);
13668 });
13669 }
13670
13671 pub fn move_to_beginning_of_line(
13672 &mut self,
13673 action: &MoveToBeginningOfLine,
13674 window: &mut Window,
13675 cx: &mut Context<Self>,
13676 ) {
13677 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13678 self.change_selections(Default::default(), window, cx, |s| {
13679 s.move_cursors_with(|map, head, _| {
13680 (
13681 movement::indented_line_beginning(
13682 map,
13683 head,
13684 action.stop_at_soft_wraps,
13685 action.stop_at_indent,
13686 ),
13687 SelectionGoal::None,
13688 )
13689 });
13690 })
13691 }
13692
13693 pub fn select_to_beginning_of_line(
13694 &mut self,
13695 action: &SelectToBeginningOfLine,
13696 window: &mut Window,
13697 cx: &mut Context<Self>,
13698 ) {
13699 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13700 self.change_selections(Default::default(), window, cx, |s| {
13701 s.move_heads_with(|map, head, _| {
13702 (
13703 movement::indented_line_beginning(
13704 map,
13705 head,
13706 action.stop_at_soft_wraps,
13707 action.stop_at_indent,
13708 ),
13709 SelectionGoal::None,
13710 )
13711 });
13712 });
13713 }
13714
13715 pub fn delete_to_beginning_of_line(
13716 &mut self,
13717 action: &DeleteToBeginningOfLine,
13718 window: &mut Window,
13719 cx: &mut Context<Self>,
13720 ) {
13721 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13722 self.transact(window, cx, |this, window, cx| {
13723 this.change_selections(Default::default(), window, cx, |s| {
13724 s.move_with(|_, selection| {
13725 selection.reversed = true;
13726 });
13727 });
13728
13729 this.select_to_beginning_of_line(
13730 &SelectToBeginningOfLine {
13731 stop_at_soft_wraps: false,
13732 stop_at_indent: action.stop_at_indent,
13733 },
13734 window,
13735 cx,
13736 );
13737 this.backspace(&Backspace, window, cx);
13738 });
13739 }
13740
13741 pub fn move_to_end_of_line(
13742 &mut self,
13743 action: &MoveToEndOfLine,
13744 window: &mut Window,
13745 cx: &mut Context<Self>,
13746 ) {
13747 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13748 self.change_selections(Default::default(), window, cx, |s| {
13749 s.move_cursors_with(|map, head, _| {
13750 (
13751 movement::line_end(map, head, action.stop_at_soft_wraps),
13752 SelectionGoal::None,
13753 )
13754 });
13755 })
13756 }
13757
13758 pub fn select_to_end_of_line(
13759 &mut self,
13760 action: &SelectToEndOfLine,
13761 window: &mut Window,
13762 cx: &mut Context<Self>,
13763 ) {
13764 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13765 self.change_selections(Default::default(), window, cx, |s| {
13766 s.move_heads_with(|map, head, _| {
13767 (
13768 movement::line_end(map, head, action.stop_at_soft_wraps),
13769 SelectionGoal::None,
13770 )
13771 });
13772 })
13773 }
13774
13775 pub fn delete_to_end_of_line(
13776 &mut self,
13777 _: &DeleteToEndOfLine,
13778 window: &mut Window,
13779 cx: &mut Context<Self>,
13780 ) {
13781 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13782 self.transact(window, cx, |this, window, cx| {
13783 this.select_to_end_of_line(
13784 &SelectToEndOfLine {
13785 stop_at_soft_wraps: false,
13786 },
13787 window,
13788 cx,
13789 );
13790 this.delete(&Delete, window, cx);
13791 });
13792 }
13793
13794 pub fn cut_to_end_of_line(
13795 &mut self,
13796 action: &CutToEndOfLine,
13797 window: &mut Window,
13798 cx: &mut Context<Self>,
13799 ) {
13800 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13801 self.transact(window, cx, |this, window, cx| {
13802 this.select_to_end_of_line(
13803 &SelectToEndOfLine {
13804 stop_at_soft_wraps: false,
13805 },
13806 window,
13807 cx,
13808 );
13809 if !action.stop_at_newlines {
13810 this.change_selections(Default::default(), window, cx, |s| {
13811 s.move_with(|_, sel| {
13812 if sel.is_empty() {
13813 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13814 }
13815 });
13816 });
13817 }
13818 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13819 let item = this.cut_common(false, window, cx);
13820 cx.write_to_clipboard(item);
13821 });
13822 }
13823
13824 pub fn move_to_start_of_paragraph(
13825 &mut self,
13826 _: &MoveToStartOfParagraph,
13827 window: &mut Window,
13828 cx: &mut Context<Self>,
13829 ) {
13830 if matches!(self.mode, EditorMode::SingleLine) {
13831 cx.propagate();
13832 return;
13833 }
13834 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13835 self.change_selections(Default::default(), window, cx, |s| {
13836 s.move_with(|map, selection| {
13837 selection.collapse_to(
13838 movement::start_of_paragraph(map, selection.head(), 1),
13839 SelectionGoal::None,
13840 )
13841 });
13842 })
13843 }
13844
13845 pub fn move_to_end_of_paragraph(
13846 &mut self,
13847 _: &MoveToEndOfParagraph,
13848 window: &mut Window,
13849 cx: &mut Context<Self>,
13850 ) {
13851 if matches!(self.mode, EditorMode::SingleLine) {
13852 cx.propagate();
13853 return;
13854 }
13855 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13856 self.change_selections(Default::default(), window, cx, |s| {
13857 s.move_with(|map, selection| {
13858 selection.collapse_to(
13859 movement::end_of_paragraph(map, selection.head(), 1),
13860 SelectionGoal::None,
13861 )
13862 });
13863 })
13864 }
13865
13866 pub fn select_to_start_of_paragraph(
13867 &mut self,
13868 _: &SelectToStartOfParagraph,
13869 window: &mut Window,
13870 cx: &mut Context<Self>,
13871 ) {
13872 if matches!(self.mode, EditorMode::SingleLine) {
13873 cx.propagate();
13874 return;
13875 }
13876 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13877 self.change_selections(Default::default(), window, cx, |s| {
13878 s.move_heads_with(|map, head, _| {
13879 (
13880 movement::start_of_paragraph(map, head, 1),
13881 SelectionGoal::None,
13882 )
13883 });
13884 })
13885 }
13886
13887 pub fn select_to_end_of_paragraph(
13888 &mut self,
13889 _: &SelectToEndOfParagraph,
13890 window: &mut Window,
13891 cx: &mut Context<Self>,
13892 ) {
13893 if matches!(self.mode, EditorMode::SingleLine) {
13894 cx.propagate();
13895 return;
13896 }
13897 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13898 self.change_selections(Default::default(), window, cx, |s| {
13899 s.move_heads_with(|map, head, _| {
13900 (
13901 movement::end_of_paragraph(map, head, 1),
13902 SelectionGoal::None,
13903 )
13904 });
13905 })
13906 }
13907
13908 pub fn move_to_start_of_excerpt(
13909 &mut self,
13910 _: &MoveToStartOfExcerpt,
13911 window: &mut Window,
13912 cx: &mut Context<Self>,
13913 ) {
13914 if matches!(self.mode, EditorMode::SingleLine) {
13915 cx.propagate();
13916 return;
13917 }
13918 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13919 self.change_selections(Default::default(), window, cx, |s| {
13920 s.move_with(|map, selection| {
13921 selection.collapse_to(
13922 movement::start_of_excerpt(
13923 map,
13924 selection.head(),
13925 workspace::searchable::Direction::Prev,
13926 ),
13927 SelectionGoal::None,
13928 )
13929 });
13930 })
13931 }
13932
13933 pub fn move_to_start_of_next_excerpt(
13934 &mut self,
13935 _: &MoveToStartOfNextExcerpt,
13936 window: &mut Window,
13937 cx: &mut Context<Self>,
13938 ) {
13939 if matches!(self.mode, EditorMode::SingleLine) {
13940 cx.propagate();
13941 return;
13942 }
13943
13944 self.change_selections(Default::default(), window, cx, |s| {
13945 s.move_with(|map, selection| {
13946 selection.collapse_to(
13947 movement::start_of_excerpt(
13948 map,
13949 selection.head(),
13950 workspace::searchable::Direction::Next,
13951 ),
13952 SelectionGoal::None,
13953 )
13954 });
13955 })
13956 }
13957
13958 pub fn move_to_end_of_excerpt(
13959 &mut self,
13960 _: &MoveToEndOfExcerpt,
13961 window: &mut Window,
13962 cx: &mut Context<Self>,
13963 ) {
13964 if matches!(self.mode, EditorMode::SingleLine) {
13965 cx.propagate();
13966 return;
13967 }
13968 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13969 self.change_selections(Default::default(), window, cx, |s| {
13970 s.move_with(|map, selection| {
13971 selection.collapse_to(
13972 movement::end_of_excerpt(
13973 map,
13974 selection.head(),
13975 workspace::searchable::Direction::Next,
13976 ),
13977 SelectionGoal::None,
13978 )
13979 });
13980 })
13981 }
13982
13983 pub fn move_to_end_of_previous_excerpt(
13984 &mut self,
13985 _: &MoveToEndOfPreviousExcerpt,
13986 window: &mut Window,
13987 cx: &mut Context<Self>,
13988 ) {
13989 if matches!(self.mode, EditorMode::SingleLine) {
13990 cx.propagate();
13991 return;
13992 }
13993 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13994 self.change_selections(Default::default(), window, cx, |s| {
13995 s.move_with(|map, selection| {
13996 selection.collapse_to(
13997 movement::end_of_excerpt(
13998 map,
13999 selection.head(),
14000 workspace::searchable::Direction::Prev,
14001 ),
14002 SelectionGoal::None,
14003 )
14004 });
14005 })
14006 }
14007
14008 pub fn select_to_start_of_excerpt(
14009 &mut self,
14010 _: &SelectToStartOfExcerpt,
14011 window: &mut Window,
14012 cx: &mut Context<Self>,
14013 ) {
14014 if matches!(self.mode, EditorMode::SingleLine) {
14015 cx.propagate();
14016 return;
14017 }
14018 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14019 self.change_selections(Default::default(), window, cx, |s| {
14020 s.move_heads_with(|map, head, _| {
14021 (
14022 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14023 SelectionGoal::None,
14024 )
14025 });
14026 })
14027 }
14028
14029 pub fn select_to_start_of_next_excerpt(
14030 &mut self,
14031 _: &SelectToStartOfNextExcerpt,
14032 window: &mut Window,
14033 cx: &mut Context<Self>,
14034 ) {
14035 if matches!(self.mode, EditorMode::SingleLine) {
14036 cx.propagate();
14037 return;
14038 }
14039 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14040 self.change_selections(Default::default(), window, cx, |s| {
14041 s.move_heads_with(|map, head, _| {
14042 (
14043 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14044 SelectionGoal::None,
14045 )
14046 });
14047 })
14048 }
14049
14050 pub fn select_to_end_of_excerpt(
14051 &mut self,
14052 _: &SelectToEndOfExcerpt,
14053 window: &mut Window,
14054 cx: &mut Context<Self>,
14055 ) {
14056 if matches!(self.mode, EditorMode::SingleLine) {
14057 cx.propagate();
14058 return;
14059 }
14060 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14061 self.change_selections(Default::default(), window, cx, |s| {
14062 s.move_heads_with(|map, head, _| {
14063 (
14064 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14065 SelectionGoal::None,
14066 )
14067 });
14068 })
14069 }
14070
14071 pub fn select_to_end_of_previous_excerpt(
14072 &mut self,
14073 _: &SelectToEndOfPreviousExcerpt,
14074 window: &mut Window,
14075 cx: &mut Context<Self>,
14076 ) {
14077 if matches!(self.mode, EditorMode::SingleLine) {
14078 cx.propagate();
14079 return;
14080 }
14081 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14082 self.change_selections(Default::default(), window, cx, |s| {
14083 s.move_heads_with(|map, head, _| {
14084 (
14085 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14086 SelectionGoal::None,
14087 )
14088 });
14089 })
14090 }
14091
14092 pub fn move_to_beginning(
14093 &mut self,
14094 _: &MoveToBeginning,
14095 window: &mut Window,
14096 cx: &mut Context<Self>,
14097 ) {
14098 if matches!(self.mode, EditorMode::SingleLine) {
14099 cx.propagate();
14100 return;
14101 }
14102 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14103 self.change_selections(Default::default(), window, cx, |s| {
14104 s.select_ranges(vec![0..0]);
14105 });
14106 }
14107
14108 pub fn select_to_beginning(
14109 &mut self,
14110 _: &SelectToBeginning,
14111 window: &mut Window,
14112 cx: &mut Context<Self>,
14113 ) {
14114 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14115 selection.set_head(Point::zero(), SelectionGoal::None);
14116 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14117 self.change_selections(Default::default(), window, cx, |s| {
14118 s.select(vec![selection]);
14119 });
14120 }
14121
14122 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14123 if matches!(self.mode, EditorMode::SingleLine) {
14124 cx.propagate();
14125 return;
14126 }
14127 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14128 let cursor = self.buffer.read(cx).read(cx).len();
14129 self.change_selections(Default::default(), window, cx, |s| {
14130 s.select_ranges(vec![cursor..cursor])
14131 });
14132 }
14133
14134 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14135 self.nav_history = nav_history;
14136 }
14137
14138 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14139 self.nav_history.as_ref()
14140 }
14141
14142 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14143 self.push_to_nav_history(
14144 self.selections.newest_anchor().head(),
14145 None,
14146 false,
14147 true,
14148 cx,
14149 );
14150 }
14151
14152 fn push_to_nav_history(
14153 &mut self,
14154 cursor_anchor: Anchor,
14155 new_position: Option<Point>,
14156 is_deactivate: bool,
14157 always: bool,
14158 cx: &mut Context<Self>,
14159 ) {
14160 if let Some(nav_history) = self.nav_history.as_mut() {
14161 let buffer = self.buffer.read(cx).read(cx);
14162 let cursor_position = cursor_anchor.to_point(&buffer);
14163 let scroll_state = self.scroll_manager.anchor();
14164 let scroll_top_row = scroll_state.top_row(&buffer);
14165 drop(buffer);
14166
14167 if let Some(new_position) = new_position {
14168 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14169 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14170 return;
14171 }
14172 }
14173
14174 nav_history.push(
14175 Some(NavigationData {
14176 cursor_anchor,
14177 cursor_position,
14178 scroll_anchor: scroll_state,
14179 scroll_top_row,
14180 }),
14181 cx,
14182 );
14183 cx.emit(EditorEvent::PushedToNavHistory {
14184 anchor: cursor_anchor,
14185 is_deactivate,
14186 })
14187 }
14188 }
14189
14190 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14191 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14192 let buffer = self.buffer.read(cx).snapshot(cx);
14193 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14194 selection.set_head(buffer.len(), SelectionGoal::None);
14195 self.change_selections(Default::default(), window, cx, |s| {
14196 s.select(vec![selection]);
14197 });
14198 }
14199
14200 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14201 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14202 let end = self.buffer.read(cx).read(cx).len();
14203 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14204 s.select_ranges(vec![0..end]);
14205 });
14206 }
14207
14208 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14209 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14210 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14211 let mut selections = self.selections.all::<Point>(&display_map);
14212 let max_point = display_map.buffer_snapshot().max_point();
14213 for selection in &mut selections {
14214 let rows = selection.spanned_rows(true, &display_map);
14215 selection.start = Point::new(rows.start.0, 0);
14216 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14217 selection.reversed = false;
14218 }
14219 self.change_selections(Default::default(), window, cx, |s| {
14220 s.select(selections);
14221 });
14222 }
14223
14224 pub fn split_selection_into_lines(
14225 &mut self,
14226 action: &SplitSelectionIntoLines,
14227 window: &mut Window,
14228 cx: &mut Context<Self>,
14229 ) {
14230 let selections = self
14231 .selections
14232 .all::<Point>(&self.display_snapshot(cx))
14233 .into_iter()
14234 .map(|selection| selection.start..selection.end)
14235 .collect::<Vec<_>>();
14236 self.unfold_ranges(&selections, true, true, cx);
14237
14238 let mut new_selection_ranges = Vec::new();
14239 {
14240 let buffer = self.buffer.read(cx).read(cx);
14241 for selection in selections {
14242 for row in selection.start.row..selection.end.row {
14243 let line_start = Point::new(row, 0);
14244 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14245
14246 if action.keep_selections {
14247 // Keep the selection range for each line
14248 let selection_start = if row == selection.start.row {
14249 selection.start
14250 } else {
14251 line_start
14252 };
14253 new_selection_ranges.push(selection_start..line_end);
14254 } else {
14255 // Collapse to cursor at end of line
14256 new_selection_ranges.push(line_end..line_end);
14257 }
14258 }
14259
14260 let is_multiline_selection = selection.start.row != selection.end.row;
14261 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14262 // so this action feels more ergonomic when paired with other selection operations
14263 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14264 if !should_skip_last {
14265 if action.keep_selections {
14266 if is_multiline_selection {
14267 let line_start = Point::new(selection.end.row, 0);
14268 new_selection_ranges.push(line_start..selection.end);
14269 } else {
14270 new_selection_ranges.push(selection.start..selection.end);
14271 }
14272 } else {
14273 new_selection_ranges.push(selection.end..selection.end);
14274 }
14275 }
14276 }
14277 }
14278 self.change_selections(Default::default(), window, cx, |s| {
14279 s.select_ranges(new_selection_ranges);
14280 });
14281 }
14282
14283 pub fn add_selection_above(
14284 &mut self,
14285 action: &AddSelectionAbove,
14286 window: &mut Window,
14287 cx: &mut Context<Self>,
14288 ) {
14289 self.add_selection(true, action.skip_soft_wrap, window, cx);
14290 }
14291
14292 pub fn add_selection_below(
14293 &mut self,
14294 action: &AddSelectionBelow,
14295 window: &mut Window,
14296 cx: &mut Context<Self>,
14297 ) {
14298 self.add_selection(false, action.skip_soft_wrap, window, cx);
14299 }
14300
14301 fn add_selection(
14302 &mut self,
14303 above: bool,
14304 skip_soft_wrap: bool,
14305 window: &mut Window,
14306 cx: &mut Context<Self>,
14307 ) {
14308 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14309
14310 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14311 let all_selections = self.selections.all::<Point>(&display_map);
14312 let text_layout_details = self.text_layout_details(window);
14313
14314 let (mut columnar_selections, new_selections_to_columnarize) = {
14315 if let Some(state) = self.add_selections_state.as_ref() {
14316 let columnar_selection_ids: HashSet<_> = state
14317 .groups
14318 .iter()
14319 .flat_map(|group| group.stack.iter())
14320 .copied()
14321 .collect();
14322
14323 all_selections
14324 .into_iter()
14325 .partition(|s| columnar_selection_ids.contains(&s.id))
14326 } else {
14327 (Vec::new(), all_selections)
14328 }
14329 };
14330
14331 let mut state = self
14332 .add_selections_state
14333 .take()
14334 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14335
14336 for selection in new_selections_to_columnarize {
14337 let range = selection.display_range(&display_map).sorted();
14338 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14339 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14340 let positions = start_x.min(end_x)..start_x.max(end_x);
14341 let mut stack = Vec::new();
14342 for row in range.start.row().0..=range.end.row().0 {
14343 if let Some(selection) = self.selections.build_columnar_selection(
14344 &display_map,
14345 DisplayRow(row),
14346 &positions,
14347 selection.reversed,
14348 &text_layout_details,
14349 ) {
14350 stack.push(selection.id);
14351 columnar_selections.push(selection);
14352 }
14353 }
14354 if !stack.is_empty() {
14355 if above {
14356 stack.reverse();
14357 }
14358 state.groups.push(AddSelectionsGroup { above, stack });
14359 }
14360 }
14361
14362 let mut final_selections = Vec::new();
14363 let end_row = if above {
14364 DisplayRow(0)
14365 } else {
14366 display_map.max_point().row()
14367 };
14368
14369 let mut last_added_item_per_group = HashMap::default();
14370 for group in state.groups.iter_mut() {
14371 if let Some(last_id) = group.stack.last() {
14372 last_added_item_per_group.insert(*last_id, group);
14373 }
14374 }
14375
14376 for selection in columnar_selections {
14377 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14378 if above == group.above {
14379 let range = selection.display_range(&display_map).sorted();
14380 debug_assert_eq!(range.start.row(), range.end.row());
14381 let mut row = range.start.row();
14382 let positions =
14383 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14384 Pixels::from(start)..Pixels::from(end)
14385 } else {
14386 let start_x =
14387 display_map.x_for_display_point(range.start, &text_layout_details);
14388 let end_x =
14389 display_map.x_for_display_point(range.end, &text_layout_details);
14390 start_x.min(end_x)..start_x.max(end_x)
14391 };
14392
14393 let mut maybe_new_selection = None;
14394 let direction = if above { -1 } else { 1 };
14395
14396 while row != end_row {
14397 if skip_soft_wrap {
14398 row = display_map
14399 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14400 .row();
14401 } else if above {
14402 row.0 -= 1;
14403 } else {
14404 row.0 += 1;
14405 }
14406
14407 if let Some(new_selection) = self.selections.build_columnar_selection(
14408 &display_map,
14409 row,
14410 &positions,
14411 selection.reversed,
14412 &text_layout_details,
14413 ) {
14414 maybe_new_selection = Some(new_selection);
14415 break;
14416 }
14417 }
14418
14419 if let Some(new_selection) = maybe_new_selection {
14420 group.stack.push(new_selection.id);
14421 if above {
14422 final_selections.push(new_selection);
14423 final_selections.push(selection);
14424 } else {
14425 final_selections.push(selection);
14426 final_selections.push(new_selection);
14427 }
14428 } else {
14429 final_selections.push(selection);
14430 }
14431 } else {
14432 group.stack.pop();
14433 }
14434 } else {
14435 final_selections.push(selection);
14436 }
14437 }
14438
14439 self.change_selections(Default::default(), window, cx, |s| {
14440 s.select(final_selections);
14441 });
14442
14443 let final_selection_ids: HashSet<_> = self
14444 .selections
14445 .all::<Point>(&display_map)
14446 .iter()
14447 .map(|s| s.id)
14448 .collect();
14449 state.groups.retain_mut(|group| {
14450 // selections might get merged above so we remove invalid items from stacks
14451 group.stack.retain(|id| final_selection_ids.contains(id));
14452
14453 // single selection in stack can be treated as initial state
14454 group.stack.len() > 1
14455 });
14456
14457 if !state.groups.is_empty() {
14458 self.add_selections_state = Some(state);
14459 }
14460 }
14461
14462 fn select_match_ranges(
14463 &mut self,
14464 range: Range<usize>,
14465 reversed: bool,
14466 replace_newest: bool,
14467 auto_scroll: Option<Autoscroll>,
14468 window: &mut Window,
14469 cx: &mut Context<Editor>,
14470 ) {
14471 self.unfold_ranges(
14472 std::slice::from_ref(&range),
14473 false,
14474 auto_scroll.is_some(),
14475 cx,
14476 );
14477 let effects = if let Some(scroll) = auto_scroll {
14478 SelectionEffects::scroll(scroll)
14479 } else {
14480 SelectionEffects::no_scroll()
14481 };
14482 self.change_selections(effects, window, cx, |s| {
14483 if replace_newest {
14484 s.delete(s.newest_anchor().id);
14485 }
14486 if reversed {
14487 s.insert_range(range.end..range.start);
14488 } else {
14489 s.insert_range(range);
14490 }
14491 });
14492 }
14493
14494 pub fn select_next_match_internal(
14495 &mut self,
14496 display_map: &DisplaySnapshot,
14497 replace_newest: bool,
14498 autoscroll: Option<Autoscroll>,
14499 window: &mut Window,
14500 cx: &mut Context<Self>,
14501 ) -> Result<()> {
14502 let buffer = display_map.buffer_snapshot();
14503 let mut selections = self.selections.all::<usize>(&display_map);
14504 if let Some(mut select_next_state) = self.select_next_state.take() {
14505 let query = &select_next_state.query;
14506 if !select_next_state.done {
14507 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14508 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14509 let mut next_selected_range = None;
14510
14511 // Collect and sort selection ranges for efficient overlap checking
14512 let mut selection_ranges: Vec<_> = selections.iter().map(|s| s.range()).collect();
14513 selection_ranges.sort_by_key(|r| r.start);
14514
14515 let bytes_after_last_selection =
14516 buffer.bytes_in_range(last_selection.end..buffer.len());
14517 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14518 let query_matches = query
14519 .stream_find_iter(bytes_after_last_selection)
14520 .map(|result| (last_selection.end, result))
14521 .chain(
14522 query
14523 .stream_find_iter(bytes_before_first_selection)
14524 .map(|result| (0, result)),
14525 );
14526
14527 for (start_offset, query_match) in query_matches {
14528 let query_match = query_match.unwrap(); // can only fail due to I/O
14529 let offset_range =
14530 start_offset + query_match.start()..start_offset + query_match.end();
14531
14532 if !select_next_state.wordwise
14533 || (!buffer.is_inside_word(offset_range.start, None)
14534 && !buffer.is_inside_word(offset_range.end, None))
14535 {
14536 // Use binary search to check for overlap (O(log n))
14537 let overlaps = selection_ranges
14538 .binary_search_by(|range| {
14539 if range.end <= offset_range.start {
14540 std::cmp::Ordering::Less
14541 } else if range.start >= offset_range.end {
14542 std::cmp::Ordering::Greater
14543 } else {
14544 std::cmp::Ordering::Equal
14545 }
14546 })
14547 .is_ok();
14548
14549 if !overlaps {
14550 next_selected_range = Some(offset_range);
14551 break;
14552 }
14553 }
14554 }
14555
14556 if let Some(next_selected_range) = next_selected_range {
14557 self.select_match_ranges(
14558 next_selected_range,
14559 last_selection.reversed,
14560 replace_newest,
14561 autoscroll,
14562 window,
14563 cx,
14564 );
14565 } else {
14566 select_next_state.done = true;
14567 }
14568 }
14569
14570 self.select_next_state = Some(select_next_state);
14571 } else {
14572 let mut only_carets = true;
14573 let mut same_text_selected = true;
14574 let mut selected_text = None;
14575
14576 let mut selections_iter = selections.iter().peekable();
14577 while let Some(selection) = selections_iter.next() {
14578 if selection.start != selection.end {
14579 only_carets = false;
14580 }
14581
14582 if same_text_selected {
14583 if selected_text.is_none() {
14584 selected_text =
14585 Some(buffer.text_for_range(selection.range()).collect::<String>());
14586 }
14587
14588 if let Some(next_selection) = selections_iter.peek() {
14589 if next_selection.range().len() == selection.range().len() {
14590 let next_selected_text = buffer
14591 .text_for_range(next_selection.range())
14592 .collect::<String>();
14593 if Some(next_selected_text) != selected_text {
14594 same_text_selected = false;
14595 selected_text = None;
14596 }
14597 } else {
14598 same_text_selected = false;
14599 selected_text = None;
14600 }
14601 }
14602 }
14603 }
14604
14605 if only_carets {
14606 for selection in &mut selections {
14607 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14608 selection.start = word_range.start;
14609 selection.end = word_range.end;
14610 selection.goal = SelectionGoal::None;
14611 selection.reversed = false;
14612 self.select_match_ranges(
14613 selection.start..selection.end,
14614 selection.reversed,
14615 replace_newest,
14616 autoscroll,
14617 window,
14618 cx,
14619 );
14620 }
14621
14622 if selections.len() == 1 {
14623 let selection = selections
14624 .last()
14625 .expect("ensured that there's only one selection");
14626 let query = buffer
14627 .text_for_range(selection.start..selection.end)
14628 .collect::<String>();
14629 let is_empty = query.is_empty();
14630 let select_state = SelectNextState {
14631 query: AhoCorasick::new(&[query])?,
14632 wordwise: true,
14633 done: is_empty,
14634 };
14635 self.select_next_state = Some(select_state);
14636 } else {
14637 self.select_next_state = None;
14638 }
14639 } else if let Some(selected_text) = selected_text {
14640 self.select_next_state = Some(SelectNextState {
14641 query: AhoCorasick::new(&[selected_text])?,
14642 wordwise: false,
14643 done: false,
14644 });
14645 self.select_next_match_internal(
14646 display_map,
14647 replace_newest,
14648 autoscroll,
14649 window,
14650 cx,
14651 )?;
14652 }
14653 }
14654 Ok(())
14655 }
14656
14657 pub fn select_all_matches(
14658 &mut self,
14659 _action: &SelectAllMatches,
14660 window: &mut Window,
14661 cx: &mut Context<Self>,
14662 ) -> Result<()> {
14663 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14664
14665 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14666
14667 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14668 let Some(select_next_state) = self.select_next_state.as_mut() else {
14669 return Ok(());
14670 };
14671 if select_next_state.done {
14672 return Ok(());
14673 }
14674
14675 let mut new_selections = Vec::new();
14676
14677 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14678 let buffer = display_map.buffer_snapshot();
14679 let query_matches = select_next_state
14680 .query
14681 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14682
14683 for query_match in query_matches.into_iter() {
14684 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14685 let offset_range = if reversed {
14686 query_match.end()..query_match.start()
14687 } else {
14688 query_match.start()..query_match.end()
14689 };
14690
14691 if !select_next_state.wordwise
14692 || (!buffer.is_inside_word(offset_range.start, None)
14693 && !buffer.is_inside_word(offset_range.end, None))
14694 {
14695 new_selections.push(offset_range.start..offset_range.end);
14696 }
14697 }
14698
14699 select_next_state.done = true;
14700
14701 if new_selections.is_empty() {
14702 log::error!("bug: new_selections is empty in select_all_matches");
14703 return Ok(());
14704 }
14705
14706 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14707 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14708 selections.select_ranges(new_selections)
14709 });
14710
14711 Ok(())
14712 }
14713
14714 pub fn select_next(
14715 &mut self,
14716 action: &SelectNext,
14717 window: &mut Window,
14718 cx: &mut Context<Self>,
14719 ) -> Result<()> {
14720 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14721 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14722 self.select_next_match_internal(
14723 &display_map,
14724 action.replace_newest,
14725 Some(Autoscroll::newest()),
14726 window,
14727 cx,
14728 )?;
14729 Ok(())
14730 }
14731
14732 pub fn select_previous(
14733 &mut self,
14734 action: &SelectPrevious,
14735 window: &mut Window,
14736 cx: &mut Context<Self>,
14737 ) -> Result<()> {
14738 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14739 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14740 let buffer = display_map.buffer_snapshot();
14741 let mut selections = self.selections.all::<usize>(&display_map);
14742 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14743 let query = &select_prev_state.query;
14744 if !select_prev_state.done {
14745 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14746 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14747 let mut next_selected_range = None;
14748 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14749 let bytes_before_last_selection =
14750 buffer.reversed_bytes_in_range(0..last_selection.start);
14751 let bytes_after_first_selection =
14752 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14753 let query_matches = query
14754 .stream_find_iter(bytes_before_last_selection)
14755 .map(|result| (last_selection.start, result))
14756 .chain(
14757 query
14758 .stream_find_iter(bytes_after_first_selection)
14759 .map(|result| (buffer.len(), result)),
14760 );
14761 for (end_offset, query_match) in query_matches {
14762 let query_match = query_match.unwrap(); // can only fail due to I/O
14763 let offset_range =
14764 end_offset - query_match.end()..end_offset - query_match.start();
14765
14766 if !select_prev_state.wordwise
14767 || (!buffer.is_inside_word(offset_range.start, None)
14768 && !buffer.is_inside_word(offset_range.end, None))
14769 {
14770 next_selected_range = Some(offset_range);
14771 break;
14772 }
14773 }
14774
14775 if let Some(next_selected_range) = next_selected_range {
14776 self.select_match_ranges(
14777 next_selected_range,
14778 last_selection.reversed,
14779 action.replace_newest,
14780 Some(Autoscroll::newest()),
14781 window,
14782 cx,
14783 );
14784 } else {
14785 select_prev_state.done = true;
14786 }
14787 }
14788
14789 self.select_prev_state = Some(select_prev_state);
14790 } else {
14791 let mut only_carets = true;
14792 let mut same_text_selected = true;
14793 let mut selected_text = None;
14794
14795 let mut selections_iter = selections.iter().peekable();
14796 while let Some(selection) = selections_iter.next() {
14797 if selection.start != selection.end {
14798 only_carets = false;
14799 }
14800
14801 if same_text_selected {
14802 if selected_text.is_none() {
14803 selected_text =
14804 Some(buffer.text_for_range(selection.range()).collect::<String>());
14805 }
14806
14807 if let Some(next_selection) = selections_iter.peek() {
14808 if next_selection.range().len() == selection.range().len() {
14809 let next_selected_text = buffer
14810 .text_for_range(next_selection.range())
14811 .collect::<String>();
14812 if Some(next_selected_text) != selected_text {
14813 same_text_selected = false;
14814 selected_text = None;
14815 }
14816 } else {
14817 same_text_selected = false;
14818 selected_text = None;
14819 }
14820 }
14821 }
14822 }
14823
14824 if only_carets {
14825 for selection in &mut selections {
14826 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14827 selection.start = word_range.start;
14828 selection.end = word_range.end;
14829 selection.goal = SelectionGoal::None;
14830 selection.reversed = false;
14831 self.select_match_ranges(
14832 selection.start..selection.end,
14833 selection.reversed,
14834 action.replace_newest,
14835 Some(Autoscroll::newest()),
14836 window,
14837 cx,
14838 );
14839 }
14840 if selections.len() == 1 {
14841 let selection = selections
14842 .last()
14843 .expect("ensured that there's only one selection");
14844 let query = buffer
14845 .text_for_range(selection.start..selection.end)
14846 .collect::<String>();
14847 let is_empty = query.is_empty();
14848 let select_state = SelectNextState {
14849 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14850 wordwise: true,
14851 done: is_empty,
14852 };
14853 self.select_prev_state = Some(select_state);
14854 } else {
14855 self.select_prev_state = None;
14856 }
14857 } else if let Some(selected_text) = selected_text {
14858 self.select_prev_state = Some(SelectNextState {
14859 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14860 wordwise: false,
14861 done: false,
14862 });
14863 self.select_previous(action, window, cx)?;
14864 }
14865 }
14866 Ok(())
14867 }
14868
14869 pub fn find_next_match(
14870 &mut self,
14871 _: &FindNextMatch,
14872 window: &mut Window,
14873 cx: &mut Context<Self>,
14874 ) -> Result<()> {
14875 let selections = self.selections.disjoint_anchors_arc();
14876 match selections.first() {
14877 Some(first) if selections.len() >= 2 => {
14878 self.change_selections(Default::default(), window, cx, |s| {
14879 s.select_ranges([first.range()]);
14880 });
14881 }
14882 _ => self.select_next(
14883 &SelectNext {
14884 replace_newest: true,
14885 },
14886 window,
14887 cx,
14888 )?,
14889 }
14890 Ok(())
14891 }
14892
14893 pub fn find_previous_match(
14894 &mut self,
14895 _: &FindPreviousMatch,
14896 window: &mut Window,
14897 cx: &mut Context<Self>,
14898 ) -> Result<()> {
14899 let selections = self.selections.disjoint_anchors_arc();
14900 match selections.last() {
14901 Some(last) if selections.len() >= 2 => {
14902 self.change_selections(Default::default(), window, cx, |s| {
14903 s.select_ranges([last.range()]);
14904 });
14905 }
14906 _ => self.select_previous(
14907 &SelectPrevious {
14908 replace_newest: true,
14909 },
14910 window,
14911 cx,
14912 )?,
14913 }
14914 Ok(())
14915 }
14916
14917 pub fn toggle_comments(
14918 &mut self,
14919 action: &ToggleComments,
14920 window: &mut Window,
14921 cx: &mut Context<Self>,
14922 ) {
14923 if self.read_only(cx) {
14924 return;
14925 }
14926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14927 let text_layout_details = &self.text_layout_details(window);
14928 self.transact(window, cx, |this, window, cx| {
14929 let mut selections = this
14930 .selections
14931 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14932 let mut edits = Vec::new();
14933 let mut selection_edit_ranges = Vec::new();
14934 let mut last_toggled_row = None;
14935 let snapshot = this.buffer.read(cx).read(cx);
14936 let empty_str: Arc<str> = Arc::default();
14937 let mut suffixes_inserted = Vec::new();
14938 let ignore_indent = action.ignore_indent;
14939
14940 fn comment_prefix_range(
14941 snapshot: &MultiBufferSnapshot,
14942 row: MultiBufferRow,
14943 comment_prefix: &str,
14944 comment_prefix_whitespace: &str,
14945 ignore_indent: bool,
14946 ) -> Range<Point> {
14947 let indent_size = if ignore_indent {
14948 0
14949 } else {
14950 snapshot.indent_size_for_line(row).len
14951 };
14952
14953 let start = Point::new(row.0, indent_size);
14954
14955 let mut line_bytes = snapshot
14956 .bytes_in_range(start..snapshot.max_point())
14957 .flatten()
14958 .copied();
14959
14960 // If this line currently begins with the line comment prefix, then record
14961 // the range containing the prefix.
14962 if line_bytes
14963 .by_ref()
14964 .take(comment_prefix.len())
14965 .eq(comment_prefix.bytes())
14966 {
14967 // Include any whitespace that matches the comment prefix.
14968 let matching_whitespace_len = line_bytes
14969 .zip(comment_prefix_whitespace.bytes())
14970 .take_while(|(a, b)| a == b)
14971 .count() as u32;
14972 let end = Point::new(
14973 start.row,
14974 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14975 );
14976 start..end
14977 } else {
14978 start..start
14979 }
14980 }
14981
14982 fn comment_suffix_range(
14983 snapshot: &MultiBufferSnapshot,
14984 row: MultiBufferRow,
14985 comment_suffix: &str,
14986 comment_suffix_has_leading_space: bool,
14987 ) -> Range<Point> {
14988 let end = Point::new(row.0, snapshot.line_len(row));
14989 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14990
14991 let mut line_end_bytes = snapshot
14992 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14993 .flatten()
14994 .copied();
14995
14996 let leading_space_len = if suffix_start_column > 0
14997 && line_end_bytes.next() == Some(b' ')
14998 && comment_suffix_has_leading_space
14999 {
15000 1
15001 } else {
15002 0
15003 };
15004
15005 // If this line currently begins with the line comment prefix, then record
15006 // the range containing the prefix.
15007 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15008 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15009 start..end
15010 } else {
15011 end..end
15012 }
15013 }
15014
15015 // TODO: Handle selections that cross excerpts
15016 for selection in &mut selections {
15017 let start_column = snapshot
15018 .indent_size_for_line(MultiBufferRow(selection.start.row))
15019 .len;
15020 let language = if let Some(language) =
15021 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15022 {
15023 language
15024 } else {
15025 continue;
15026 };
15027
15028 selection_edit_ranges.clear();
15029
15030 // If multiple selections contain a given row, avoid processing that
15031 // row more than once.
15032 let mut start_row = MultiBufferRow(selection.start.row);
15033 if last_toggled_row == Some(start_row) {
15034 start_row = start_row.next_row();
15035 }
15036 let end_row =
15037 if selection.end.row > selection.start.row && selection.end.column == 0 {
15038 MultiBufferRow(selection.end.row - 1)
15039 } else {
15040 MultiBufferRow(selection.end.row)
15041 };
15042 last_toggled_row = Some(end_row);
15043
15044 if start_row > end_row {
15045 continue;
15046 }
15047
15048 // If the language has line comments, toggle those.
15049 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15050
15051 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15052 if ignore_indent {
15053 full_comment_prefixes = full_comment_prefixes
15054 .into_iter()
15055 .map(|s| Arc::from(s.trim_end()))
15056 .collect();
15057 }
15058
15059 if !full_comment_prefixes.is_empty() {
15060 let first_prefix = full_comment_prefixes
15061 .first()
15062 .expect("prefixes is non-empty");
15063 let prefix_trimmed_lengths = full_comment_prefixes
15064 .iter()
15065 .map(|p| p.trim_end_matches(' ').len())
15066 .collect::<SmallVec<[usize; 4]>>();
15067
15068 let mut all_selection_lines_are_comments = true;
15069
15070 for row in start_row.0..=end_row.0 {
15071 let row = MultiBufferRow(row);
15072 if start_row < end_row && snapshot.is_line_blank(row) {
15073 continue;
15074 }
15075
15076 let prefix_range = full_comment_prefixes
15077 .iter()
15078 .zip(prefix_trimmed_lengths.iter().copied())
15079 .map(|(prefix, trimmed_prefix_len)| {
15080 comment_prefix_range(
15081 snapshot.deref(),
15082 row,
15083 &prefix[..trimmed_prefix_len],
15084 &prefix[trimmed_prefix_len..],
15085 ignore_indent,
15086 )
15087 })
15088 .max_by_key(|range| range.end.column - range.start.column)
15089 .expect("prefixes is non-empty");
15090
15091 if prefix_range.is_empty() {
15092 all_selection_lines_are_comments = false;
15093 }
15094
15095 selection_edit_ranges.push(prefix_range);
15096 }
15097
15098 if all_selection_lines_are_comments {
15099 edits.extend(
15100 selection_edit_ranges
15101 .iter()
15102 .cloned()
15103 .map(|range| (range, empty_str.clone())),
15104 );
15105 } else {
15106 let min_column = selection_edit_ranges
15107 .iter()
15108 .map(|range| range.start.column)
15109 .min()
15110 .unwrap_or(0);
15111 edits.extend(selection_edit_ranges.iter().map(|range| {
15112 let position = Point::new(range.start.row, min_column);
15113 (position..position, first_prefix.clone())
15114 }));
15115 }
15116 } else if let Some(BlockCommentConfig {
15117 start: full_comment_prefix,
15118 end: comment_suffix,
15119 ..
15120 }) = language.block_comment()
15121 {
15122 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15123 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15124 let prefix_range = comment_prefix_range(
15125 snapshot.deref(),
15126 start_row,
15127 comment_prefix,
15128 comment_prefix_whitespace,
15129 ignore_indent,
15130 );
15131 let suffix_range = comment_suffix_range(
15132 snapshot.deref(),
15133 end_row,
15134 comment_suffix.trim_start_matches(' '),
15135 comment_suffix.starts_with(' '),
15136 );
15137
15138 if prefix_range.is_empty() || suffix_range.is_empty() {
15139 edits.push((
15140 prefix_range.start..prefix_range.start,
15141 full_comment_prefix.clone(),
15142 ));
15143 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15144 suffixes_inserted.push((end_row, comment_suffix.len()));
15145 } else {
15146 edits.push((prefix_range, empty_str.clone()));
15147 edits.push((suffix_range, empty_str.clone()));
15148 }
15149 } else {
15150 continue;
15151 }
15152 }
15153
15154 drop(snapshot);
15155 this.buffer.update(cx, |buffer, cx| {
15156 buffer.edit(edits, None, cx);
15157 });
15158
15159 // Adjust selections so that they end before any comment suffixes that
15160 // were inserted.
15161 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15162 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15163 let snapshot = this.buffer.read(cx).read(cx);
15164 for selection in &mut selections {
15165 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15166 match row.cmp(&MultiBufferRow(selection.end.row)) {
15167 Ordering::Less => {
15168 suffixes_inserted.next();
15169 continue;
15170 }
15171 Ordering::Greater => break,
15172 Ordering::Equal => {
15173 if selection.end.column == snapshot.line_len(row) {
15174 if selection.is_empty() {
15175 selection.start.column -= suffix_len as u32;
15176 }
15177 selection.end.column -= suffix_len as u32;
15178 }
15179 break;
15180 }
15181 }
15182 }
15183 }
15184
15185 drop(snapshot);
15186 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15187
15188 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15189 let selections_on_single_row = selections.windows(2).all(|selections| {
15190 selections[0].start.row == selections[1].start.row
15191 && selections[0].end.row == selections[1].end.row
15192 && selections[0].start.row == selections[0].end.row
15193 });
15194 let selections_selecting = selections
15195 .iter()
15196 .any(|selection| selection.start != selection.end);
15197 let advance_downwards = action.advance_downwards
15198 && selections_on_single_row
15199 && !selections_selecting
15200 && !matches!(this.mode, EditorMode::SingleLine);
15201
15202 if advance_downwards {
15203 let snapshot = this.buffer.read(cx).snapshot(cx);
15204
15205 this.change_selections(Default::default(), window, cx, |s| {
15206 s.move_cursors_with(|display_snapshot, display_point, _| {
15207 let mut point = display_point.to_point(display_snapshot);
15208 point.row += 1;
15209 point = snapshot.clip_point(point, Bias::Left);
15210 let display_point = point.to_display_point(display_snapshot);
15211 let goal = SelectionGoal::HorizontalPosition(
15212 display_snapshot
15213 .x_for_display_point(display_point, text_layout_details)
15214 .into(),
15215 );
15216 (display_point, goal)
15217 })
15218 });
15219 }
15220 });
15221 }
15222
15223 pub fn select_enclosing_symbol(
15224 &mut self,
15225 _: &SelectEnclosingSymbol,
15226 window: &mut Window,
15227 cx: &mut Context<Self>,
15228 ) {
15229 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15230
15231 let buffer = self.buffer.read(cx).snapshot(cx);
15232 let old_selections = self
15233 .selections
15234 .all::<usize>(&self.display_snapshot(cx))
15235 .into_boxed_slice();
15236
15237 fn update_selection(
15238 selection: &Selection<usize>,
15239 buffer_snap: &MultiBufferSnapshot,
15240 ) -> Option<Selection<usize>> {
15241 let cursor = selection.head();
15242 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15243 for symbol in symbols.iter().rev() {
15244 let start = symbol.range.start.to_offset(buffer_snap);
15245 let end = symbol.range.end.to_offset(buffer_snap);
15246 let new_range = start..end;
15247 if start < selection.start || end > selection.end {
15248 return Some(Selection {
15249 id: selection.id,
15250 start: new_range.start,
15251 end: new_range.end,
15252 goal: SelectionGoal::None,
15253 reversed: selection.reversed,
15254 });
15255 }
15256 }
15257 None
15258 }
15259
15260 let mut selected_larger_symbol = false;
15261 let new_selections = old_selections
15262 .iter()
15263 .map(|selection| match update_selection(selection, &buffer) {
15264 Some(new_selection) => {
15265 if new_selection.range() != selection.range() {
15266 selected_larger_symbol = true;
15267 }
15268 new_selection
15269 }
15270 None => selection.clone(),
15271 })
15272 .collect::<Vec<_>>();
15273
15274 if selected_larger_symbol {
15275 self.change_selections(Default::default(), window, cx, |s| {
15276 s.select(new_selections);
15277 });
15278 }
15279 }
15280
15281 pub fn select_larger_syntax_node(
15282 &mut self,
15283 _: &SelectLargerSyntaxNode,
15284 window: &mut Window,
15285 cx: &mut Context<Self>,
15286 ) {
15287 let Some(visible_row_count) = self.visible_row_count() else {
15288 return;
15289 };
15290 let old_selections: Box<[_]> = self
15291 .selections
15292 .all::<usize>(&self.display_snapshot(cx))
15293 .into();
15294 if old_selections.is_empty() {
15295 return;
15296 }
15297
15298 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15299
15300 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15301 let buffer = self.buffer.read(cx).snapshot(cx);
15302
15303 let mut selected_larger_node = false;
15304 let mut new_selections = old_selections
15305 .iter()
15306 .map(|selection| {
15307 let old_range = selection.start..selection.end;
15308
15309 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15310 // manually select word at selection
15311 if ["string_content", "inline"].contains(&node.kind()) {
15312 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15313 // ignore if word is already selected
15314 if !word_range.is_empty() && old_range != word_range {
15315 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15316 // only select word if start and end point belongs to same word
15317 if word_range == last_word_range {
15318 selected_larger_node = true;
15319 return Selection {
15320 id: selection.id,
15321 start: word_range.start,
15322 end: word_range.end,
15323 goal: SelectionGoal::None,
15324 reversed: selection.reversed,
15325 };
15326 }
15327 }
15328 }
15329 }
15330
15331 let mut new_range = old_range.clone();
15332 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15333 new_range = range;
15334 if !node.is_named() {
15335 continue;
15336 }
15337 if !display_map.intersects_fold(new_range.start)
15338 && !display_map.intersects_fold(new_range.end)
15339 {
15340 break;
15341 }
15342 }
15343
15344 selected_larger_node |= new_range != old_range;
15345 Selection {
15346 id: selection.id,
15347 start: new_range.start,
15348 end: new_range.end,
15349 goal: SelectionGoal::None,
15350 reversed: selection.reversed,
15351 }
15352 })
15353 .collect::<Vec<_>>();
15354
15355 if !selected_larger_node {
15356 return; // don't put this call in the history
15357 }
15358
15359 // scroll based on transformation done to the last selection created by the user
15360 let (last_old, last_new) = old_selections
15361 .last()
15362 .zip(new_selections.last().cloned())
15363 .expect("old_selections isn't empty");
15364
15365 // revert selection
15366 let is_selection_reversed = {
15367 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15368 new_selections.last_mut().expect("checked above").reversed =
15369 should_newest_selection_be_reversed;
15370 should_newest_selection_be_reversed
15371 };
15372
15373 if selected_larger_node {
15374 self.select_syntax_node_history.disable_clearing = true;
15375 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15376 s.select(new_selections.clone());
15377 });
15378 self.select_syntax_node_history.disable_clearing = false;
15379 }
15380
15381 let start_row = last_new.start.to_display_point(&display_map).row().0;
15382 let end_row = last_new.end.to_display_point(&display_map).row().0;
15383 let selection_height = end_row - start_row + 1;
15384 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15385
15386 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15387 let scroll_behavior = if fits_on_the_screen {
15388 self.request_autoscroll(Autoscroll::fit(), cx);
15389 SelectSyntaxNodeScrollBehavior::FitSelection
15390 } else if is_selection_reversed {
15391 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15392 SelectSyntaxNodeScrollBehavior::CursorTop
15393 } else {
15394 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15395 SelectSyntaxNodeScrollBehavior::CursorBottom
15396 };
15397
15398 self.select_syntax_node_history.push((
15399 old_selections,
15400 scroll_behavior,
15401 is_selection_reversed,
15402 ));
15403 }
15404
15405 pub fn select_smaller_syntax_node(
15406 &mut self,
15407 _: &SelectSmallerSyntaxNode,
15408 window: &mut Window,
15409 cx: &mut Context<Self>,
15410 ) {
15411 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15412
15413 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15414 self.select_syntax_node_history.pop()
15415 {
15416 if let Some(selection) = selections.last_mut() {
15417 selection.reversed = is_selection_reversed;
15418 }
15419
15420 self.select_syntax_node_history.disable_clearing = true;
15421 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15422 s.select(selections.to_vec());
15423 });
15424 self.select_syntax_node_history.disable_clearing = false;
15425
15426 match scroll_behavior {
15427 SelectSyntaxNodeScrollBehavior::CursorTop => {
15428 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15429 }
15430 SelectSyntaxNodeScrollBehavior::FitSelection => {
15431 self.request_autoscroll(Autoscroll::fit(), cx);
15432 }
15433 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15434 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15435 }
15436 }
15437 }
15438 }
15439
15440 pub fn unwrap_syntax_node(
15441 &mut self,
15442 _: &UnwrapSyntaxNode,
15443 window: &mut Window,
15444 cx: &mut Context<Self>,
15445 ) {
15446 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15447
15448 let buffer = self.buffer.read(cx).snapshot(cx);
15449 let selections = self
15450 .selections
15451 .all::<usize>(&self.display_snapshot(cx))
15452 .into_iter()
15453 // subtracting the offset requires sorting
15454 .sorted_by_key(|i| i.start);
15455
15456 let full_edits = selections
15457 .into_iter()
15458 .filter_map(|selection| {
15459 let child = if selection.is_empty()
15460 && let Some((_, ancestor_range)) =
15461 buffer.syntax_ancestor(selection.start..selection.end)
15462 {
15463 ancestor_range
15464 } else {
15465 selection.range()
15466 };
15467
15468 let mut parent = child.clone();
15469 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15470 parent = ancestor_range;
15471 if parent.start < child.start || parent.end > child.end {
15472 break;
15473 }
15474 }
15475
15476 if parent == child {
15477 return None;
15478 }
15479 let text = buffer.text_for_range(child).collect::<String>();
15480 Some((selection.id, parent, text))
15481 })
15482 .collect::<Vec<_>>();
15483 if full_edits.is_empty() {
15484 return;
15485 }
15486
15487 self.transact(window, cx, |this, window, cx| {
15488 this.buffer.update(cx, |buffer, cx| {
15489 buffer.edit(
15490 full_edits
15491 .iter()
15492 .map(|(_, p, t)| (p.clone(), t.clone()))
15493 .collect::<Vec<_>>(),
15494 None,
15495 cx,
15496 );
15497 });
15498 this.change_selections(Default::default(), window, cx, |s| {
15499 let mut offset = 0;
15500 let mut selections = vec![];
15501 for (id, parent, text) in full_edits {
15502 let start = parent.start - offset;
15503 offset += parent.len() - text.len();
15504 selections.push(Selection {
15505 id,
15506 start,
15507 end: start + text.len(),
15508 reversed: false,
15509 goal: Default::default(),
15510 });
15511 }
15512 s.select(selections);
15513 });
15514 });
15515 }
15516
15517 pub fn select_next_syntax_node(
15518 &mut self,
15519 _: &SelectNextSyntaxNode,
15520 window: &mut Window,
15521 cx: &mut Context<Self>,
15522 ) {
15523 let old_selections: Box<[_]> = self
15524 .selections
15525 .all::<usize>(&self.display_snapshot(cx))
15526 .into();
15527 if old_selections.is_empty() {
15528 return;
15529 }
15530
15531 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15532
15533 let buffer = self.buffer.read(cx).snapshot(cx);
15534 let mut selected_sibling = false;
15535
15536 let new_selections = old_selections
15537 .iter()
15538 .map(|selection| {
15539 let old_range = selection.start..selection.end;
15540
15541 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15542 let new_range = node.byte_range();
15543 selected_sibling = true;
15544 Selection {
15545 id: selection.id,
15546 start: new_range.start,
15547 end: new_range.end,
15548 goal: SelectionGoal::None,
15549 reversed: selection.reversed,
15550 }
15551 } else {
15552 selection.clone()
15553 }
15554 })
15555 .collect::<Vec<_>>();
15556
15557 if selected_sibling {
15558 self.change_selections(
15559 SelectionEffects::scroll(Autoscroll::fit()),
15560 window,
15561 cx,
15562 |s| {
15563 s.select(new_selections);
15564 },
15565 );
15566 }
15567 }
15568
15569 pub fn select_prev_syntax_node(
15570 &mut self,
15571 _: &SelectPreviousSyntaxNode,
15572 window: &mut Window,
15573 cx: &mut Context<Self>,
15574 ) {
15575 let old_selections: Box<[_]> = self
15576 .selections
15577 .all::<usize>(&self.display_snapshot(cx))
15578 .into();
15579 if old_selections.is_empty() {
15580 return;
15581 }
15582
15583 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15584
15585 let buffer = self.buffer.read(cx).snapshot(cx);
15586 let mut selected_sibling = false;
15587
15588 let new_selections = old_selections
15589 .iter()
15590 .map(|selection| {
15591 let old_range = selection.start..selection.end;
15592
15593 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15594 let new_range = node.byte_range();
15595 selected_sibling = true;
15596 Selection {
15597 id: selection.id,
15598 start: new_range.start,
15599 end: new_range.end,
15600 goal: SelectionGoal::None,
15601 reversed: selection.reversed,
15602 }
15603 } else {
15604 selection.clone()
15605 }
15606 })
15607 .collect::<Vec<_>>();
15608
15609 if selected_sibling {
15610 self.change_selections(
15611 SelectionEffects::scroll(Autoscroll::fit()),
15612 window,
15613 cx,
15614 |s| {
15615 s.select(new_selections);
15616 },
15617 );
15618 }
15619 }
15620
15621 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15622 if !EditorSettings::get_global(cx).gutter.runnables {
15623 self.clear_tasks();
15624 return Task::ready(());
15625 }
15626 let project = self.project().map(Entity::downgrade);
15627 let task_sources = self.lsp_task_sources(cx);
15628 let multi_buffer = self.buffer.downgrade();
15629 cx.spawn_in(window, async move |editor, cx| {
15630 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15631 let Some(project) = project.and_then(|p| p.upgrade()) else {
15632 return;
15633 };
15634 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15635 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15636 }) else {
15637 return;
15638 };
15639
15640 let hide_runnables = project
15641 .update(cx, |project, _| project.is_via_collab())
15642 .unwrap_or(true);
15643 if hide_runnables {
15644 return;
15645 }
15646 let new_rows =
15647 cx.background_spawn({
15648 let snapshot = display_snapshot.clone();
15649 async move {
15650 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15651 }
15652 })
15653 .await;
15654 let Ok(lsp_tasks) =
15655 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15656 else {
15657 return;
15658 };
15659 let lsp_tasks = lsp_tasks.await;
15660
15661 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15662 lsp_tasks
15663 .into_iter()
15664 .flat_map(|(kind, tasks)| {
15665 tasks.into_iter().filter_map(move |(location, task)| {
15666 Some((kind.clone(), location?, task))
15667 })
15668 })
15669 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15670 let buffer = location.target.buffer;
15671 let buffer_snapshot = buffer.read(cx).snapshot();
15672 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15673 |(excerpt_id, snapshot, _)| {
15674 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15675 display_snapshot
15676 .buffer_snapshot()
15677 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15678 } else {
15679 None
15680 }
15681 },
15682 );
15683 if let Some(offset) = offset {
15684 let task_buffer_range =
15685 location.target.range.to_point(&buffer_snapshot);
15686 let context_buffer_range =
15687 task_buffer_range.to_offset(&buffer_snapshot);
15688 let context_range = BufferOffset(context_buffer_range.start)
15689 ..BufferOffset(context_buffer_range.end);
15690
15691 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15692 .or_insert_with(|| RunnableTasks {
15693 templates: Vec::new(),
15694 offset,
15695 column: task_buffer_range.start.column,
15696 extra_variables: HashMap::default(),
15697 context_range,
15698 })
15699 .templates
15700 .push((kind, task.original_task().clone()));
15701 }
15702
15703 acc
15704 })
15705 }) else {
15706 return;
15707 };
15708
15709 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15710 buffer.language_settings(cx).tasks.prefer_lsp
15711 }) else {
15712 return;
15713 };
15714
15715 let rows = Self::runnable_rows(
15716 project,
15717 display_snapshot,
15718 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15719 new_rows,
15720 cx.clone(),
15721 )
15722 .await;
15723 editor
15724 .update(cx, |editor, _| {
15725 editor.clear_tasks();
15726 for (key, mut value) in rows {
15727 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15728 value.templates.extend(lsp_tasks.templates);
15729 }
15730
15731 editor.insert_tasks(key, value);
15732 }
15733 for (key, value) in lsp_tasks_by_rows {
15734 editor.insert_tasks(key, value);
15735 }
15736 })
15737 .ok();
15738 })
15739 }
15740 fn fetch_runnable_ranges(
15741 snapshot: &DisplaySnapshot,
15742 range: Range<Anchor>,
15743 ) -> Vec<language::RunnableRange> {
15744 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15745 }
15746
15747 fn runnable_rows(
15748 project: Entity<Project>,
15749 snapshot: DisplaySnapshot,
15750 prefer_lsp: bool,
15751 runnable_ranges: Vec<RunnableRange>,
15752 cx: AsyncWindowContext,
15753 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15754 cx.spawn(async move |cx| {
15755 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15756 for mut runnable in runnable_ranges {
15757 let Some(tasks) = cx
15758 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15759 .ok()
15760 else {
15761 continue;
15762 };
15763 let mut tasks = tasks.await;
15764
15765 if prefer_lsp {
15766 tasks.retain(|(task_kind, _)| {
15767 !matches!(task_kind, TaskSourceKind::Language { .. })
15768 });
15769 }
15770 if tasks.is_empty() {
15771 continue;
15772 }
15773
15774 let point = runnable
15775 .run_range
15776 .start
15777 .to_point(&snapshot.buffer_snapshot());
15778 let Some(row) = snapshot
15779 .buffer_snapshot()
15780 .buffer_line_for_row(MultiBufferRow(point.row))
15781 .map(|(_, range)| range.start.row)
15782 else {
15783 continue;
15784 };
15785
15786 let context_range =
15787 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15788 runnable_rows.push((
15789 (runnable.buffer_id, row),
15790 RunnableTasks {
15791 templates: tasks,
15792 offset: snapshot
15793 .buffer_snapshot()
15794 .anchor_before(runnable.run_range.start),
15795 context_range,
15796 column: point.column,
15797 extra_variables: runnable.extra_captures,
15798 },
15799 ));
15800 }
15801 runnable_rows
15802 })
15803 }
15804
15805 fn templates_with_tags(
15806 project: &Entity<Project>,
15807 runnable: &mut Runnable,
15808 cx: &mut App,
15809 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15810 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15811 let (worktree_id, file) = project
15812 .buffer_for_id(runnable.buffer, cx)
15813 .and_then(|buffer| buffer.read(cx).file())
15814 .map(|file| (file.worktree_id(cx), file.clone()))
15815 .unzip();
15816
15817 (
15818 project.task_store().read(cx).task_inventory().cloned(),
15819 worktree_id,
15820 file,
15821 )
15822 });
15823
15824 let tags = mem::take(&mut runnable.tags);
15825 let language = runnable.language.clone();
15826 cx.spawn(async move |cx| {
15827 let mut templates_with_tags = Vec::new();
15828 if let Some(inventory) = inventory {
15829 for RunnableTag(tag) in tags {
15830 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15831 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15832 }) else {
15833 return templates_with_tags;
15834 };
15835 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15836 move |(_, template)| {
15837 template.tags.iter().any(|source_tag| source_tag == &tag)
15838 },
15839 ));
15840 }
15841 }
15842 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15843
15844 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15845 // Strongest source wins; if we have worktree tag binding, prefer that to
15846 // global and language bindings;
15847 // if we have a global binding, prefer that to language binding.
15848 let first_mismatch = templates_with_tags
15849 .iter()
15850 .position(|(tag_source, _)| tag_source != leading_tag_source);
15851 if let Some(index) = first_mismatch {
15852 templates_with_tags.truncate(index);
15853 }
15854 }
15855
15856 templates_with_tags
15857 })
15858 }
15859
15860 pub fn move_to_enclosing_bracket(
15861 &mut self,
15862 _: &MoveToEnclosingBracket,
15863 window: &mut Window,
15864 cx: &mut Context<Self>,
15865 ) {
15866 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15867 self.change_selections(Default::default(), window, cx, |s| {
15868 s.move_offsets_with(|snapshot, selection| {
15869 let Some(enclosing_bracket_ranges) =
15870 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15871 else {
15872 return;
15873 };
15874
15875 let mut best_length = usize::MAX;
15876 let mut best_inside = false;
15877 let mut best_in_bracket_range = false;
15878 let mut best_destination = None;
15879 for (open, close) in enclosing_bracket_ranges {
15880 let close = close.to_inclusive();
15881 let length = close.end() - open.start;
15882 let inside = selection.start >= open.end && selection.end <= *close.start();
15883 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15884 || close.contains(&selection.head());
15885
15886 // If best is next to a bracket and current isn't, skip
15887 if !in_bracket_range && best_in_bracket_range {
15888 continue;
15889 }
15890
15891 // Prefer smaller lengths unless best is inside and current isn't
15892 if length > best_length && (best_inside || !inside) {
15893 continue;
15894 }
15895
15896 best_length = length;
15897 best_inside = inside;
15898 best_in_bracket_range = in_bracket_range;
15899 best_destination = Some(
15900 if close.contains(&selection.start) && close.contains(&selection.end) {
15901 if inside { open.end } else { open.start }
15902 } else if inside {
15903 *close.start()
15904 } else {
15905 *close.end()
15906 },
15907 );
15908 }
15909
15910 if let Some(destination) = best_destination {
15911 selection.collapse_to(destination, SelectionGoal::None);
15912 }
15913 })
15914 });
15915 }
15916
15917 pub fn undo_selection(
15918 &mut self,
15919 _: &UndoSelection,
15920 window: &mut Window,
15921 cx: &mut Context<Self>,
15922 ) {
15923 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15924 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15925 self.selection_history.mode = SelectionHistoryMode::Undoing;
15926 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15927 this.end_selection(window, cx);
15928 this.change_selections(
15929 SelectionEffects::scroll(Autoscroll::newest()),
15930 window,
15931 cx,
15932 |s| s.select_anchors(entry.selections.to_vec()),
15933 );
15934 });
15935 self.selection_history.mode = SelectionHistoryMode::Normal;
15936
15937 self.select_next_state = entry.select_next_state;
15938 self.select_prev_state = entry.select_prev_state;
15939 self.add_selections_state = entry.add_selections_state;
15940 }
15941 }
15942
15943 pub fn redo_selection(
15944 &mut self,
15945 _: &RedoSelection,
15946 window: &mut Window,
15947 cx: &mut Context<Self>,
15948 ) {
15949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15950 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15951 self.selection_history.mode = SelectionHistoryMode::Redoing;
15952 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15953 this.end_selection(window, cx);
15954 this.change_selections(
15955 SelectionEffects::scroll(Autoscroll::newest()),
15956 window,
15957 cx,
15958 |s| s.select_anchors(entry.selections.to_vec()),
15959 );
15960 });
15961 self.selection_history.mode = SelectionHistoryMode::Normal;
15962
15963 self.select_next_state = entry.select_next_state;
15964 self.select_prev_state = entry.select_prev_state;
15965 self.add_selections_state = entry.add_selections_state;
15966 }
15967 }
15968
15969 pub fn expand_excerpts(
15970 &mut self,
15971 action: &ExpandExcerpts,
15972 _: &mut Window,
15973 cx: &mut Context<Self>,
15974 ) {
15975 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15976 }
15977
15978 pub fn expand_excerpts_down(
15979 &mut self,
15980 action: &ExpandExcerptsDown,
15981 _: &mut Window,
15982 cx: &mut Context<Self>,
15983 ) {
15984 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15985 }
15986
15987 pub fn expand_excerpts_up(
15988 &mut self,
15989 action: &ExpandExcerptsUp,
15990 _: &mut Window,
15991 cx: &mut Context<Self>,
15992 ) {
15993 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15994 }
15995
15996 pub fn expand_excerpts_for_direction(
15997 &mut self,
15998 lines: u32,
15999 direction: ExpandExcerptDirection,
16000
16001 cx: &mut Context<Self>,
16002 ) {
16003 let selections = self.selections.disjoint_anchors_arc();
16004
16005 let lines = if lines == 0 {
16006 EditorSettings::get_global(cx).expand_excerpt_lines
16007 } else {
16008 lines
16009 };
16010
16011 self.buffer.update(cx, |buffer, cx| {
16012 let snapshot = buffer.snapshot(cx);
16013 let mut excerpt_ids = selections
16014 .iter()
16015 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16016 .collect::<Vec<_>>();
16017 excerpt_ids.sort();
16018 excerpt_ids.dedup();
16019 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16020 })
16021 }
16022
16023 pub fn expand_excerpt(
16024 &mut self,
16025 excerpt: ExcerptId,
16026 direction: ExpandExcerptDirection,
16027 window: &mut Window,
16028 cx: &mut Context<Self>,
16029 ) {
16030 let current_scroll_position = self.scroll_position(cx);
16031 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16032 let mut should_scroll_up = false;
16033
16034 if direction == ExpandExcerptDirection::Down {
16035 let multi_buffer = self.buffer.read(cx);
16036 let snapshot = multi_buffer.snapshot(cx);
16037 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16038 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16039 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16040 {
16041 let buffer_snapshot = buffer.read(cx).snapshot();
16042 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16043 let last_row = buffer_snapshot.max_point().row;
16044 let lines_below = last_row.saturating_sub(excerpt_end_row);
16045 should_scroll_up = lines_below >= lines_to_expand;
16046 }
16047 }
16048
16049 self.buffer.update(cx, |buffer, cx| {
16050 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16051 });
16052
16053 if should_scroll_up {
16054 let new_scroll_position =
16055 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
16056 self.set_scroll_position(new_scroll_position, window, cx);
16057 }
16058 }
16059
16060 pub fn go_to_singleton_buffer_point(
16061 &mut self,
16062 point: Point,
16063 window: &mut Window,
16064 cx: &mut Context<Self>,
16065 ) {
16066 self.go_to_singleton_buffer_range(point..point, window, cx);
16067 }
16068
16069 pub fn go_to_singleton_buffer_range(
16070 &mut self,
16071 range: Range<Point>,
16072 window: &mut Window,
16073 cx: &mut Context<Self>,
16074 ) {
16075 let multibuffer = self.buffer().read(cx);
16076 let Some(buffer) = multibuffer.as_singleton() else {
16077 return;
16078 };
16079 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16080 return;
16081 };
16082 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16083 return;
16084 };
16085 self.change_selections(
16086 SelectionEffects::default().nav_history(true),
16087 window,
16088 cx,
16089 |s| s.select_anchor_ranges([start..end]),
16090 );
16091 }
16092
16093 pub fn go_to_diagnostic(
16094 &mut self,
16095 action: &GoToDiagnostic,
16096 window: &mut Window,
16097 cx: &mut Context<Self>,
16098 ) {
16099 if !self.diagnostics_enabled() {
16100 return;
16101 }
16102 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16103 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16104 }
16105
16106 pub fn go_to_prev_diagnostic(
16107 &mut self,
16108 action: &GoToPreviousDiagnostic,
16109 window: &mut Window,
16110 cx: &mut Context<Self>,
16111 ) {
16112 if !self.diagnostics_enabled() {
16113 return;
16114 }
16115 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16116 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16117 }
16118
16119 pub fn go_to_diagnostic_impl(
16120 &mut self,
16121 direction: Direction,
16122 severity: GoToDiagnosticSeverityFilter,
16123 window: &mut Window,
16124 cx: &mut Context<Self>,
16125 ) {
16126 let buffer = self.buffer.read(cx).snapshot(cx);
16127 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16128
16129 let mut active_group_id = None;
16130 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16131 && active_group.active_range.start.to_offset(&buffer) == selection.start
16132 {
16133 active_group_id = Some(active_group.group_id);
16134 }
16135
16136 fn filtered<'a>(
16137 snapshot: EditorSnapshot,
16138 severity: GoToDiagnosticSeverityFilter,
16139 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16140 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16141 diagnostics
16142 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16143 .filter(|entry| entry.range.start != entry.range.end)
16144 .filter(|entry| !entry.diagnostic.is_unnecessary)
16145 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16146 }
16147
16148 let snapshot = self.snapshot(window, cx);
16149 let before = filtered(
16150 snapshot.clone(),
16151 severity,
16152 buffer
16153 .diagnostics_in_range(0..selection.start)
16154 .filter(|entry| entry.range.start <= selection.start),
16155 );
16156 let after = filtered(
16157 snapshot,
16158 severity,
16159 buffer
16160 .diagnostics_in_range(selection.start..buffer.len())
16161 .filter(|entry| entry.range.start >= selection.start),
16162 );
16163
16164 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16165 if direction == Direction::Prev {
16166 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16167 {
16168 for diagnostic in prev_diagnostics.into_iter().rev() {
16169 if diagnostic.range.start != selection.start
16170 || active_group_id
16171 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16172 {
16173 found = Some(diagnostic);
16174 break 'outer;
16175 }
16176 }
16177 }
16178 } else {
16179 for diagnostic in after.chain(before) {
16180 if diagnostic.range.start != selection.start
16181 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16182 {
16183 found = Some(diagnostic);
16184 break;
16185 }
16186 }
16187 }
16188 let Some(next_diagnostic) = found else {
16189 return;
16190 };
16191
16192 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16193 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16194 return;
16195 };
16196 self.change_selections(Default::default(), window, cx, |s| {
16197 s.select_ranges(vec![
16198 next_diagnostic.range.start..next_diagnostic.range.start,
16199 ])
16200 });
16201 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16202 self.refresh_edit_prediction(false, true, window, cx);
16203 }
16204
16205 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16206 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16207 let snapshot = self.snapshot(window, cx);
16208 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16209 self.go_to_hunk_before_or_after_position(
16210 &snapshot,
16211 selection.head(),
16212 Direction::Next,
16213 window,
16214 cx,
16215 );
16216 }
16217
16218 pub fn go_to_hunk_before_or_after_position(
16219 &mut self,
16220 snapshot: &EditorSnapshot,
16221 position: Point,
16222 direction: Direction,
16223 window: &mut Window,
16224 cx: &mut Context<Editor>,
16225 ) {
16226 let row = if direction == Direction::Next {
16227 self.hunk_after_position(snapshot, position)
16228 .map(|hunk| hunk.row_range.start)
16229 } else {
16230 self.hunk_before_position(snapshot, position)
16231 };
16232
16233 if let Some(row) = row {
16234 let destination = Point::new(row.0, 0);
16235 let autoscroll = Autoscroll::center();
16236
16237 self.unfold_ranges(&[destination..destination], false, false, cx);
16238 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16239 s.select_ranges([destination..destination]);
16240 });
16241 }
16242 }
16243
16244 fn hunk_after_position(
16245 &mut self,
16246 snapshot: &EditorSnapshot,
16247 position: Point,
16248 ) -> Option<MultiBufferDiffHunk> {
16249 snapshot
16250 .buffer_snapshot()
16251 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16252 .find(|hunk| hunk.row_range.start.0 > position.row)
16253 .or_else(|| {
16254 snapshot
16255 .buffer_snapshot()
16256 .diff_hunks_in_range(Point::zero()..position)
16257 .find(|hunk| hunk.row_range.end.0 < position.row)
16258 })
16259 }
16260
16261 fn go_to_prev_hunk(
16262 &mut self,
16263 _: &GoToPreviousHunk,
16264 window: &mut Window,
16265 cx: &mut Context<Self>,
16266 ) {
16267 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16268 let snapshot = self.snapshot(window, cx);
16269 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16270 self.go_to_hunk_before_or_after_position(
16271 &snapshot,
16272 selection.head(),
16273 Direction::Prev,
16274 window,
16275 cx,
16276 );
16277 }
16278
16279 fn hunk_before_position(
16280 &mut self,
16281 snapshot: &EditorSnapshot,
16282 position: Point,
16283 ) -> Option<MultiBufferRow> {
16284 snapshot
16285 .buffer_snapshot()
16286 .diff_hunk_before(position)
16287 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16288 }
16289
16290 fn go_to_next_change(
16291 &mut self,
16292 _: &GoToNextChange,
16293 window: &mut Window,
16294 cx: &mut Context<Self>,
16295 ) {
16296 if let Some(selections) = self
16297 .change_list
16298 .next_change(1, Direction::Next)
16299 .map(|s| s.to_vec())
16300 {
16301 self.change_selections(Default::default(), window, cx, |s| {
16302 let map = s.display_map();
16303 s.select_display_ranges(selections.iter().map(|a| {
16304 let point = a.to_display_point(&map);
16305 point..point
16306 }))
16307 })
16308 }
16309 }
16310
16311 fn go_to_previous_change(
16312 &mut self,
16313 _: &GoToPreviousChange,
16314 window: &mut Window,
16315 cx: &mut Context<Self>,
16316 ) {
16317 if let Some(selections) = self
16318 .change_list
16319 .next_change(1, Direction::Prev)
16320 .map(|s| s.to_vec())
16321 {
16322 self.change_selections(Default::default(), window, cx, |s| {
16323 let map = s.display_map();
16324 s.select_display_ranges(selections.iter().map(|a| {
16325 let point = a.to_display_point(&map);
16326 point..point
16327 }))
16328 })
16329 }
16330 }
16331
16332 pub fn go_to_next_document_highlight(
16333 &mut self,
16334 _: &GoToNextDocumentHighlight,
16335 window: &mut Window,
16336 cx: &mut Context<Self>,
16337 ) {
16338 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16339 }
16340
16341 pub fn go_to_prev_document_highlight(
16342 &mut self,
16343 _: &GoToPreviousDocumentHighlight,
16344 window: &mut Window,
16345 cx: &mut Context<Self>,
16346 ) {
16347 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16348 }
16349
16350 pub fn go_to_document_highlight_before_or_after_position(
16351 &mut self,
16352 direction: Direction,
16353 window: &mut Window,
16354 cx: &mut Context<Editor>,
16355 ) {
16356 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16357 let snapshot = self.snapshot(window, cx);
16358 let buffer = &snapshot.buffer_snapshot();
16359 let position = self
16360 .selections
16361 .newest::<Point>(&snapshot.display_snapshot)
16362 .head();
16363 let anchor_position = buffer.anchor_after(position);
16364
16365 // Get all document highlights (both read and write)
16366 let mut all_highlights = Vec::new();
16367
16368 if let Some((_, read_highlights)) = self
16369 .background_highlights
16370 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16371 {
16372 all_highlights.extend(read_highlights.iter());
16373 }
16374
16375 if let Some((_, write_highlights)) = self
16376 .background_highlights
16377 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16378 {
16379 all_highlights.extend(write_highlights.iter());
16380 }
16381
16382 if all_highlights.is_empty() {
16383 return;
16384 }
16385
16386 // Sort highlights by position
16387 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16388
16389 let target_highlight = match direction {
16390 Direction::Next => {
16391 // Find the first highlight after the current position
16392 all_highlights
16393 .iter()
16394 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16395 }
16396 Direction::Prev => {
16397 // Find the last highlight before the current position
16398 all_highlights
16399 .iter()
16400 .rev()
16401 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16402 }
16403 };
16404
16405 if let Some(highlight) = target_highlight {
16406 let destination = highlight.start.to_point(buffer);
16407 let autoscroll = Autoscroll::center();
16408
16409 self.unfold_ranges(&[destination..destination], false, false, cx);
16410 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16411 s.select_ranges([destination..destination]);
16412 });
16413 }
16414 }
16415
16416 fn go_to_line<T: 'static>(
16417 &mut self,
16418 position: Anchor,
16419 highlight_color: Option<Hsla>,
16420 window: &mut Window,
16421 cx: &mut Context<Self>,
16422 ) {
16423 let snapshot = self.snapshot(window, cx).display_snapshot;
16424 let position = position.to_point(&snapshot.buffer_snapshot());
16425 let start = snapshot
16426 .buffer_snapshot()
16427 .clip_point(Point::new(position.row, 0), Bias::Left);
16428 let end = start + Point::new(1, 0);
16429 let start = snapshot.buffer_snapshot().anchor_before(start);
16430 let end = snapshot.buffer_snapshot().anchor_before(end);
16431
16432 self.highlight_rows::<T>(
16433 start..end,
16434 highlight_color
16435 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16436 Default::default(),
16437 cx,
16438 );
16439
16440 if self.buffer.read(cx).is_singleton() {
16441 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16442 }
16443 }
16444
16445 pub fn go_to_definition(
16446 &mut self,
16447 _: &GoToDefinition,
16448 window: &mut Window,
16449 cx: &mut Context<Self>,
16450 ) -> Task<Result<Navigated>> {
16451 let definition =
16452 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16453 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16454 cx.spawn_in(window, async move |editor, cx| {
16455 if definition.await? == Navigated::Yes {
16456 return Ok(Navigated::Yes);
16457 }
16458 match fallback_strategy {
16459 GoToDefinitionFallback::None => Ok(Navigated::No),
16460 GoToDefinitionFallback::FindAllReferences => {
16461 match editor.update_in(cx, |editor, window, cx| {
16462 editor.find_all_references(&FindAllReferences, window, cx)
16463 })? {
16464 Some(references) => references.await,
16465 None => Ok(Navigated::No),
16466 }
16467 }
16468 }
16469 })
16470 }
16471
16472 pub fn go_to_declaration(
16473 &mut self,
16474 _: &GoToDeclaration,
16475 window: &mut Window,
16476 cx: &mut Context<Self>,
16477 ) -> Task<Result<Navigated>> {
16478 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16479 }
16480
16481 pub fn go_to_declaration_split(
16482 &mut self,
16483 _: &GoToDeclaration,
16484 window: &mut Window,
16485 cx: &mut Context<Self>,
16486 ) -> Task<Result<Navigated>> {
16487 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16488 }
16489
16490 pub fn go_to_implementation(
16491 &mut self,
16492 _: &GoToImplementation,
16493 window: &mut Window,
16494 cx: &mut Context<Self>,
16495 ) -> Task<Result<Navigated>> {
16496 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16497 }
16498
16499 pub fn go_to_implementation_split(
16500 &mut self,
16501 _: &GoToImplementationSplit,
16502 window: &mut Window,
16503 cx: &mut Context<Self>,
16504 ) -> Task<Result<Navigated>> {
16505 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16506 }
16507
16508 pub fn go_to_type_definition(
16509 &mut self,
16510 _: &GoToTypeDefinition,
16511 window: &mut Window,
16512 cx: &mut Context<Self>,
16513 ) -> Task<Result<Navigated>> {
16514 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16515 }
16516
16517 pub fn go_to_definition_split(
16518 &mut self,
16519 _: &GoToDefinitionSplit,
16520 window: &mut Window,
16521 cx: &mut Context<Self>,
16522 ) -> Task<Result<Navigated>> {
16523 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16524 }
16525
16526 pub fn go_to_type_definition_split(
16527 &mut self,
16528 _: &GoToTypeDefinitionSplit,
16529 window: &mut Window,
16530 cx: &mut Context<Self>,
16531 ) -> Task<Result<Navigated>> {
16532 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16533 }
16534
16535 fn go_to_definition_of_kind(
16536 &mut self,
16537 kind: GotoDefinitionKind,
16538 split: bool,
16539 window: &mut Window,
16540 cx: &mut Context<Self>,
16541 ) -> Task<Result<Navigated>> {
16542 let Some(provider) = self.semantics_provider.clone() else {
16543 return Task::ready(Ok(Navigated::No));
16544 };
16545 let head = self
16546 .selections
16547 .newest::<usize>(&self.display_snapshot(cx))
16548 .head();
16549 let buffer = self.buffer.read(cx);
16550 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16551 return Task::ready(Ok(Navigated::No));
16552 };
16553 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16554 return Task::ready(Ok(Navigated::No));
16555 };
16556
16557 cx.spawn_in(window, async move |editor, cx| {
16558 let Some(definitions) = definitions.await? else {
16559 return Ok(Navigated::No);
16560 };
16561 let navigated = editor
16562 .update_in(cx, |editor, window, cx| {
16563 editor.navigate_to_hover_links(
16564 Some(kind),
16565 definitions
16566 .into_iter()
16567 .filter(|location| {
16568 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16569 })
16570 .map(HoverLink::Text)
16571 .collect::<Vec<_>>(),
16572 split,
16573 window,
16574 cx,
16575 )
16576 })?
16577 .await?;
16578 anyhow::Ok(navigated)
16579 })
16580 }
16581
16582 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16583 let selection = self.selections.newest_anchor();
16584 let head = selection.head();
16585 let tail = selection.tail();
16586
16587 let Some((buffer, start_position)) =
16588 self.buffer.read(cx).text_anchor_for_position(head, cx)
16589 else {
16590 return;
16591 };
16592
16593 let end_position = if head != tail {
16594 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16595 return;
16596 };
16597 Some(pos)
16598 } else {
16599 None
16600 };
16601
16602 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16603 let url = if let Some(end_pos) = end_position {
16604 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16605 } else {
16606 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16607 };
16608
16609 if let Some(url) = url {
16610 cx.update(|window, cx| {
16611 if parse_zed_link(&url, cx).is_some() {
16612 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16613 } else {
16614 cx.open_url(&url);
16615 }
16616 })?;
16617 }
16618
16619 anyhow::Ok(())
16620 });
16621
16622 url_finder.detach();
16623 }
16624
16625 pub fn open_selected_filename(
16626 &mut self,
16627 _: &OpenSelectedFilename,
16628 window: &mut Window,
16629 cx: &mut Context<Self>,
16630 ) {
16631 let Some(workspace) = self.workspace() else {
16632 return;
16633 };
16634
16635 let position = self.selections.newest_anchor().head();
16636
16637 let Some((buffer, buffer_position)) =
16638 self.buffer.read(cx).text_anchor_for_position(position, cx)
16639 else {
16640 return;
16641 };
16642
16643 let project = self.project.clone();
16644
16645 cx.spawn_in(window, async move |_, cx| {
16646 let result = find_file(&buffer, project, buffer_position, cx).await;
16647
16648 if let Some((_, path)) = result {
16649 workspace
16650 .update_in(cx, |workspace, window, cx| {
16651 workspace.open_resolved_path(path, window, cx)
16652 })?
16653 .await?;
16654 }
16655 anyhow::Ok(())
16656 })
16657 .detach();
16658 }
16659
16660 pub(crate) fn navigate_to_hover_links(
16661 &mut self,
16662 kind: Option<GotoDefinitionKind>,
16663 definitions: Vec<HoverLink>,
16664 split: bool,
16665 window: &mut Window,
16666 cx: &mut Context<Editor>,
16667 ) -> Task<Result<Navigated>> {
16668 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16669 let mut first_url_or_file = None;
16670 let definitions: Vec<_> = definitions
16671 .into_iter()
16672 .filter_map(|def| match def {
16673 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16674 HoverLink::InlayHint(lsp_location, server_id) => {
16675 let computation =
16676 self.compute_target_location(lsp_location, server_id, window, cx);
16677 Some(cx.background_spawn(computation))
16678 }
16679 HoverLink::Url(url) => {
16680 first_url_or_file = Some(Either::Left(url));
16681 None
16682 }
16683 HoverLink::File(path) => {
16684 first_url_or_file = Some(Either::Right(path));
16685 None
16686 }
16687 })
16688 .collect();
16689
16690 let workspace = self.workspace();
16691
16692 cx.spawn_in(window, async move |editor, cx| {
16693 let locations: Vec<Location> = future::join_all(definitions)
16694 .await
16695 .into_iter()
16696 .filter_map(|location| location.transpose())
16697 .collect::<Result<_>>()
16698 .context("location tasks")?;
16699 let mut locations = cx.update(|_, cx| {
16700 locations
16701 .into_iter()
16702 .map(|location| {
16703 let buffer = location.buffer.read(cx);
16704 (location.buffer, location.range.to_point(buffer))
16705 })
16706 .into_group_map()
16707 })?;
16708 let mut num_locations = 0;
16709 for ranges in locations.values_mut() {
16710 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16711 ranges.dedup();
16712 num_locations += ranges.len();
16713 }
16714
16715 if num_locations > 1 {
16716 let Some(workspace) = workspace else {
16717 return Ok(Navigated::No);
16718 };
16719
16720 let tab_kind = match kind {
16721 Some(GotoDefinitionKind::Implementation) => "Implementations",
16722 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16723 Some(GotoDefinitionKind::Declaration) => "Declarations",
16724 Some(GotoDefinitionKind::Type) => "Types",
16725 };
16726 let title = editor
16727 .update_in(cx, |_, _, cx| {
16728 let target = locations
16729 .iter()
16730 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16731 .map(|(buffer, location)| {
16732 buffer
16733 .read(cx)
16734 .text_for_range(location.clone())
16735 .collect::<String>()
16736 })
16737 .filter(|text| !text.contains('\n'))
16738 .unique()
16739 .take(3)
16740 .join(", ");
16741 if target.is_empty() {
16742 tab_kind.to_owned()
16743 } else {
16744 format!("{tab_kind} for {target}")
16745 }
16746 })
16747 .context("buffer title")?;
16748
16749 let opened = workspace
16750 .update_in(cx, |workspace, window, cx| {
16751 Self::open_locations_in_multibuffer(
16752 workspace,
16753 locations,
16754 title,
16755 split,
16756 MultibufferSelectionMode::First,
16757 window,
16758 cx,
16759 )
16760 })
16761 .is_ok();
16762
16763 anyhow::Ok(Navigated::from_bool(opened))
16764 } else if num_locations == 0 {
16765 // If there is one url or file, open it directly
16766 match first_url_or_file {
16767 Some(Either::Left(url)) => {
16768 cx.update(|_, cx| cx.open_url(&url))?;
16769 Ok(Navigated::Yes)
16770 }
16771 Some(Either::Right(path)) => {
16772 let Some(workspace) = workspace else {
16773 return Ok(Navigated::No);
16774 };
16775
16776 workspace
16777 .update_in(cx, |workspace, window, cx| {
16778 workspace.open_resolved_path(path, window, cx)
16779 })?
16780 .await?;
16781 Ok(Navigated::Yes)
16782 }
16783 None => Ok(Navigated::No),
16784 }
16785 } else {
16786 let Some(workspace) = workspace else {
16787 return Ok(Navigated::No);
16788 };
16789
16790 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16791 let target_range = target_ranges.first().unwrap().clone();
16792
16793 editor.update_in(cx, |editor, window, cx| {
16794 let range = target_range.to_point(target_buffer.read(cx));
16795 let range = editor.range_for_match(&range);
16796 let range = collapse_multiline_range(range);
16797
16798 if !split
16799 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16800 {
16801 editor.go_to_singleton_buffer_range(range, window, cx);
16802 } else {
16803 let pane = workspace.read(cx).active_pane().clone();
16804 window.defer(cx, move |window, cx| {
16805 let target_editor: Entity<Self> =
16806 workspace.update(cx, |workspace, cx| {
16807 let pane = if split {
16808 workspace.adjacent_pane(window, cx)
16809 } else {
16810 workspace.active_pane().clone()
16811 };
16812
16813 workspace.open_project_item(
16814 pane,
16815 target_buffer.clone(),
16816 true,
16817 true,
16818 window,
16819 cx,
16820 )
16821 });
16822 target_editor.update(cx, |target_editor, cx| {
16823 // When selecting a definition in a different buffer, disable the nav history
16824 // to avoid creating a history entry at the previous cursor location.
16825 pane.update(cx, |pane, _| pane.disable_history());
16826 target_editor.go_to_singleton_buffer_range(range, window, cx);
16827 pane.update(cx, |pane, _| pane.enable_history());
16828 });
16829 });
16830 }
16831 Navigated::Yes
16832 })
16833 }
16834 })
16835 }
16836
16837 fn compute_target_location(
16838 &self,
16839 lsp_location: lsp::Location,
16840 server_id: LanguageServerId,
16841 window: &mut Window,
16842 cx: &mut Context<Self>,
16843 ) -> Task<anyhow::Result<Option<Location>>> {
16844 let Some(project) = self.project.clone() else {
16845 return Task::ready(Ok(None));
16846 };
16847
16848 cx.spawn_in(window, async move |editor, cx| {
16849 let location_task = editor.update(cx, |_, cx| {
16850 project.update(cx, |project, cx| {
16851 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16852 })
16853 })?;
16854 let location = Some({
16855 let target_buffer_handle = location_task.await.context("open local buffer")?;
16856 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16857 let target_start = target_buffer
16858 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16859 let target_end = target_buffer
16860 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16861 target_buffer.anchor_after(target_start)
16862 ..target_buffer.anchor_before(target_end)
16863 })?;
16864 Location {
16865 buffer: target_buffer_handle,
16866 range,
16867 }
16868 });
16869 Ok(location)
16870 })
16871 }
16872
16873 pub fn find_all_references(
16874 &mut self,
16875 _: &FindAllReferences,
16876 window: &mut Window,
16877 cx: &mut Context<Self>,
16878 ) -> Option<Task<Result<Navigated>>> {
16879 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16880 let multi_buffer = self.buffer.read(cx);
16881 let head = selection.head();
16882
16883 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16884 let head_anchor = multi_buffer_snapshot.anchor_at(
16885 head,
16886 if head < selection.tail() {
16887 Bias::Right
16888 } else {
16889 Bias::Left
16890 },
16891 );
16892
16893 match self
16894 .find_all_references_task_sources
16895 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16896 {
16897 Ok(_) => {
16898 log::info!(
16899 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16900 );
16901 return None;
16902 }
16903 Err(i) => {
16904 self.find_all_references_task_sources.insert(i, head_anchor);
16905 }
16906 }
16907
16908 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16909 let workspace = self.workspace()?;
16910 let project = workspace.read(cx).project().clone();
16911 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16912 Some(cx.spawn_in(window, async move |editor, cx| {
16913 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16914 if let Ok(i) = editor
16915 .find_all_references_task_sources
16916 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16917 {
16918 editor.find_all_references_task_sources.remove(i);
16919 }
16920 });
16921
16922 let Some(locations) = references.await? else {
16923 return anyhow::Ok(Navigated::No);
16924 };
16925 let mut locations = cx.update(|_, cx| {
16926 locations
16927 .into_iter()
16928 .map(|location| {
16929 let buffer = location.buffer.read(cx);
16930 (location.buffer, location.range.to_point(buffer))
16931 })
16932 .into_group_map()
16933 })?;
16934 if locations.is_empty() {
16935 return anyhow::Ok(Navigated::No);
16936 }
16937 for ranges in locations.values_mut() {
16938 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16939 ranges.dedup();
16940 }
16941
16942 workspace.update_in(cx, |workspace, window, cx| {
16943 let target = locations
16944 .iter()
16945 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16946 .map(|(buffer, location)| {
16947 buffer
16948 .read(cx)
16949 .text_for_range(location.clone())
16950 .collect::<String>()
16951 })
16952 .filter(|text| !text.contains('\n'))
16953 .unique()
16954 .take(3)
16955 .join(", ");
16956 let title = if target.is_empty() {
16957 "References".to_owned()
16958 } else {
16959 format!("References to {target}")
16960 };
16961 Self::open_locations_in_multibuffer(
16962 workspace,
16963 locations,
16964 title,
16965 false,
16966 MultibufferSelectionMode::First,
16967 window,
16968 cx,
16969 );
16970 Navigated::Yes
16971 })
16972 }))
16973 }
16974
16975 /// Opens a multibuffer with the given project locations in it
16976 pub fn open_locations_in_multibuffer(
16977 workspace: &mut Workspace,
16978 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16979 title: String,
16980 split: bool,
16981 multibuffer_selection_mode: MultibufferSelectionMode,
16982 window: &mut Window,
16983 cx: &mut Context<Workspace>,
16984 ) {
16985 if locations.is_empty() {
16986 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16987 return;
16988 }
16989
16990 let capability = workspace.project().read(cx).capability();
16991 let mut ranges = <Vec<Range<Anchor>>>::new();
16992
16993 // a key to find existing multibuffer editors with the same set of locations
16994 // to prevent us from opening more and more multibuffer tabs for searches and the like
16995 let mut key = (title.clone(), vec![]);
16996 let excerpt_buffer = cx.new(|cx| {
16997 let key = &mut key.1;
16998 let mut multibuffer = MultiBuffer::new(capability);
16999 for (buffer, mut ranges_for_buffer) in locations {
17000 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17001 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17002 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17003 PathKey::for_buffer(&buffer, cx),
17004 buffer.clone(),
17005 ranges_for_buffer,
17006 multibuffer_context_lines(cx),
17007 cx,
17008 );
17009 ranges.extend(new_ranges)
17010 }
17011
17012 multibuffer.with_title(title)
17013 });
17014 let existing = workspace.active_pane().update(cx, |pane, cx| {
17015 pane.items()
17016 .filter_map(|item| item.downcast::<Editor>())
17017 .find(|editor| {
17018 editor
17019 .read(cx)
17020 .lookup_key
17021 .as_ref()
17022 .and_then(|it| {
17023 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17024 })
17025 .is_some_and(|it| *it == key)
17026 })
17027 });
17028 let editor = existing.unwrap_or_else(|| {
17029 cx.new(|cx| {
17030 let mut editor = Editor::for_multibuffer(
17031 excerpt_buffer,
17032 Some(workspace.project().clone()),
17033 window,
17034 cx,
17035 );
17036 editor.lookup_key = Some(Box::new(key));
17037 editor
17038 })
17039 });
17040 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17041 MultibufferSelectionMode::First => {
17042 if let Some(first_range) = ranges.first() {
17043 editor.change_selections(
17044 SelectionEffects::no_scroll(),
17045 window,
17046 cx,
17047 |selections| {
17048 selections.clear_disjoint();
17049 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17050 },
17051 );
17052 }
17053 editor.highlight_background::<Self>(
17054 &ranges,
17055 |theme| theme.colors().editor_highlighted_line_background,
17056 cx,
17057 );
17058 }
17059 MultibufferSelectionMode::All => {
17060 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17061 selections.clear_disjoint();
17062 selections.select_anchor_ranges(ranges);
17063 });
17064 }
17065 });
17066
17067 let item = Box::new(editor);
17068 let item_id = item.item_id();
17069
17070 if split {
17071 let pane = workspace.adjacent_pane(window, cx);
17072 workspace.add_item(pane, item, None, true, true, window, cx);
17073 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17074 let (preview_item_id, preview_item_idx) =
17075 workspace.active_pane().read_with(cx, |pane, _| {
17076 (pane.preview_item_id(), pane.preview_item_idx())
17077 });
17078
17079 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17080
17081 if let Some(preview_item_id) = preview_item_id {
17082 workspace.active_pane().update(cx, |pane, cx| {
17083 pane.remove_item(preview_item_id, false, false, window, cx);
17084 });
17085 }
17086 } else {
17087 workspace.add_item_to_active_pane(item, None, true, window, cx);
17088 }
17089 workspace.active_pane().update(cx, |pane, cx| {
17090 pane.set_preview_item_id(Some(item_id), cx);
17091 });
17092 }
17093
17094 pub fn rename(
17095 &mut self,
17096 _: &Rename,
17097 window: &mut Window,
17098 cx: &mut Context<Self>,
17099 ) -> Option<Task<Result<()>>> {
17100 use language::ToOffset as _;
17101
17102 let provider = self.semantics_provider.clone()?;
17103 let selection = self.selections.newest_anchor().clone();
17104 let (cursor_buffer, cursor_buffer_position) = self
17105 .buffer
17106 .read(cx)
17107 .text_anchor_for_position(selection.head(), cx)?;
17108 let (tail_buffer, cursor_buffer_position_end) = self
17109 .buffer
17110 .read(cx)
17111 .text_anchor_for_position(selection.tail(), cx)?;
17112 if tail_buffer != cursor_buffer {
17113 return None;
17114 }
17115
17116 let snapshot = cursor_buffer.read(cx).snapshot();
17117 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17118 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17119 let prepare_rename = provider
17120 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17121 .unwrap_or_else(|| Task::ready(Ok(None)));
17122 drop(snapshot);
17123
17124 Some(cx.spawn_in(window, async move |this, cx| {
17125 let rename_range = if let Some(range) = prepare_rename.await? {
17126 Some(range)
17127 } else {
17128 this.update(cx, |this, cx| {
17129 let buffer = this.buffer.read(cx).snapshot(cx);
17130 let mut buffer_highlights = this
17131 .document_highlights_for_position(selection.head(), &buffer)
17132 .filter(|highlight| {
17133 highlight.start.excerpt_id == selection.head().excerpt_id
17134 && highlight.end.excerpt_id == selection.head().excerpt_id
17135 });
17136 buffer_highlights
17137 .next()
17138 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17139 })?
17140 };
17141 if let Some(rename_range) = rename_range {
17142 this.update_in(cx, |this, window, cx| {
17143 let snapshot = cursor_buffer.read(cx).snapshot();
17144 let rename_buffer_range = rename_range.to_offset(&snapshot);
17145 let cursor_offset_in_rename_range =
17146 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17147 let cursor_offset_in_rename_range_end =
17148 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17149
17150 this.take_rename(false, window, cx);
17151 let buffer = this.buffer.read(cx).read(cx);
17152 let cursor_offset = selection.head().to_offset(&buffer);
17153 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17154 let rename_end = rename_start + rename_buffer_range.len();
17155 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17156 let mut old_highlight_id = None;
17157 let old_name: Arc<str> = buffer
17158 .chunks(rename_start..rename_end, true)
17159 .map(|chunk| {
17160 if old_highlight_id.is_none() {
17161 old_highlight_id = chunk.syntax_highlight_id;
17162 }
17163 chunk.text
17164 })
17165 .collect::<String>()
17166 .into();
17167
17168 drop(buffer);
17169
17170 // Position the selection in the rename editor so that it matches the current selection.
17171 this.show_local_selections = false;
17172 let rename_editor = cx.new(|cx| {
17173 let mut editor = Editor::single_line(window, cx);
17174 editor.buffer.update(cx, |buffer, cx| {
17175 buffer.edit([(0..0, old_name.clone())], None, cx)
17176 });
17177 let rename_selection_range = match cursor_offset_in_rename_range
17178 .cmp(&cursor_offset_in_rename_range_end)
17179 {
17180 Ordering::Equal => {
17181 editor.select_all(&SelectAll, window, cx);
17182 return editor;
17183 }
17184 Ordering::Less => {
17185 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17186 }
17187 Ordering::Greater => {
17188 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17189 }
17190 };
17191 if rename_selection_range.end > old_name.len() {
17192 editor.select_all(&SelectAll, window, cx);
17193 } else {
17194 editor.change_selections(Default::default(), window, cx, |s| {
17195 s.select_ranges([rename_selection_range]);
17196 });
17197 }
17198 editor
17199 });
17200 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17201 if e == &EditorEvent::Focused {
17202 cx.emit(EditorEvent::FocusedIn)
17203 }
17204 })
17205 .detach();
17206
17207 let write_highlights =
17208 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17209 let read_highlights =
17210 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17211 let ranges = write_highlights
17212 .iter()
17213 .flat_map(|(_, ranges)| ranges.iter())
17214 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17215 .cloned()
17216 .collect();
17217
17218 this.highlight_text::<Rename>(
17219 ranges,
17220 HighlightStyle {
17221 fade_out: Some(0.6),
17222 ..Default::default()
17223 },
17224 cx,
17225 );
17226 let rename_focus_handle = rename_editor.focus_handle(cx);
17227 window.focus(&rename_focus_handle);
17228 let block_id = this.insert_blocks(
17229 [BlockProperties {
17230 style: BlockStyle::Flex,
17231 placement: BlockPlacement::Below(range.start),
17232 height: Some(1),
17233 render: Arc::new({
17234 let rename_editor = rename_editor.clone();
17235 move |cx: &mut BlockContext| {
17236 let mut text_style = cx.editor_style.text.clone();
17237 if let Some(highlight_style) = old_highlight_id
17238 .and_then(|h| h.style(&cx.editor_style.syntax))
17239 {
17240 text_style = text_style.highlight(highlight_style);
17241 }
17242 div()
17243 .block_mouse_except_scroll()
17244 .pl(cx.anchor_x)
17245 .child(EditorElement::new(
17246 &rename_editor,
17247 EditorStyle {
17248 background: cx.theme().system().transparent,
17249 local_player: cx.editor_style.local_player,
17250 text: text_style,
17251 scrollbar_width: cx.editor_style.scrollbar_width,
17252 syntax: cx.editor_style.syntax.clone(),
17253 status: cx.editor_style.status.clone(),
17254 inlay_hints_style: HighlightStyle {
17255 font_weight: Some(FontWeight::BOLD),
17256 ..make_inlay_hints_style(cx.app)
17257 },
17258 edit_prediction_styles: make_suggestion_styles(
17259 cx.app,
17260 ),
17261 ..EditorStyle::default()
17262 },
17263 ))
17264 .into_any_element()
17265 }
17266 }),
17267 priority: 0,
17268 }],
17269 Some(Autoscroll::fit()),
17270 cx,
17271 )[0];
17272 this.pending_rename = Some(RenameState {
17273 range,
17274 old_name,
17275 editor: rename_editor,
17276 block_id,
17277 });
17278 })?;
17279 }
17280
17281 Ok(())
17282 }))
17283 }
17284
17285 pub fn confirm_rename(
17286 &mut self,
17287 _: &ConfirmRename,
17288 window: &mut Window,
17289 cx: &mut Context<Self>,
17290 ) -> Option<Task<Result<()>>> {
17291 let rename = self.take_rename(false, window, cx)?;
17292 let workspace = self.workspace()?.downgrade();
17293 let (buffer, start) = self
17294 .buffer
17295 .read(cx)
17296 .text_anchor_for_position(rename.range.start, cx)?;
17297 let (end_buffer, _) = self
17298 .buffer
17299 .read(cx)
17300 .text_anchor_for_position(rename.range.end, cx)?;
17301 if buffer != end_buffer {
17302 return None;
17303 }
17304
17305 let old_name = rename.old_name;
17306 let new_name = rename.editor.read(cx).text(cx);
17307
17308 let rename = self.semantics_provider.as_ref()?.perform_rename(
17309 &buffer,
17310 start,
17311 new_name.clone(),
17312 cx,
17313 )?;
17314
17315 Some(cx.spawn_in(window, async move |editor, cx| {
17316 let project_transaction = rename.await?;
17317 Self::open_project_transaction(
17318 &editor,
17319 workspace,
17320 project_transaction,
17321 format!("Rename: {} → {}", old_name, new_name),
17322 cx,
17323 )
17324 .await?;
17325
17326 editor.update(cx, |editor, cx| {
17327 editor.refresh_document_highlights(cx);
17328 })?;
17329 Ok(())
17330 }))
17331 }
17332
17333 fn take_rename(
17334 &mut self,
17335 moving_cursor: bool,
17336 window: &mut Window,
17337 cx: &mut Context<Self>,
17338 ) -> Option<RenameState> {
17339 let rename = self.pending_rename.take()?;
17340 if rename.editor.focus_handle(cx).is_focused(window) {
17341 window.focus(&self.focus_handle);
17342 }
17343
17344 self.remove_blocks(
17345 [rename.block_id].into_iter().collect(),
17346 Some(Autoscroll::fit()),
17347 cx,
17348 );
17349 self.clear_highlights::<Rename>(cx);
17350 self.show_local_selections = true;
17351
17352 if moving_cursor {
17353 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17354 editor
17355 .selections
17356 .newest::<usize>(&editor.display_snapshot(cx))
17357 .head()
17358 });
17359
17360 // Update the selection to match the position of the selection inside
17361 // the rename editor.
17362 let snapshot = self.buffer.read(cx).read(cx);
17363 let rename_range = rename.range.to_offset(&snapshot);
17364 let cursor_in_editor = snapshot
17365 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17366 .min(rename_range.end);
17367 drop(snapshot);
17368
17369 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17370 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17371 });
17372 } else {
17373 self.refresh_document_highlights(cx);
17374 }
17375
17376 Some(rename)
17377 }
17378
17379 pub fn pending_rename(&self) -> Option<&RenameState> {
17380 self.pending_rename.as_ref()
17381 }
17382
17383 fn format(
17384 &mut self,
17385 _: &Format,
17386 window: &mut Window,
17387 cx: &mut Context<Self>,
17388 ) -> Option<Task<Result<()>>> {
17389 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17390
17391 let project = match &self.project {
17392 Some(project) => project.clone(),
17393 None => return None,
17394 };
17395
17396 Some(self.perform_format(
17397 project,
17398 FormatTrigger::Manual,
17399 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17400 window,
17401 cx,
17402 ))
17403 }
17404
17405 fn format_selections(
17406 &mut self,
17407 _: &FormatSelections,
17408 window: &mut Window,
17409 cx: &mut Context<Self>,
17410 ) -> Option<Task<Result<()>>> {
17411 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17412
17413 let project = match &self.project {
17414 Some(project) => project.clone(),
17415 None => return None,
17416 };
17417
17418 let ranges = self
17419 .selections
17420 .all_adjusted(&self.display_snapshot(cx))
17421 .into_iter()
17422 .map(|selection| selection.range())
17423 .collect_vec();
17424
17425 Some(self.perform_format(
17426 project,
17427 FormatTrigger::Manual,
17428 FormatTarget::Ranges(ranges),
17429 window,
17430 cx,
17431 ))
17432 }
17433
17434 fn perform_format(
17435 &mut self,
17436 project: Entity<Project>,
17437 trigger: FormatTrigger,
17438 target: FormatTarget,
17439 window: &mut Window,
17440 cx: &mut Context<Self>,
17441 ) -> Task<Result<()>> {
17442 let buffer = self.buffer.clone();
17443 let (buffers, target) = match target {
17444 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17445 FormatTarget::Ranges(selection_ranges) => {
17446 let multi_buffer = buffer.read(cx);
17447 let snapshot = multi_buffer.read(cx);
17448 let mut buffers = HashSet::default();
17449 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17450 BTreeMap::new();
17451 for selection_range in selection_ranges {
17452 for (buffer, buffer_range, _) in
17453 snapshot.range_to_buffer_ranges(selection_range)
17454 {
17455 let buffer_id = buffer.remote_id();
17456 let start = buffer.anchor_before(buffer_range.start);
17457 let end = buffer.anchor_after(buffer_range.end);
17458 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17459 buffer_id_to_ranges
17460 .entry(buffer_id)
17461 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17462 .or_insert_with(|| vec![start..end]);
17463 }
17464 }
17465 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17466 }
17467 };
17468
17469 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17470 let selections_prev = transaction_id_prev
17471 .and_then(|transaction_id_prev| {
17472 // default to selections as they were after the last edit, if we have them,
17473 // instead of how they are now.
17474 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17475 // will take you back to where you made the last edit, instead of staying where you scrolled
17476 self.selection_history
17477 .transaction(transaction_id_prev)
17478 .map(|t| t.0.clone())
17479 })
17480 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17481
17482 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17483 let format = project.update(cx, |project, cx| {
17484 project.format(buffers, target, true, trigger, cx)
17485 });
17486
17487 cx.spawn_in(window, async move |editor, cx| {
17488 let transaction = futures::select_biased! {
17489 transaction = format.log_err().fuse() => transaction,
17490 () = timeout => {
17491 log::warn!("timed out waiting for formatting");
17492 None
17493 }
17494 };
17495
17496 buffer
17497 .update(cx, |buffer, cx| {
17498 if let Some(transaction) = transaction
17499 && !buffer.is_singleton()
17500 {
17501 buffer.push_transaction(&transaction.0, cx);
17502 }
17503 cx.notify();
17504 })
17505 .ok();
17506
17507 if let Some(transaction_id_now) =
17508 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17509 {
17510 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17511 if has_new_transaction {
17512 _ = editor.update(cx, |editor, _| {
17513 editor
17514 .selection_history
17515 .insert_transaction(transaction_id_now, selections_prev);
17516 });
17517 }
17518 }
17519
17520 Ok(())
17521 })
17522 }
17523
17524 fn organize_imports(
17525 &mut self,
17526 _: &OrganizeImports,
17527 window: &mut Window,
17528 cx: &mut Context<Self>,
17529 ) -> Option<Task<Result<()>>> {
17530 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17531 let project = match &self.project {
17532 Some(project) => project.clone(),
17533 None => return None,
17534 };
17535 Some(self.perform_code_action_kind(
17536 project,
17537 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17538 window,
17539 cx,
17540 ))
17541 }
17542
17543 fn perform_code_action_kind(
17544 &mut self,
17545 project: Entity<Project>,
17546 kind: CodeActionKind,
17547 window: &mut Window,
17548 cx: &mut Context<Self>,
17549 ) -> Task<Result<()>> {
17550 let buffer = self.buffer.clone();
17551 let buffers = buffer.read(cx).all_buffers();
17552 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17553 let apply_action = project.update(cx, |project, cx| {
17554 project.apply_code_action_kind(buffers, kind, true, cx)
17555 });
17556 cx.spawn_in(window, async move |_, cx| {
17557 let transaction = futures::select_biased! {
17558 () = timeout => {
17559 log::warn!("timed out waiting for executing code action");
17560 None
17561 }
17562 transaction = apply_action.log_err().fuse() => transaction,
17563 };
17564 buffer
17565 .update(cx, |buffer, cx| {
17566 // check if we need this
17567 if let Some(transaction) = transaction
17568 && !buffer.is_singleton()
17569 {
17570 buffer.push_transaction(&transaction.0, cx);
17571 }
17572 cx.notify();
17573 })
17574 .ok();
17575 Ok(())
17576 })
17577 }
17578
17579 pub fn restart_language_server(
17580 &mut self,
17581 _: &RestartLanguageServer,
17582 _: &mut Window,
17583 cx: &mut Context<Self>,
17584 ) {
17585 if let Some(project) = self.project.clone() {
17586 self.buffer.update(cx, |multi_buffer, cx| {
17587 project.update(cx, |project, cx| {
17588 project.restart_language_servers_for_buffers(
17589 multi_buffer.all_buffers().into_iter().collect(),
17590 HashSet::default(),
17591 cx,
17592 );
17593 });
17594 })
17595 }
17596 }
17597
17598 pub fn stop_language_server(
17599 &mut self,
17600 _: &StopLanguageServer,
17601 _: &mut Window,
17602 cx: &mut Context<Self>,
17603 ) {
17604 if let Some(project) = self.project.clone() {
17605 self.buffer.update(cx, |multi_buffer, cx| {
17606 project.update(cx, |project, cx| {
17607 project.stop_language_servers_for_buffers(
17608 multi_buffer.all_buffers().into_iter().collect(),
17609 HashSet::default(),
17610 cx,
17611 );
17612 cx.emit(project::Event::RefreshInlayHints);
17613 });
17614 });
17615 }
17616 }
17617
17618 fn cancel_language_server_work(
17619 workspace: &mut Workspace,
17620 _: &actions::CancelLanguageServerWork,
17621 _: &mut Window,
17622 cx: &mut Context<Workspace>,
17623 ) {
17624 let project = workspace.project();
17625 let buffers = workspace
17626 .active_item(cx)
17627 .and_then(|item| item.act_as::<Editor>(cx))
17628 .map_or(HashSet::default(), |editor| {
17629 editor.read(cx).buffer.read(cx).all_buffers()
17630 });
17631 project.update(cx, |project, cx| {
17632 project.cancel_language_server_work_for_buffers(buffers, cx);
17633 });
17634 }
17635
17636 fn show_character_palette(
17637 &mut self,
17638 _: &ShowCharacterPalette,
17639 window: &mut Window,
17640 _: &mut Context<Self>,
17641 ) {
17642 window.show_character_palette();
17643 }
17644
17645 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17646 if !self.diagnostics_enabled() {
17647 return;
17648 }
17649
17650 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17651 let buffer = self.buffer.read(cx).snapshot(cx);
17652 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17653 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17654 let is_valid = buffer
17655 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17656 .any(|entry| {
17657 entry.diagnostic.is_primary
17658 && !entry.range.is_empty()
17659 && entry.range.start == primary_range_start
17660 && entry.diagnostic.message == active_diagnostics.active_message
17661 });
17662
17663 if !is_valid {
17664 self.dismiss_diagnostics(cx);
17665 }
17666 }
17667 }
17668
17669 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17670 match &self.active_diagnostics {
17671 ActiveDiagnostic::Group(group) => Some(group),
17672 _ => None,
17673 }
17674 }
17675
17676 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17677 if !self.diagnostics_enabled() {
17678 return;
17679 }
17680 self.dismiss_diagnostics(cx);
17681 self.active_diagnostics = ActiveDiagnostic::All;
17682 }
17683
17684 fn activate_diagnostics(
17685 &mut self,
17686 buffer_id: BufferId,
17687 diagnostic: DiagnosticEntryRef<'_, usize>,
17688 window: &mut Window,
17689 cx: &mut Context<Self>,
17690 ) {
17691 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17692 return;
17693 }
17694 self.dismiss_diagnostics(cx);
17695 let snapshot = self.snapshot(window, cx);
17696 let buffer = self.buffer.read(cx).snapshot(cx);
17697 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17698 return;
17699 };
17700
17701 let diagnostic_group = buffer
17702 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17703 .collect::<Vec<_>>();
17704
17705 let blocks =
17706 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17707
17708 let blocks = self.display_map.update(cx, |display_map, cx| {
17709 display_map.insert_blocks(blocks, cx).into_iter().collect()
17710 });
17711 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17712 active_range: buffer.anchor_before(diagnostic.range.start)
17713 ..buffer.anchor_after(diagnostic.range.end),
17714 active_message: diagnostic.diagnostic.message.clone(),
17715 group_id: diagnostic.diagnostic.group_id,
17716 blocks,
17717 });
17718 cx.notify();
17719 }
17720
17721 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17722 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17723 return;
17724 };
17725
17726 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17727 if let ActiveDiagnostic::Group(group) = prev {
17728 self.display_map.update(cx, |display_map, cx| {
17729 display_map.remove_blocks(group.blocks, cx);
17730 });
17731 cx.notify();
17732 }
17733 }
17734
17735 /// Disable inline diagnostics rendering for this editor.
17736 pub fn disable_inline_diagnostics(&mut self) {
17737 self.inline_diagnostics_enabled = false;
17738 self.inline_diagnostics_update = Task::ready(());
17739 self.inline_diagnostics.clear();
17740 }
17741
17742 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17743 self.diagnostics_enabled = false;
17744 self.dismiss_diagnostics(cx);
17745 self.inline_diagnostics_update = Task::ready(());
17746 self.inline_diagnostics.clear();
17747 }
17748
17749 pub fn disable_word_completions(&mut self) {
17750 self.word_completions_enabled = false;
17751 }
17752
17753 pub fn diagnostics_enabled(&self) -> bool {
17754 self.diagnostics_enabled && self.mode.is_full()
17755 }
17756
17757 pub fn inline_diagnostics_enabled(&self) -> bool {
17758 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17759 }
17760
17761 pub fn show_inline_diagnostics(&self) -> bool {
17762 self.show_inline_diagnostics
17763 }
17764
17765 pub fn toggle_inline_diagnostics(
17766 &mut self,
17767 _: &ToggleInlineDiagnostics,
17768 window: &mut Window,
17769 cx: &mut Context<Editor>,
17770 ) {
17771 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17772 self.refresh_inline_diagnostics(false, window, cx);
17773 }
17774
17775 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17776 self.diagnostics_max_severity = severity;
17777 self.display_map.update(cx, |display_map, _| {
17778 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17779 });
17780 }
17781
17782 pub fn toggle_diagnostics(
17783 &mut self,
17784 _: &ToggleDiagnostics,
17785 window: &mut Window,
17786 cx: &mut Context<Editor>,
17787 ) {
17788 if !self.diagnostics_enabled() {
17789 return;
17790 }
17791
17792 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17793 EditorSettings::get_global(cx)
17794 .diagnostics_max_severity
17795 .filter(|severity| severity != &DiagnosticSeverity::Off)
17796 .unwrap_or(DiagnosticSeverity::Hint)
17797 } else {
17798 DiagnosticSeverity::Off
17799 };
17800 self.set_max_diagnostics_severity(new_severity, cx);
17801 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17802 self.active_diagnostics = ActiveDiagnostic::None;
17803 self.inline_diagnostics_update = Task::ready(());
17804 self.inline_diagnostics.clear();
17805 } else {
17806 self.refresh_inline_diagnostics(false, window, cx);
17807 }
17808
17809 cx.notify();
17810 }
17811
17812 pub fn toggle_minimap(
17813 &mut self,
17814 _: &ToggleMinimap,
17815 window: &mut Window,
17816 cx: &mut Context<Editor>,
17817 ) {
17818 if self.supports_minimap(cx) {
17819 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17820 }
17821 }
17822
17823 fn refresh_inline_diagnostics(
17824 &mut self,
17825 debounce: bool,
17826 window: &mut Window,
17827 cx: &mut Context<Self>,
17828 ) {
17829 let max_severity = ProjectSettings::get_global(cx)
17830 .diagnostics
17831 .inline
17832 .max_severity
17833 .unwrap_or(self.diagnostics_max_severity);
17834
17835 if !self.inline_diagnostics_enabled()
17836 || !self.show_inline_diagnostics
17837 || max_severity == DiagnosticSeverity::Off
17838 {
17839 self.inline_diagnostics_update = Task::ready(());
17840 self.inline_diagnostics.clear();
17841 return;
17842 }
17843
17844 let debounce_ms = ProjectSettings::get_global(cx)
17845 .diagnostics
17846 .inline
17847 .update_debounce_ms;
17848 let debounce = if debounce && debounce_ms > 0 {
17849 Some(Duration::from_millis(debounce_ms))
17850 } else {
17851 None
17852 };
17853 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17854 if let Some(debounce) = debounce {
17855 cx.background_executor().timer(debounce).await;
17856 }
17857 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17858 editor
17859 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17860 .ok()
17861 }) else {
17862 return;
17863 };
17864
17865 let new_inline_diagnostics = cx
17866 .background_spawn(async move {
17867 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17868 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17869 let message = diagnostic_entry
17870 .diagnostic
17871 .message
17872 .split_once('\n')
17873 .map(|(line, _)| line)
17874 .map(SharedString::new)
17875 .unwrap_or_else(|| {
17876 SharedString::new(&*diagnostic_entry.diagnostic.message)
17877 });
17878 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17879 let (Ok(i) | Err(i)) = inline_diagnostics
17880 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17881 inline_diagnostics.insert(
17882 i,
17883 (
17884 start_anchor,
17885 InlineDiagnostic {
17886 message,
17887 group_id: diagnostic_entry.diagnostic.group_id,
17888 start: diagnostic_entry.range.start.to_point(&snapshot),
17889 is_primary: diagnostic_entry.diagnostic.is_primary,
17890 severity: diagnostic_entry.diagnostic.severity,
17891 },
17892 ),
17893 );
17894 }
17895 inline_diagnostics
17896 })
17897 .await;
17898
17899 editor
17900 .update(cx, |editor, cx| {
17901 editor.inline_diagnostics = new_inline_diagnostics;
17902 cx.notify();
17903 })
17904 .ok();
17905 });
17906 }
17907
17908 fn pull_diagnostics(
17909 &mut self,
17910 buffer_id: Option<BufferId>,
17911 window: &Window,
17912 cx: &mut Context<Self>,
17913 ) -> Option<()> {
17914 if self.ignore_lsp_data() {
17915 return None;
17916 }
17917 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17918 .diagnostics
17919 .lsp_pull_diagnostics;
17920 if !pull_diagnostics_settings.enabled {
17921 return None;
17922 }
17923 let project = self.project()?.downgrade();
17924 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17925 let mut buffers = self.buffer.read(cx).all_buffers();
17926 buffers.retain(|buffer| {
17927 let buffer_id_to_retain = buffer.read(cx).remote_id();
17928 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17929 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17930 });
17931 if buffers.is_empty() {
17932 self.pull_diagnostics_task = Task::ready(());
17933 return None;
17934 }
17935
17936 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17937 cx.background_executor().timer(debounce).await;
17938
17939 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17940 buffers
17941 .into_iter()
17942 .filter_map(|buffer| {
17943 project
17944 .update(cx, |project, cx| {
17945 project.lsp_store().update(cx, |lsp_store, cx| {
17946 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17947 })
17948 })
17949 .ok()
17950 })
17951 .collect::<FuturesUnordered<_>>()
17952 }) else {
17953 return;
17954 };
17955
17956 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17957 match pull_task {
17958 Ok(()) => {
17959 if editor
17960 .update_in(cx, |editor, window, cx| {
17961 editor.update_diagnostics_state(window, cx);
17962 })
17963 .is_err()
17964 {
17965 return;
17966 }
17967 }
17968 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17969 }
17970 }
17971 });
17972
17973 Some(())
17974 }
17975
17976 pub fn set_selections_from_remote(
17977 &mut self,
17978 selections: Vec<Selection<Anchor>>,
17979 pending_selection: Option<Selection<Anchor>>,
17980 window: &mut Window,
17981 cx: &mut Context<Self>,
17982 ) {
17983 let old_cursor_position = self.selections.newest_anchor().head();
17984 self.selections.change_with(cx, |s| {
17985 s.select_anchors(selections);
17986 if let Some(pending_selection) = pending_selection {
17987 s.set_pending(pending_selection, SelectMode::Character);
17988 } else {
17989 s.clear_pending();
17990 }
17991 });
17992 self.selections_did_change(
17993 false,
17994 &old_cursor_position,
17995 SelectionEffects::default(),
17996 window,
17997 cx,
17998 );
17999 }
18000
18001 pub fn transact(
18002 &mut self,
18003 window: &mut Window,
18004 cx: &mut Context<Self>,
18005 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18006 ) -> Option<TransactionId> {
18007 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18008 this.start_transaction_at(Instant::now(), window, cx);
18009 update(this, window, cx);
18010 this.end_transaction_at(Instant::now(), cx)
18011 })
18012 }
18013
18014 pub fn start_transaction_at(
18015 &mut self,
18016 now: Instant,
18017 window: &mut Window,
18018 cx: &mut Context<Self>,
18019 ) -> Option<TransactionId> {
18020 self.end_selection(window, cx);
18021 if let Some(tx_id) = self
18022 .buffer
18023 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18024 {
18025 self.selection_history
18026 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18027 cx.emit(EditorEvent::TransactionBegun {
18028 transaction_id: tx_id,
18029 });
18030 Some(tx_id)
18031 } else {
18032 None
18033 }
18034 }
18035
18036 pub fn end_transaction_at(
18037 &mut self,
18038 now: Instant,
18039 cx: &mut Context<Self>,
18040 ) -> Option<TransactionId> {
18041 if let Some(transaction_id) = self
18042 .buffer
18043 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18044 {
18045 if let Some((_, end_selections)) =
18046 self.selection_history.transaction_mut(transaction_id)
18047 {
18048 *end_selections = Some(self.selections.disjoint_anchors_arc());
18049 } else {
18050 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18051 }
18052
18053 cx.emit(EditorEvent::Edited { transaction_id });
18054 Some(transaction_id)
18055 } else {
18056 None
18057 }
18058 }
18059
18060 pub fn modify_transaction_selection_history(
18061 &mut self,
18062 transaction_id: TransactionId,
18063 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18064 ) -> bool {
18065 self.selection_history
18066 .transaction_mut(transaction_id)
18067 .map(modify)
18068 .is_some()
18069 }
18070
18071 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18072 if self.selection_mark_mode {
18073 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18074 s.move_with(|_, sel| {
18075 sel.collapse_to(sel.head(), SelectionGoal::None);
18076 });
18077 })
18078 }
18079 self.selection_mark_mode = true;
18080 cx.notify();
18081 }
18082
18083 pub fn swap_selection_ends(
18084 &mut self,
18085 _: &actions::SwapSelectionEnds,
18086 window: &mut Window,
18087 cx: &mut Context<Self>,
18088 ) {
18089 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18090 s.move_with(|_, sel| {
18091 if sel.start != sel.end {
18092 sel.reversed = !sel.reversed
18093 }
18094 });
18095 });
18096 self.request_autoscroll(Autoscroll::newest(), cx);
18097 cx.notify();
18098 }
18099
18100 pub fn toggle_focus(
18101 workspace: &mut Workspace,
18102 _: &actions::ToggleFocus,
18103 window: &mut Window,
18104 cx: &mut Context<Workspace>,
18105 ) {
18106 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18107 return;
18108 };
18109 workspace.activate_item(&item, true, true, window, cx);
18110 }
18111
18112 pub fn toggle_fold(
18113 &mut self,
18114 _: &actions::ToggleFold,
18115 window: &mut Window,
18116 cx: &mut Context<Self>,
18117 ) {
18118 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18119 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18120 let selection = self.selections.newest::<Point>(&display_map);
18121
18122 let range = if selection.is_empty() {
18123 let point = selection.head().to_display_point(&display_map);
18124 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18125 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18126 .to_point(&display_map);
18127 start..end
18128 } else {
18129 selection.range()
18130 };
18131 if display_map.folds_in_range(range).next().is_some() {
18132 self.unfold_lines(&Default::default(), window, cx)
18133 } else {
18134 self.fold(&Default::default(), window, cx)
18135 }
18136 } else {
18137 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18138 let buffer_ids: HashSet<_> = self
18139 .selections
18140 .disjoint_anchor_ranges()
18141 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18142 .collect();
18143
18144 let should_unfold = buffer_ids
18145 .iter()
18146 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18147
18148 for buffer_id in buffer_ids {
18149 if should_unfold {
18150 self.unfold_buffer(buffer_id, cx);
18151 } else {
18152 self.fold_buffer(buffer_id, cx);
18153 }
18154 }
18155 }
18156 }
18157
18158 pub fn toggle_fold_recursive(
18159 &mut self,
18160 _: &actions::ToggleFoldRecursive,
18161 window: &mut Window,
18162 cx: &mut Context<Self>,
18163 ) {
18164 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18165
18166 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18167 let range = if selection.is_empty() {
18168 let point = selection.head().to_display_point(&display_map);
18169 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18170 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18171 .to_point(&display_map);
18172 start..end
18173 } else {
18174 selection.range()
18175 };
18176 if display_map.folds_in_range(range).next().is_some() {
18177 self.unfold_recursive(&Default::default(), window, cx)
18178 } else {
18179 self.fold_recursive(&Default::default(), window, cx)
18180 }
18181 }
18182
18183 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18184 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18185 let mut to_fold = Vec::new();
18186 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18187 let selections = self.selections.all_adjusted(&display_map);
18188
18189 for selection in selections {
18190 let range = selection.range().sorted();
18191 let buffer_start_row = range.start.row;
18192
18193 if range.start.row != range.end.row {
18194 let mut found = false;
18195 let mut row = range.start.row;
18196 while row <= range.end.row {
18197 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18198 {
18199 found = true;
18200 row = crease.range().end.row + 1;
18201 to_fold.push(crease);
18202 } else {
18203 row += 1
18204 }
18205 }
18206 if found {
18207 continue;
18208 }
18209 }
18210
18211 for row in (0..=range.start.row).rev() {
18212 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18213 && crease.range().end.row >= buffer_start_row
18214 {
18215 to_fold.push(crease);
18216 if row <= range.start.row {
18217 break;
18218 }
18219 }
18220 }
18221 }
18222
18223 self.fold_creases(to_fold, true, window, cx);
18224 } else {
18225 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18226 let buffer_ids = self
18227 .selections
18228 .disjoint_anchor_ranges()
18229 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18230 .collect::<HashSet<_>>();
18231 for buffer_id in buffer_ids {
18232 self.fold_buffer(buffer_id, cx);
18233 }
18234 }
18235 }
18236
18237 pub fn toggle_fold_all(
18238 &mut self,
18239 _: &actions::ToggleFoldAll,
18240 window: &mut Window,
18241 cx: &mut Context<Self>,
18242 ) {
18243 if self.buffer.read(cx).is_singleton() {
18244 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18245 let has_folds = display_map
18246 .folds_in_range(0..display_map.buffer_snapshot().len())
18247 .next()
18248 .is_some();
18249
18250 if has_folds {
18251 self.unfold_all(&actions::UnfoldAll, window, cx);
18252 } else {
18253 self.fold_all(&actions::FoldAll, window, cx);
18254 }
18255 } else {
18256 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18257 let should_unfold = buffer_ids
18258 .iter()
18259 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18260
18261 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18262 editor
18263 .update_in(cx, |editor, _, cx| {
18264 for buffer_id in buffer_ids {
18265 if should_unfold {
18266 editor.unfold_buffer(buffer_id, cx);
18267 } else {
18268 editor.fold_buffer(buffer_id, cx);
18269 }
18270 }
18271 })
18272 .ok();
18273 });
18274 }
18275 }
18276
18277 fn fold_at_level(
18278 &mut self,
18279 fold_at: &FoldAtLevel,
18280 window: &mut Window,
18281 cx: &mut Context<Self>,
18282 ) {
18283 if !self.buffer.read(cx).is_singleton() {
18284 return;
18285 }
18286
18287 let fold_at_level = fold_at.0;
18288 let snapshot = self.buffer.read(cx).snapshot(cx);
18289 let mut to_fold = Vec::new();
18290 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18291
18292 let row_ranges_to_keep: Vec<Range<u32>> = self
18293 .selections
18294 .all::<Point>(&self.display_snapshot(cx))
18295 .into_iter()
18296 .map(|sel| sel.start.row..sel.end.row)
18297 .collect();
18298
18299 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18300 while start_row < end_row {
18301 match self
18302 .snapshot(window, cx)
18303 .crease_for_buffer_row(MultiBufferRow(start_row))
18304 {
18305 Some(crease) => {
18306 let nested_start_row = crease.range().start.row + 1;
18307 let nested_end_row = crease.range().end.row;
18308
18309 if current_level < fold_at_level {
18310 stack.push((nested_start_row, nested_end_row, current_level + 1));
18311 } else if current_level == fold_at_level {
18312 // Fold iff there is no selection completely contained within the fold region
18313 if !row_ranges_to_keep.iter().any(|selection| {
18314 selection.end >= nested_start_row
18315 && selection.start <= nested_end_row
18316 }) {
18317 to_fold.push(crease);
18318 }
18319 }
18320
18321 start_row = nested_end_row + 1;
18322 }
18323 None => start_row += 1,
18324 }
18325 }
18326 }
18327
18328 self.fold_creases(to_fold, true, window, cx);
18329 }
18330
18331 pub fn fold_at_level_1(
18332 &mut self,
18333 _: &actions::FoldAtLevel1,
18334 window: &mut Window,
18335 cx: &mut Context<Self>,
18336 ) {
18337 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18338 }
18339
18340 pub fn fold_at_level_2(
18341 &mut self,
18342 _: &actions::FoldAtLevel2,
18343 window: &mut Window,
18344 cx: &mut Context<Self>,
18345 ) {
18346 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18347 }
18348
18349 pub fn fold_at_level_3(
18350 &mut self,
18351 _: &actions::FoldAtLevel3,
18352 window: &mut Window,
18353 cx: &mut Context<Self>,
18354 ) {
18355 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18356 }
18357
18358 pub fn fold_at_level_4(
18359 &mut self,
18360 _: &actions::FoldAtLevel4,
18361 window: &mut Window,
18362 cx: &mut Context<Self>,
18363 ) {
18364 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18365 }
18366
18367 pub fn fold_at_level_5(
18368 &mut self,
18369 _: &actions::FoldAtLevel5,
18370 window: &mut Window,
18371 cx: &mut Context<Self>,
18372 ) {
18373 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18374 }
18375
18376 pub fn fold_at_level_6(
18377 &mut self,
18378 _: &actions::FoldAtLevel6,
18379 window: &mut Window,
18380 cx: &mut Context<Self>,
18381 ) {
18382 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18383 }
18384
18385 pub fn fold_at_level_7(
18386 &mut self,
18387 _: &actions::FoldAtLevel7,
18388 window: &mut Window,
18389 cx: &mut Context<Self>,
18390 ) {
18391 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18392 }
18393
18394 pub fn fold_at_level_8(
18395 &mut self,
18396 _: &actions::FoldAtLevel8,
18397 window: &mut Window,
18398 cx: &mut Context<Self>,
18399 ) {
18400 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18401 }
18402
18403 pub fn fold_at_level_9(
18404 &mut self,
18405 _: &actions::FoldAtLevel9,
18406 window: &mut Window,
18407 cx: &mut Context<Self>,
18408 ) {
18409 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18410 }
18411
18412 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18413 if self.buffer.read(cx).is_singleton() {
18414 let mut fold_ranges = Vec::new();
18415 let snapshot = self.buffer.read(cx).snapshot(cx);
18416
18417 for row in 0..snapshot.max_row().0 {
18418 if let Some(foldable_range) = self
18419 .snapshot(window, cx)
18420 .crease_for_buffer_row(MultiBufferRow(row))
18421 {
18422 fold_ranges.push(foldable_range);
18423 }
18424 }
18425
18426 self.fold_creases(fold_ranges, true, window, cx);
18427 } else {
18428 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18429 editor
18430 .update_in(cx, |editor, _, cx| {
18431 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18432 editor.fold_buffer(buffer_id, cx);
18433 }
18434 })
18435 .ok();
18436 });
18437 }
18438 }
18439
18440 pub fn fold_function_bodies(
18441 &mut self,
18442 _: &actions::FoldFunctionBodies,
18443 window: &mut Window,
18444 cx: &mut Context<Self>,
18445 ) {
18446 let snapshot = self.buffer.read(cx).snapshot(cx);
18447
18448 let ranges = snapshot
18449 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18450 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18451 .collect::<Vec<_>>();
18452
18453 let creases = ranges
18454 .into_iter()
18455 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18456 .collect();
18457
18458 self.fold_creases(creases, true, window, cx);
18459 }
18460
18461 pub fn fold_recursive(
18462 &mut self,
18463 _: &actions::FoldRecursive,
18464 window: &mut Window,
18465 cx: &mut Context<Self>,
18466 ) {
18467 let mut to_fold = Vec::new();
18468 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18469 let selections = self.selections.all_adjusted(&display_map);
18470
18471 for selection in selections {
18472 let range = selection.range().sorted();
18473 let buffer_start_row = range.start.row;
18474
18475 if range.start.row != range.end.row {
18476 let mut found = false;
18477 for row in range.start.row..=range.end.row {
18478 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18479 found = true;
18480 to_fold.push(crease);
18481 }
18482 }
18483 if found {
18484 continue;
18485 }
18486 }
18487
18488 for row in (0..=range.start.row).rev() {
18489 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18490 if crease.range().end.row >= buffer_start_row {
18491 to_fold.push(crease);
18492 } else {
18493 break;
18494 }
18495 }
18496 }
18497 }
18498
18499 self.fold_creases(to_fold, true, window, cx);
18500 }
18501
18502 pub fn fold_at(
18503 &mut self,
18504 buffer_row: MultiBufferRow,
18505 window: &mut Window,
18506 cx: &mut Context<Self>,
18507 ) {
18508 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18509
18510 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18511 let autoscroll = self
18512 .selections
18513 .all::<Point>(&display_map)
18514 .iter()
18515 .any(|selection| crease.range().overlaps(&selection.range()));
18516
18517 self.fold_creases(vec![crease], autoscroll, window, cx);
18518 }
18519 }
18520
18521 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18522 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18523 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18524 let buffer = display_map.buffer_snapshot();
18525 let selections = self.selections.all::<Point>(&display_map);
18526 let ranges = selections
18527 .iter()
18528 .map(|s| {
18529 let range = s.display_range(&display_map).sorted();
18530 let mut start = range.start.to_point(&display_map);
18531 let mut end = range.end.to_point(&display_map);
18532 start.column = 0;
18533 end.column = buffer.line_len(MultiBufferRow(end.row));
18534 start..end
18535 })
18536 .collect::<Vec<_>>();
18537
18538 self.unfold_ranges(&ranges, true, true, cx);
18539 } else {
18540 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18541 let buffer_ids = self
18542 .selections
18543 .disjoint_anchor_ranges()
18544 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18545 .collect::<HashSet<_>>();
18546 for buffer_id in buffer_ids {
18547 self.unfold_buffer(buffer_id, cx);
18548 }
18549 }
18550 }
18551
18552 pub fn unfold_recursive(
18553 &mut self,
18554 _: &UnfoldRecursive,
18555 _window: &mut Window,
18556 cx: &mut Context<Self>,
18557 ) {
18558 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18559 let selections = self.selections.all::<Point>(&display_map);
18560 let ranges = selections
18561 .iter()
18562 .map(|s| {
18563 let mut range = s.display_range(&display_map).sorted();
18564 *range.start.column_mut() = 0;
18565 *range.end.column_mut() = display_map.line_len(range.end.row());
18566 let start = range.start.to_point(&display_map);
18567 let end = range.end.to_point(&display_map);
18568 start..end
18569 })
18570 .collect::<Vec<_>>();
18571
18572 self.unfold_ranges(&ranges, true, true, cx);
18573 }
18574
18575 pub fn unfold_at(
18576 &mut self,
18577 buffer_row: MultiBufferRow,
18578 _window: &mut Window,
18579 cx: &mut Context<Self>,
18580 ) {
18581 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18582
18583 let intersection_range = Point::new(buffer_row.0, 0)
18584 ..Point::new(
18585 buffer_row.0,
18586 display_map.buffer_snapshot().line_len(buffer_row),
18587 );
18588
18589 let autoscroll = self
18590 .selections
18591 .all::<Point>(&display_map)
18592 .iter()
18593 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18594
18595 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18596 }
18597
18598 pub fn unfold_all(
18599 &mut self,
18600 _: &actions::UnfoldAll,
18601 _window: &mut Window,
18602 cx: &mut Context<Self>,
18603 ) {
18604 if self.buffer.read(cx).is_singleton() {
18605 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18606 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18607 } else {
18608 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18609 editor
18610 .update(cx, |editor, cx| {
18611 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18612 editor.unfold_buffer(buffer_id, cx);
18613 }
18614 })
18615 .ok();
18616 });
18617 }
18618 }
18619
18620 pub fn fold_selected_ranges(
18621 &mut self,
18622 _: &FoldSelectedRanges,
18623 window: &mut Window,
18624 cx: &mut Context<Self>,
18625 ) {
18626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18627 let selections = self.selections.all_adjusted(&display_map);
18628 let ranges = selections
18629 .into_iter()
18630 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18631 .collect::<Vec<_>>();
18632 self.fold_creases(ranges, true, window, cx);
18633 }
18634
18635 pub fn fold_ranges<T: ToOffset + Clone>(
18636 &mut self,
18637 ranges: Vec<Range<T>>,
18638 auto_scroll: bool,
18639 window: &mut Window,
18640 cx: &mut Context<Self>,
18641 ) {
18642 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18643 let ranges = ranges
18644 .into_iter()
18645 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18646 .collect::<Vec<_>>();
18647 self.fold_creases(ranges, auto_scroll, window, cx);
18648 }
18649
18650 pub fn fold_creases<T: ToOffset + Clone>(
18651 &mut self,
18652 creases: Vec<Crease<T>>,
18653 auto_scroll: bool,
18654 _window: &mut Window,
18655 cx: &mut Context<Self>,
18656 ) {
18657 if creases.is_empty() {
18658 return;
18659 }
18660
18661 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18662
18663 if auto_scroll {
18664 self.request_autoscroll(Autoscroll::fit(), cx);
18665 }
18666
18667 cx.notify();
18668
18669 self.scrollbar_marker_state.dirty = true;
18670 self.folds_did_change(cx);
18671 }
18672
18673 /// Removes any folds whose ranges intersect any of the given ranges.
18674 pub fn unfold_ranges<T: ToOffset + Clone>(
18675 &mut self,
18676 ranges: &[Range<T>],
18677 inclusive: bool,
18678 auto_scroll: bool,
18679 cx: &mut Context<Self>,
18680 ) {
18681 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18682 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18683 });
18684 self.folds_did_change(cx);
18685 }
18686
18687 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18688 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18689 return;
18690 }
18691 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18692 self.display_map.update(cx, |display_map, cx| {
18693 display_map.fold_buffers([buffer_id], cx)
18694 });
18695 cx.emit(EditorEvent::BufferFoldToggled {
18696 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18697 folded: true,
18698 });
18699 cx.notify();
18700 }
18701
18702 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18703 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18704 return;
18705 }
18706 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18707 self.display_map.update(cx, |display_map, cx| {
18708 display_map.unfold_buffers([buffer_id], cx);
18709 });
18710 cx.emit(EditorEvent::BufferFoldToggled {
18711 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18712 folded: false,
18713 });
18714 cx.notify();
18715 }
18716
18717 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18718 self.display_map.read(cx).is_buffer_folded(buffer)
18719 }
18720
18721 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18722 self.display_map.read(cx).folded_buffers()
18723 }
18724
18725 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18726 self.display_map.update(cx, |display_map, cx| {
18727 display_map.disable_header_for_buffer(buffer_id, cx);
18728 });
18729 cx.notify();
18730 }
18731
18732 /// Removes any folds with the given ranges.
18733 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18734 &mut self,
18735 ranges: &[Range<T>],
18736 type_id: TypeId,
18737 auto_scroll: bool,
18738 cx: &mut Context<Self>,
18739 ) {
18740 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18741 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18742 });
18743 self.folds_did_change(cx);
18744 }
18745
18746 fn remove_folds_with<T: ToOffset + Clone>(
18747 &mut self,
18748 ranges: &[Range<T>],
18749 auto_scroll: bool,
18750 cx: &mut Context<Self>,
18751 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18752 ) {
18753 if ranges.is_empty() {
18754 return;
18755 }
18756
18757 let mut buffers_affected = HashSet::default();
18758 let multi_buffer = self.buffer().read(cx);
18759 for range in ranges {
18760 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18761 buffers_affected.insert(buffer.read(cx).remote_id());
18762 };
18763 }
18764
18765 self.display_map.update(cx, update);
18766
18767 if auto_scroll {
18768 self.request_autoscroll(Autoscroll::fit(), cx);
18769 }
18770
18771 cx.notify();
18772 self.scrollbar_marker_state.dirty = true;
18773 self.active_indent_guides_state.dirty = true;
18774 }
18775
18776 pub fn update_renderer_widths(
18777 &mut self,
18778 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18779 cx: &mut Context<Self>,
18780 ) -> bool {
18781 self.display_map
18782 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18783 }
18784
18785 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18786 self.display_map.read(cx).fold_placeholder.clone()
18787 }
18788
18789 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18790 self.buffer.update(cx, |buffer, cx| {
18791 buffer.set_all_diff_hunks_expanded(cx);
18792 });
18793 }
18794
18795 pub fn expand_all_diff_hunks(
18796 &mut self,
18797 _: &ExpandAllDiffHunks,
18798 _window: &mut Window,
18799 cx: &mut Context<Self>,
18800 ) {
18801 self.buffer.update(cx, |buffer, cx| {
18802 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18803 });
18804 }
18805
18806 pub fn collapse_all_diff_hunks(
18807 &mut self,
18808 _: &CollapseAllDiffHunks,
18809 _window: &mut Window,
18810 cx: &mut Context<Self>,
18811 ) {
18812 self.buffer.update(cx, |buffer, cx| {
18813 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18814 });
18815 }
18816
18817 pub fn toggle_selected_diff_hunks(
18818 &mut self,
18819 _: &ToggleSelectedDiffHunks,
18820 _window: &mut Window,
18821 cx: &mut Context<Self>,
18822 ) {
18823 let ranges: Vec<_> = self
18824 .selections
18825 .disjoint_anchors()
18826 .iter()
18827 .map(|s| s.range())
18828 .collect();
18829 self.toggle_diff_hunks_in_ranges(ranges, cx);
18830 }
18831
18832 pub fn diff_hunks_in_ranges<'a>(
18833 &'a self,
18834 ranges: &'a [Range<Anchor>],
18835 buffer: &'a MultiBufferSnapshot,
18836 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18837 ranges.iter().flat_map(move |range| {
18838 let end_excerpt_id = range.end.excerpt_id;
18839 let range = range.to_point(buffer);
18840 let mut peek_end = range.end;
18841 if range.end.row < buffer.max_row().0 {
18842 peek_end = Point::new(range.end.row + 1, 0);
18843 }
18844 buffer
18845 .diff_hunks_in_range(range.start..peek_end)
18846 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18847 })
18848 }
18849
18850 pub fn has_stageable_diff_hunks_in_ranges(
18851 &self,
18852 ranges: &[Range<Anchor>],
18853 snapshot: &MultiBufferSnapshot,
18854 ) -> bool {
18855 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18856 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18857 }
18858
18859 pub fn toggle_staged_selected_diff_hunks(
18860 &mut self,
18861 _: &::git::ToggleStaged,
18862 _: &mut Window,
18863 cx: &mut Context<Self>,
18864 ) {
18865 let snapshot = self.buffer.read(cx).snapshot(cx);
18866 let ranges: Vec<_> = self
18867 .selections
18868 .disjoint_anchors()
18869 .iter()
18870 .map(|s| s.range())
18871 .collect();
18872 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18873 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18874 }
18875
18876 pub fn set_render_diff_hunk_controls(
18877 &mut self,
18878 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18879 cx: &mut Context<Self>,
18880 ) {
18881 self.render_diff_hunk_controls = render_diff_hunk_controls;
18882 cx.notify();
18883 }
18884
18885 pub fn stage_and_next(
18886 &mut self,
18887 _: &::git::StageAndNext,
18888 window: &mut Window,
18889 cx: &mut Context<Self>,
18890 ) {
18891 self.do_stage_or_unstage_and_next(true, window, cx);
18892 }
18893
18894 pub fn unstage_and_next(
18895 &mut self,
18896 _: &::git::UnstageAndNext,
18897 window: &mut Window,
18898 cx: &mut Context<Self>,
18899 ) {
18900 self.do_stage_or_unstage_and_next(false, window, cx);
18901 }
18902
18903 pub fn stage_or_unstage_diff_hunks(
18904 &mut self,
18905 stage: bool,
18906 ranges: Vec<Range<Anchor>>,
18907 cx: &mut Context<Self>,
18908 ) {
18909 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18910 cx.spawn(async move |this, cx| {
18911 task.await?;
18912 this.update(cx, |this, cx| {
18913 let snapshot = this.buffer.read(cx).snapshot(cx);
18914 let chunk_by = this
18915 .diff_hunks_in_ranges(&ranges, &snapshot)
18916 .chunk_by(|hunk| hunk.buffer_id);
18917 for (buffer_id, hunks) in &chunk_by {
18918 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18919 }
18920 })
18921 })
18922 .detach_and_log_err(cx);
18923 }
18924
18925 fn save_buffers_for_ranges_if_needed(
18926 &mut self,
18927 ranges: &[Range<Anchor>],
18928 cx: &mut Context<Editor>,
18929 ) -> Task<Result<()>> {
18930 let multibuffer = self.buffer.read(cx);
18931 let snapshot = multibuffer.read(cx);
18932 let buffer_ids: HashSet<_> = ranges
18933 .iter()
18934 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18935 .collect();
18936 drop(snapshot);
18937
18938 let mut buffers = HashSet::default();
18939 for buffer_id in buffer_ids {
18940 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18941 let buffer = buffer_entity.read(cx);
18942 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18943 {
18944 buffers.insert(buffer_entity);
18945 }
18946 }
18947 }
18948
18949 if let Some(project) = &self.project {
18950 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18951 } else {
18952 Task::ready(Ok(()))
18953 }
18954 }
18955
18956 fn do_stage_or_unstage_and_next(
18957 &mut self,
18958 stage: bool,
18959 window: &mut Window,
18960 cx: &mut Context<Self>,
18961 ) {
18962 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18963
18964 if ranges.iter().any(|range| range.start != range.end) {
18965 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18966 return;
18967 }
18968
18969 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18970 let snapshot = self.snapshot(window, cx);
18971 let position = self
18972 .selections
18973 .newest::<Point>(&snapshot.display_snapshot)
18974 .head();
18975 let mut row = snapshot
18976 .buffer_snapshot()
18977 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18978 .find(|hunk| hunk.row_range.start.0 > position.row)
18979 .map(|hunk| hunk.row_range.start);
18980
18981 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18982 // Outside of the project diff editor, wrap around to the beginning.
18983 if !all_diff_hunks_expanded {
18984 row = row.or_else(|| {
18985 snapshot
18986 .buffer_snapshot()
18987 .diff_hunks_in_range(Point::zero()..position)
18988 .find(|hunk| hunk.row_range.end.0 < position.row)
18989 .map(|hunk| hunk.row_range.start)
18990 });
18991 }
18992
18993 if let Some(row) = row {
18994 let destination = Point::new(row.0, 0);
18995 let autoscroll = Autoscroll::center();
18996
18997 self.unfold_ranges(&[destination..destination], false, false, cx);
18998 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18999 s.select_ranges([destination..destination]);
19000 });
19001 }
19002 }
19003
19004 fn do_stage_or_unstage(
19005 &self,
19006 stage: bool,
19007 buffer_id: BufferId,
19008 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19009 cx: &mut App,
19010 ) -> Option<()> {
19011 let project = self.project()?;
19012 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19013 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19014 let buffer_snapshot = buffer.read(cx).snapshot();
19015 let file_exists = buffer_snapshot
19016 .file()
19017 .is_some_and(|file| file.disk_state().exists());
19018 diff.update(cx, |diff, cx| {
19019 diff.stage_or_unstage_hunks(
19020 stage,
19021 &hunks
19022 .map(|hunk| buffer_diff::DiffHunk {
19023 buffer_range: hunk.buffer_range,
19024 diff_base_byte_range: hunk.diff_base_byte_range,
19025 secondary_status: hunk.secondary_status,
19026 range: Point::zero()..Point::zero(), // unused
19027 })
19028 .collect::<Vec<_>>(),
19029 &buffer_snapshot,
19030 file_exists,
19031 cx,
19032 )
19033 });
19034 None
19035 }
19036
19037 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19038 let ranges: Vec<_> = self
19039 .selections
19040 .disjoint_anchors()
19041 .iter()
19042 .map(|s| s.range())
19043 .collect();
19044 self.buffer
19045 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19046 }
19047
19048 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19049 self.buffer.update(cx, |buffer, cx| {
19050 let ranges = vec![Anchor::min()..Anchor::max()];
19051 if !buffer.all_diff_hunks_expanded()
19052 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19053 {
19054 buffer.collapse_diff_hunks(ranges, cx);
19055 true
19056 } else {
19057 false
19058 }
19059 })
19060 }
19061
19062 fn toggle_diff_hunks_in_ranges(
19063 &mut self,
19064 ranges: Vec<Range<Anchor>>,
19065 cx: &mut Context<Editor>,
19066 ) {
19067 self.buffer.update(cx, |buffer, cx| {
19068 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19069 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19070 })
19071 }
19072
19073 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19074 self.buffer.update(cx, |buffer, cx| {
19075 let snapshot = buffer.snapshot(cx);
19076 let excerpt_id = range.end.excerpt_id;
19077 let point_range = range.to_point(&snapshot);
19078 let expand = !buffer.single_hunk_is_expanded(range, cx);
19079 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19080 })
19081 }
19082
19083 pub(crate) fn apply_all_diff_hunks(
19084 &mut self,
19085 _: &ApplyAllDiffHunks,
19086 window: &mut Window,
19087 cx: &mut Context<Self>,
19088 ) {
19089 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19090
19091 let buffers = self.buffer.read(cx).all_buffers();
19092 for branch_buffer in buffers {
19093 branch_buffer.update(cx, |branch_buffer, cx| {
19094 branch_buffer.merge_into_base(Vec::new(), cx);
19095 });
19096 }
19097
19098 if let Some(project) = self.project.clone() {
19099 self.save(
19100 SaveOptions {
19101 format: true,
19102 autosave: false,
19103 },
19104 project,
19105 window,
19106 cx,
19107 )
19108 .detach_and_log_err(cx);
19109 }
19110 }
19111
19112 pub(crate) fn apply_selected_diff_hunks(
19113 &mut self,
19114 _: &ApplyDiffHunk,
19115 window: &mut Window,
19116 cx: &mut Context<Self>,
19117 ) {
19118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19119 let snapshot = self.snapshot(window, cx);
19120 let hunks = snapshot.hunks_for_ranges(
19121 self.selections
19122 .all(&snapshot.display_snapshot)
19123 .into_iter()
19124 .map(|selection| selection.range()),
19125 );
19126 let mut ranges_by_buffer = HashMap::default();
19127 self.transact(window, cx, |editor, _window, cx| {
19128 for hunk in hunks {
19129 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19130 ranges_by_buffer
19131 .entry(buffer.clone())
19132 .or_insert_with(Vec::new)
19133 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19134 }
19135 }
19136
19137 for (buffer, ranges) in ranges_by_buffer {
19138 buffer.update(cx, |buffer, cx| {
19139 buffer.merge_into_base(ranges, cx);
19140 });
19141 }
19142 });
19143
19144 if let Some(project) = self.project.clone() {
19145 self.save(
19146 SaveOptions {
19147 format: true,
19148 autosave: false,
19149 },
19150 project,
19151 window,
19152 cx,
19153 )
19154 .detach_and_log_err(cx);
19155 }
19156 }
19157
19158 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19159 if hovered != self.gutter_hovered {
19160 self.gutter_hovered = hovered;
19161 cx.notify();
19162 }
19163 }
19164
19165 pub fn insert_blocks(
19166 &mut self,
19167 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19168 autoscroll: Option<Autoscroll>,
19169 cx: &mut Context<Self>,
19170 ) -> Vec<CustomBlockId> {
19171 let blocks = self
19172 .display_map
19173 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19174 if let Some(autoscroll) = autoscroll {
19175 self.request_autoscroll(autoscroll, cx);
19176 }
19177 cx.notify();
19178 blocks
19179 }
19180
19181 pub fn resize_blocks(
19182 &mut self,
19183 heights: HashMap<CustomBlockId, u32>,
19184 autoscroll: Option<Autoscroll>,
19185 cx: &mut Context<Self>,
19186 ) {
19187 self.display_map
19188 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19189 if let Some(autoscroll) = autoscroll {
19190 self.request_autoscroll(autoscroll, cx);
19191 }
19192 cx.notify();
19193 }
19194
19195 pub fn replace_blocks(
19196 &mut self,
19197 renderers: HashMap<CustomBlockId, RenderBlock>,
19198 autoscroll: Option<Autoscroll>,
19199 cx: &mut Context<Self>,
19200 ) {
19201 self.display_map
19202 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19203 if let Some(autoscroll) = autoscroll {
19204 self.request_autoscroll(autoscroll, cx);
19205 }
19206 cx.notify();
19207 }
19208
19209 pub fn remove_blocks(
19210 &mut self,
19211 block_ids: HashSet<CustomBlockId>,
19212 autoscroll: Option<Autoscroll>,
19213 cx: &mut Context<Self>,
19214 ) {
19215 self.display_map.update(cx, |display_map, cx| {
19216 display_map.remove_blocks(block_ids, cx)
19217 });
19218 if let Some(autoscroll) = autoscroll {
19219 self.request_autoscroll(autoscroll, cx);
19220 }
19221 cx.notify();
19222 }
19223
19224 pub fn row_for_block(
19225 &self,
19226 block_id: CustomBlockId,
19227 cx: &mut Context<Self>,
19228 ) -> Option<DisplayRow> {
19229 self.display_map
19230 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19231 }
19232
19233 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19234 self.focused_block = Some(focused_block);
19235 }
19236
19237 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19238 self.focused_block.take()
19239 }
19240
19241 pub fn insert_creases(
19242 &mut self,
19243 creases: impl IntoIterator<Item = Crease<Anchor>>,
19244 cx: &mut Context<Self>,
19245 ) -> Vec<CreaseId> {
19246 self.display_map
19247 .update(cx, |map, cx| map.insert_creases(creases, cx))
19248 }
19249
19250 pub fn remove_creases(
19251 &mut self,
19252 ids: impl IntoIterator<Item = CreaseId>,
19253 cx: &mut Context<Self>,
19254 ) -> Vec<(CreaseId, Range<Anchor>)> {
19255 self.display_map
19256 .update(cx, |map, cx| map.remove_creases(ids, cx))
19257 }
19258
19259 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19260 self.display_map
19261 .update(cx, |map, cx| map.snapshot(cx))
19262 .longest_row()
19263 }
19264
19265 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19266 self.display_map
19267 .update(cx, |map, cx| map.snapshot(cx))
19268 .max_point()
19269 }
19270
19271 pub fn text(&self, cx: &App) -> String {
19272 self.buffer.read(cx).read(cx).text()
19273 }
19274
19275 pub fn is_empty(&self, cx: &App) -> bool {
19276 self.buffer.read(cx).read(cx).is_empty()
19277 }
19278
19279 pub fn text_option(&self, cx: &App) -> Option<String> {
19280 let text = self.text(cx);
19281 let text = text.trim();
19282
19283 if text.is_empty() {
19284 return None;
19285 }
19286
19287 Some(text.to_string())
19288 }
19289
19290 pub fn set_text(
19291 &mut self,
19292 text: impl Into<Arc<str>>,
19293 window: &mut Window,
19294 cx: &mut Context<Self>,
19295 ) {
19296 self.transact(window, cx, |this, _, cx| {
19297 this.buffer
19298 .read(cx)
19299 .as_singleton()
19300 .expect("you can only call set_text on editors for singleton buffers")
19301 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19302 });
19303 }
19304
19305 pub fn display_text(&self, cx: &mut App) -> String {
19306 self.display_map
19307 .update(cx, |map, cx| map.snapshot(cx))
19308 .text()
19309 }
19310
19311 fn create_minimap(
19312 &self,
19313 minimap_settings: MinimapSettings,
19314 window: &mut Window,
19315 cx: &mut Context<Self>,
19316 ) -> Option<Entity<Self>> {
19317 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19318 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19319 }
19320
19321 fn initialize_new_minimap(
19322 &self,
19323 minimap_settings: MinimapSettings,
19324 window: &mut Window,
19325 cx: &mut Context<Self>,
19326 ) -> Entity<Self> {
19327 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19328
19329 let mut minimap = Editor::new_internal(
19330 EditorMode::Minimap {
19331 parent: cx.weak_entity(),
19332 },
19333 self.buffer.clone(),
19334 None,
19335 Some(self.display_map.clone()),
19336 window,
19337 cx,
19338 );
19339 minimap.scroll_manager.clone_state(&self.scroll_manager);
19340 minimap.set_text_style_refinement(TextStyleRefinement {
19341 font_size: Some(MINIMAP_FONT_SIZE),
19342 font_weight: Some(MINIMAP_FONT_WEIGHT),
19343 ..Default::default()
19344 });
19345 minimap.update_minimap_configuration(minimap_settings, cx);
19346 cx.new(|_| minimap)
19347 }
19348
19349 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19350 let current_line_highlight = minimap_settings
19351 .current_line_highlight
19352 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19353 self.set_current_line_highlight(Some(current_line_highlight));
19354 }
19355
19356 pub fn minimap(&self) -> Option<&Entity<Self>> {
19357 self.minimap
19358 .as_ref()
19359 .filter(|_| self.minimap_visibility.visible())
19360 }
19361
19362 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19363 let mut wrap_guides = smallvec![];
19364
19365 if self.show_wrap_guides == Some(false) {
19366 return wrap_guides;
19367 }
19368
19369 let settings = self.buffer.read(cx).language_settings(cx);
19370 if settings.show_wrap_guides {
19371 match self.soft_wrap_mode(cx) {
19372 SoftWrap::Column(soft_wrap) => {
19373 wrap_guides.push((soft_wrap as usize, true));
19374 }
19375 SoftWrap::Bounded(soft_wrap) => {
19376 wrap_guides.push((soft_wrap as usize, true));
19377 }
19378 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19379 }
19380 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19381 }
19382
19383 wrap_guides
19384 }
19385
19386 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19387 let settings = self.buffer.read(cx).language_settings(cx);
19388 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19389 match mode {
19390 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19391 SoftWrap::None
19392 }
19393 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19394 language_settings::SoftWrap::PreferredLineLength => {
19395 SoftWrap::Column(settings.preferred_line_length)
19396 }
19397 language_settings::SoftWrap::Bounded => {
19398 SoftWrap::Bounded(settings.preferred_line_length)
19399 }
19400 }
19401 }
19402
19403 pub fn set_soft_wrap_mode(
19404 &mut self,
19405 mode: language_settings::SoftWrap,
19406
19407 cx: &mut Context<Self>,
19408 ) {
19409 self.soft_wrap_mode_override = Some(mode);
19410 cx.notify();
19411 }
19412
19413 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19414 self.hard_wrap = hard_wrap;
19415 cx.notify();
19416 }
19417
19418 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19419 self.text_style_refinement = Some(style);
19420 }
19421
19422 /// called by the Element so we know what style we were most recently rendered with.
19423 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19424 // We intentionally do not inform the display map about the minimap style
19425 // so that wrapping is not recalculated and stays consistent for the editor
19426 // and its linked minimap.
19427 if !self.mode.is_minimap() {
19428 let font = style.text.font();
19429 let font_size = style.text.font_size.to_pixels(window.rem_size());
19430 let display_map = self
19431 .placeholder_display_map
19432 .as_ref()
19433 .filter(|_| self.is_empty(cx))
19434 .unwrap_or(&self.display_map);
19435
19436 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19437 }
19438 self.style = Some(style);
19439 }
19440
19441 pub fn style(&self) -> Option<&EditorStyle> {
19442 self.style.as_ref()
19443 }
19444
19445 // Called by the element. This method is not designed to be called outside of the editor
19446 // element's layout code because it does not notify when rewrapping is computed synchronously.
19447 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19448 if self.is_empty(cx) {
19449 self.placeholder_display_map
19450 .as_ref()
19451 .map_or(false, |display_map| {
19452 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19453 })
19454 } else {
19455 self.display_map
19456 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19457 }
19458 }
19459
19460 pub fn set_soft_wrap(&mut self) {
19461 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19462 }
19463
19464 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19465 if self.soft_wrap_mode_override.is_some() {
19466 self.soft_wrap_mode_override.take();
19467 } else {
19468 let soft_wrap = match self.soft_wrap_mode(cx) {
19469 SoftWrap::GitDiff => return,
19470 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19471 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19472 language_settings::SoftWrap::None
19473 }
19474 };
19475 self.soft_wrap_mode_override = Some(soft_wrap);
19476 }
19477 cx.notify();
19478 }
19479
19480 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19481 let Some(workspace) = self.workspace() else {
19482 return;
19483 };
19484 let fs = workspace.read(cx).app_state().fs.clone();
19485 let current_show = TabBarSettings::get_global(cx).show;
19486 update_settings_file(fs, cx, move |setting, _| {
19487 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19488 });
19489 }
19490
19491 pub fn toggle_indent_guides(
19492 &mut self,
19493 _: &ToggleIndentGuides,
19494 _: &mut Window,
19495 cx: &mut Context<Self>,
19496 ) {
19497 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19498 self.buffer
19499 .read(cx)
19500 .language_settings(cx)
19501 .indent_guides
19502 .enabled
19503 });
19504 self.show_indent_guides = Some(!currently_enabled);
19505 cx.notify();
19506 }
19507
19508 fn should_show_indent_guides(&self) -> Option<bool> {
19509 self.show_indent_guides
19510 }
19511
19512 pub fn toggle_line_numbers(
19513 &mut self,
19514 _: &ToggleLineNumbers,
19515 _: &mut Window,
19516 cx: &mut Context<Self>,
19517 ) {
19518 let mut editor_settings = EditorSettings::get_global(cx).clone();
19519 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19520 EditorSettings::override_global(editor_settings, cx);
19521 }
19522
19523 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19524 if let Some(show_line_numbers) = self.show_line_numbers {
19525 return show_line_numbers;
19526 }
19527 EditorSettings::get_global(cx).gutter.line_numbers
19528 }
19529
19530 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19531 self.use_relative_line_numbers
19532 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19533 }
19534
19535 pub fn toggle_relative_line_numbers(
19536 &mut self,
19537 _: &ToggleRelativeLineNumbers,
19538 _: &mut Window,
19539 cx: &mut Context<Self>,
19540 ) {
19541 let is_relative = self.should_use_relative_line_numbers(cx);
19542 self.set_relative_line_number(Some(!is_relative), cx)
19543 }
19544
19545 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19546 self.use_relative_line_numbers = is_relative;
19547 cx.notify();
19548 }
19549
19550 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19551 self.show_gutter = show_gutter;
19552 cx.notify();
19553 }
19554
19555 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19556 self.show_scrollbars = ScrollbarAxes {
19557 horizontal: show,
19558 vertical: show,
19559 };
19560 cx.notify();
19561 }
19562
19563 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19564 self.show_scrollbars.vertical = show;
19565 cx.notify();
19566 }
19567
19568 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19569 self.show_scrollbars.horizontal = show;
19570 cx.notify();
19571 }
19572
19573 pub fn set_minimap_visibility(
19574 &mut self,
19575 minimap_visibility: MinimapVisibility,
19576 window: &mut Window,
19577 cx: &mut Context<Self>,
19578 ) {
19579 if self.minimap_visibility != minimap_visibility {
19580 if minimap_visibility.visible() && self.minimap.is_none() {
19581 let minimap_settings = EditorSettings::get_global(cx).minimap;
19582 self.minimap =
19583 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19584 }
19585 self.minimap_visibility = minimap_visibility;
19586 cx.notify();
19587 }
19588 }
19589
19590 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19591 self.set_show_scrollbars(false, cx);
19592 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19593 }
19594
19595 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19596 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19597 }
19598
19599 /// Normally the text in full mode and auto height editors is padded on the
19600 /// left side by roughly half a character width for improved hit testing.
19601 ///
19602 /// Use this method to disable this for cases where this is not wanted (e.g.
19603 /// if you want to align the editor text with some other text above or below)
19604 /// or if you want to add this padding to single-line editors.
19605 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19606 self.offset_content = offset_content;
19607 cx.notify();
19608 }
19609
19610 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19611 self.show_line_numbers = Some(show_line_numbers);
19612 cx.notify();
19613 }
19614
19615 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19616 self.disable_expand_excerpt_buttons = true;
19617 cx.notify();
19618 }
19619
19620 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19621 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19622 cx.notify();
19623 }
19624
19625 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19626 self.show_code_actions = Some(show_code_actions);
19627 cx.notify();
19628 }
19629
19630 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19631 self.show_runnables = Some(show_runnables);
19632 cx.notify();
19633 }
19634
19635 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19636 self.show_breakpoints = Some(show_breakpoints);
19637 cx.notify();
19638 }
19639
19640 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19641 if self.display_map.read(cx).masked != masked {
19642 self.display_map.update(cx, |map, _| map.masked = masked);
19643 }
19644 cx.notify()
19645 }
19646
19647 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19648 self.show_wrap_guides = Some(show_wrap_guides);
19649 cx.notify();
19650 }
19651
19652 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19653 self.show_indent_guides = Some(show_indent_guides);
19654 cx.notify();
19655 }
19656
19657 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19658 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19659 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19660 && let Some(dir) = file.abs_path(cx).parent()
19661 {
19662 return Some(dir.to_owned());
19663 }
19664 }
19665
19666 None
19667 }
19668
19669 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19670 self.active_excerpt(cx)?
19671 .1
19672 .read(cx)
19673 .file()
19674 .and_then(|f| f.as_local())
19675 }
19676
19677 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19678 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19679 let buffer = buffer.read(cx);
19680 if let Some(project_path) = buffer.project_path(cx) {
19681 let project = self.project()?.read(cx);
19682 project.absolute_path(&project_path, cx)
19683 } else {
19684 buffer
19685 .file()
19686 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19687 }
19688 })
19689 }
19690
19691 pub fn reveal_in_finder(
19692 &mut self,
19693 _: &RevealInFileManager,
19694 _window: &mut Window,
19695 cx: &mut Context<Self>,
19696 ) {
19697 if let Some(target) = self.target_file(cx) {
19698 cx.reveal_path(&target.abs_path(cx));
19699 }
19700 }
19701
19702 pub fn copy_path(
19703 &mut self,
19704 _: &zed_actions::workspace::CopyPath,
19705 _window: &mut Window,
19706 cx: &mut Context<Self>,
19707 ) {
19708 if let Some(path) = self.target_file_abs_path(cx)
19709 && let Some(path) = path.to_str()
19710 {
19711 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19712 } else {
19713 cx.propagate();
19714 }
19715 }
19716
19717 pub fn copy_relative_path(
19718 &mut self,
19719 _: &zed_actions::workspace::CopyRelativePath,
19720 _window: &mut Window,
19721 cx: &mut Context<Self>,
19722 ) {
19723 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19724 let project = self.project()?.read(cx);
19725 let path = buffer.read(cx).file()?.path();
19726 let path = path.display(project.path_style(cx));
19727 Some(path)
19728 }) {
19729 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19730 } else {
19731 cx.propagate();
19732 }
19733 }
19734
19735 /// Returns the project path for the editor's buffer, if any buffer is
19736 /// opened in the editor.
19737 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19738 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19739 buffer.read(cx).project_path(cx)
19740 } else {
19741 None
19742 }
19743 }
19744
19745 // Returns true if the editor handled a go-to-line request
19746 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19747 maybe!({
19748 let breakpoint_store = self.breakpoint_store.as_ref()?;
19749
19750 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19751 else {
19752 self.clear_row_highlights::<ActiveDebugLine>();
19753 return None;
19754 };
19755
19756 let position = active_stack_frame.position;
19757 let buffer_id = position.buffer_id?;
19758 let snapshot = self
19759 .project
19760 .as_ref()?
19761 .read(cx)
19762 .buffer_for_id(buffer_id, cx)?
19763 .read(cx)
19764 .snapshot();
19765
19766 let mut handled = false;
19767 for (id, ExcerptRange { context, .. }) in
19768 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19769 {
19770 if context.start.cmp(&position, &snapshot).is_ge()
19771 || context.end.cmp(&position, &snapshot).is_lt()
19772 {
19773 continue;
19774 }
19775 let snapshot = self.buffer.read(cx).snapshot(cx);
19776 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19777
19778 handled = true;
19779 self.clear_row_highlights::<ActiveDebugLine>();
19780
19781 self.go_to_line::<ActiveDebugLine>(
19782 multibuffer_anchor,
19783 Some(cx.theme().colors().editor_debugger_active_line_background),
19784 window,
19785 cx,
19786 );
19787
19788 cx.notify();
19789 }
19790
19791 handled.then_some(())
19792 })
19793 .is_some()
19794 }
19795
19796 pub fn copy_file_name_without_extension(
19797 &mut self,
19798 _: &CopyFileNameWithoutExtension,
19799 _: &mut Window,
19800 cx: &mut Context<Self>,
19801 ) {
19802 if let Some(file) = self.target_file(cx)
19803 && let Some(file_stem) = file.path().file_stem()
19804 {
19805 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19806 }
19807 }
19808
19809 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19810 if let Some(file) = self.target_file(cx)
19811 && let Some(name) = file.path().file_name()
19812 {
19813 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19814 }
19815 }
19816
19817 pub fn toggle_git_blame(
19818 &mut self,
19819 _: &::git::Blame,
19820 window: &mut Window,
19821 cx: &mut Context<Self>,
19822 ) {
19823 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19824
19825 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19826 self.start_git_blame(true, window, cx);
19827 }
19828
19829 cx.notify();
19830 }
19831
19832 pub fn toggle_git_blame_inline(
19833 &mut self,
19834 _: &ToggleGitBlameInline,
19835 window: &mut Window,
19836 cx: &mut Context<Self>,
19837 ) {
19838 self.toggle_git_blame_inline_internal(true, window, cx);
19839 cx.notify();
19840 }
19841
19842 pub fn open_git_blame_commit(
19843 &mut self,
19844 _: &OpenGitBlameCommit,
19845 window: &mut Window,
19846 cx: &mut Context<Self>,
19847 ) {
19848 self.open_git_blame_commit_internal(window, cx);
19849 }
19850
19851 fn open_git_blame_commit_internal(
19852 &mut self,
19853 window: &mut Window,
19854 cx: &mut Context<Self>,
19855 ) -> Option<()> {
19856 let blame = self.blame.as_ref()?;
19857 let snapshot = self.snapshot(window, cx);
19858 let cursor = self
19859 .selections
19860 .newest::<Point>(&snapshot.display_snapshot)
19861 .head();
19862 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19863 let (_, blame_entry) = blame
19864 .update(cx, |blame, cx| {
19865 blame
19866 .blame_for_rows(
19867 &[RowInfo {
19868 buffer_id: Some(buffer.remote_id()),
19869 buffer_row: Some(point.row),
19870 ..Default::default()
19871 }],
19872 cx,
19873 )
19874 .next()
19875 })
19876 .flatten()?;
19877 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19878 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19879 let workspace = self.workspace()?.downgrade();
19880 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19881 None
19882 }
19883
19884 pub fn git_blame_inline_enabled(&self) -> bool {
19885 self.git_blame_inline_enabled
19886 }
19887
19888 pub fn toggle_selection_menu(
19889 &mut self,
19890 _: &ToggleSelectionMenu,
19891 _: &mut Window,
19892 cx: &mut Context<Self>,
19893 ) {
19894 self.show_selection_menu = self
19895 .show_selection_menu
19896 .map(|show_selections_menu| !show_selections_menu)
19897 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19898
19899 cx.notify();
19900 }
19901
19902 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19903 self.show_selection_menu
19904 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19905 }
19906
19907 fn start_git_blame(
19908 &mut self,
19909 user_triggered: bool,
19910 window: &mut Window,
19911 cx: &mut Context<Self>,
19912 ) {
19913 if let Some(project) = self.project() {
19914 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19915 && buffer.read(cx).file().is_none()
19916 {
19917 return;
19918 }
19919
19920 let focused = self.focus_handle(cx).contains_focused(window, cx);
19921
19922 let project = project.clone();
19923 let blame = cx
19924 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19925 self.blame_subscription =
19926 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19927 self.blame = Some(blame);
19928 }
19929 }
19930
19931 fn toggle_git_blame_inline_internal(
19932 &mut self,
19933 user_triggered: bool,
19934 window: &mut Window,
19935 cx: &mut Context<Self>,
19936 ) {
19937 if self.git_blame_inline_enabled {
19938 self.git_blame_inline_enabled = false;
19939 self.show_git_blame_inline = false;
19940 self.show_git_blame_inline_delay_task.take();
19941 } else {
19942 self.git_blame_inline_enabled = true;
19943 self.start_git_blame_inline(user_triggered, window, cx);
19944 }
19945
19946 cx.notify();
19947 }
19948
19949 fn start_git_blame_inline(
19950 &mut self,
19951 user_triggered: bool,
19952 window: &mut Window,
19953 cx: &mut Context<Self>,
19954 ) {
19955 self.start_git_blame(user_triggered, window, cx);
19956
19957 if ProjectSettings::get_global(cx)
19958 .git
19959 .inline_blame_delay()
19960 .is_some()
19961 {
19962 self.start_inline_blame_timer(window, cx);
19963 } else {
19964 self.show_git_blame_inline = true
19965 }
19966 }
19967
19968 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19969 self.blame.as_ref()
19970 }
19971
19972 pub fn show_git_blame_gutter(&self) -> bool {
19973 self.show_git_blame_gutter
19974 }
19975
19976 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19977 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19978 }
19979
19980 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19981 self.show_git_blame_inline
19982 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19983 && !self.newest_selection_head_on_empty_line(cx)
19984 && self.has_blame_entries(cx)
19985 }
19986
19987 fn has_blame_entries(&self, cx: &App) -> bool {
19988 self.blame()
19989 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19990 }
19991
19992 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19993 let cursor_anchor = self.selections.newest_anchor().head();
19994
19995 let snapshot = self.buffer.read(cx).snapshot(cx);
19996 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19997
19998 snapshot.line_len(buffer_row) == 0
19999 }
20000
20001 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20002 let buffer_and_selection = maybe!({
20003 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20004 let selection_range = selection.range();
20005
20006 let multi_buffer = self.buffer().read(cx);
20007 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20008 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20009
20010 let (buffer, range, _) = if selection.reversed {
20011 buffer_ranges.first()
20012 } else {
20013 buffer_ranges.last()
20014 }?;
20015
20016 let selection = text::ToPoint::to_point(&range.start, buffer).row
20017 ..text::ToPoint::to_point(&range.end, buffer).row;
20018 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20019 });
20020
20021 let Some((buffer, selection)) = buffer_and_selection else {
20022 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20023 };
20024
20025 let Some(project) = self.project() else {
20026 return Task::ready(Err(anyhow!("editor does not have project")));
20027 };
20028
20029 project.update(cx, |project, cx| {
20030 project.get_permalink_to_line(&buffer, selection, cx)
20031 })
20032 }
20033
20034 pub fn copy_permalink_to_line(
20035 &mut self,
20036 _: &CopyPermalinkToLine,
20037 window: &mut Window,
20038 cx: &mut Context<Self>,
20039 ) {
20040 let permalink_task = self.get_permalink_to_line(cx);
20041 let workspace = self.workspace();
20042
20043 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20044 Ok(permalink) => {
20045 cx.update(|_, cx| {
20046 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20047 })
20048 .ok();
20049 }
20050 Err(err) => {
20051 let message = format!("Failed to copy permalink: {err}");
20052
20053 anyhow::Result::<()>::Err(err).log_err();
20054
20055 if let Some(workspace) = workspace {
20056 workspace
20057 .update_in(cx, |workspace, _, cx| {
20058 struct CopyPermalinkToLine;
20059
20060 workspace.show_toast(
20061 Toast::new(
20062 NotificationId::unique::<CopyPermalinkToLine>(),
20063 message,
20064 ),
20065 cx,
20066 )
20067 })
20068 .ok();
20069 }
20070 }
20071 })
20072 .detach();
20073 }
20074
20075 pub fn copy_file_location(
20076 &mut self,
20077 _: &CopyFileLocation,
20078 _: &mut Window,
20079 cx: &mut Context<Self>,
20080 ) {
20081 let selection = self
20082 .selections
20083 .newest::<Point>(&self.display_snapshot(cx))
20084 .start
20085 .row
20086 + 1;
20087 if let Some(file) = self.target_file(cx) {
20088 let path = file.path().display(file.path_style(cx));
20089 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20090 }
20091 }
20092
20093 pub fn open_permalink_to_line(
20094 &mut self,
20095 _: &OpenPermalinkToLine,
20096 window: &mut Window,
20097 cx: &mut Context<Self>,
20098 ) {
20099 let permalink_task = self.get_permalink_to_line(cx);
20100 let workspace = self.workspace();
20101
20102 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20103 Ok(permalink) => {
20104 cx.update(|_, cx| {
20105 cx.open_url(permalink.as_ref());
20106 })
20107 .ok();
20108 }
20109 Err(err) => {
20110 let message = format!("Failed to open permalink: {err}");
20111
20112 anyhow::Result::<()>::Err(err).log_err();
20113
20114 if let Some(workspace) = workspace {
20115 workspace
20116 .update(cx, |workspace, cx| {
20117 struct OpenPermalinkToLine;
20118
20119 workspace.show_toast(
20120 Toast::new(
20121 NotificationId::unique::<OpenPermalinkToLine>(),
20122 message,
20123 ),
20124 cx,
20125 )
20126 })
20127 .ok();
20128 }
20129 }
20130 })
20131 .detach();
20132 }
20133
20134 pub fn insert_uuid_v4(
20135 &mut self,
20136 _: &InsertUuidV4,
20137 window: &mut Window,
20138 cx: &mut Context<Self>,
20139 ) {
20140 self.insert_uuid(UuidVersion::V4, window, cx);
20141 }
20142
20143 pub fn insert_uuid_v7(
20144 &mut self,
20145 _: &InsertUuidV7,
20146 window: &mut Window,
20147 cx: &mut Context<Self>,
20148 ) {
20149 self.insert_uuid(UuidVersion::V7, window, cx);
20150 }
20151
20152 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20153 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20154 self.transact(window, cx, |this, window, cx| {
20155 let edits = this
20156 .selections
20157 .all::<Point>(&this.display_snapshot(cx))
20158 .into_iter()
20159 .map(|selection| {
20160 let uuid = match version {
20161 UuidVersion::V4 => uuid::Uuid::new_v4(),
20162 UuidVersion::V7 => uuid::Uuid::now_v7(),
20163 };
20164
20165 (selection.range(), uuid.to_string())
20166 });
20167 this.edit(edits, cx);
20168 this.refresh_edit_prediction(true, false, window, cx);
20169 });
20170 }
20171
20172 pub fn open_selections_in_multibuffer(
20173 &mut self,
20174 _: &OpenSelectionsInMultibuffer,
20175 window: &mut Window,
20176 cx: &mut Context<Self>,
20177 ) {
20178 let multibuffer = self.buffer.read(cx);
20179
20180 let Some(buffer) = multibuffer.as_singleton() else {
20181 return;
20182 };
20183
20184 let Some(workspace) = self.workspace() else {
20185 return;
20186 };
20187
20188 let title = multibuffer.title(cx).to_string();
20189
20190 let locations = self
20191 .selections
20192 .all_anchors(cx)
20193 .iter()
20194 .map(|selection| {
20195 (
20196 buffer.clone(),
20197 (selection.start.text_anchor..selection.end.text_anchor)
20198 .to_point(buffer.read(cx)),
20199 )
20200 })
20201 .into_group_map();
20202
20203 cx.spawn_in(window, async move |_, cx| {
20204 workspace.update_in(cx, |workspace, window, cx| {
20205 Self::open_locations_in_multibuffer(
20206 workspace,
20207 locations,
20208 format!("Selections for '{title}'"),
20209 false,
20210 MultibufferSelectionMode::All,
20211 window,
20212 cx,
20213 );
20214 })
20215 })
20216 .detach();
20217 }
20218
20219 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20220 /// last highlight added will be used.
20221 ///
20222 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20223 pub fn highlight_rows<T: 'static>(
20224 &mut self,
20225 range: Range<Anchor>,
20226 color: Hsla,
20227 options: RowHighlightOptions,
20228 cx: &mut Context<Self>,
20229 ) {
20230 let snapshot = self.buffer().read(cx).snapshot(cx);
20231 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20232 let ix = row_highlights.binary_search_by(|highlight| {
20233 Ordering::Equal
20234 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20235 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20236 });
20237
20238 if let Err(mut ix) = ix {
20239 let index = post_inc(&mut self.highlight_order);
20240
20241 // If this range intersects with the preceding highlight, then merge it with
20242 // the preceding highlight. Otherwise insert a new highlight.
20243 let mut merged = false;
20244 if ix > 0 {
20245 let prev_highlight = &mut row_highlights[ix - 1];
20246 if prev_highlight
20247 .range
20248 .end
20249 .cmp(&range.start, &snapshot)
20250 .is_ge()
20251 {
20252 ix -= 1;
20253 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20254 prev_highlight.range.end = range.end;
20255 }
20256 merged = true;
20257 prev_highlight.index = index;
20258 prev_highlight.color = color;
20259 prev_highlight.options = options;
20260 }
20261 }
20262
20263 if !merged {
20264 row_highlights.insert(
20265 ix,
20266 RowHighlight {
20267 range,
20268 index,
20269 color,
20270 options,
20271 type_id: TypeId::of::<T>(),
20272 },
20273 );
20274 }
20275
20276 // If any of the following highlights intersect with this one, merge them.
20277 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20278 let highlight = &row_highlights[ix];
20279 if next_highlight
20280 .range
20281 .start
20282 .cmp(&highlight.range.end, &snapshot)
20283 .is_le()
20284 {
20285 if next_highlight
20286 .range
20287 .end
20288 .cmp(&highlight.range.end, &snapshot)
20289 .is_gt()
20290 {
20291 row_highlights[ix].range.end = next_highlight.range.end;
20292 }
20293 row_highlights.remove(ix + 1);
20294 } else {
20295 break;
20296 }
20297 }
20298 }
20299 }
20300
20301 /// Remove any highlighted row ranges of the given type that intersect the
20302 /// given ranges.
20303 pub fn remove_highlighted_rows<T: 'static>(
20304 &mut self,
20305 ranges_to_remove: Vec<Range<Anchor>>,
20306 cx: &mut Context<Self>,
20307 ) {
20308 let snapshot = self.buffer().read(cx).snapshot(cx);
20309 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20310 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20311 row_highlights.retain(|highlight| {
20312 while let Some(range_to_remove) = ranges_to_remove.peek() {
20313 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20314 Ordering::Less | Ordering::Equal => {
20315 ranges_to_remove.next();
20316 }
20317 Ordering::Greater => {
20318 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20319 Ordering::Less | Ordering::Equal => {
20320 return false;
20321 }
20322 Ordering::Greater => break,
20323 }
20324 }
20325 }
20326 }
20327
20328 true
20329 })
20330 }
20331
20332 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20333 pub fn clear_row_highlights<T: 'static>(&mut self) {
20334 self.highlighted_rows.remove(&TypeId::of::<T>());
20335 }
20336
20337 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20338 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20339 self.highlighted_rows
20340 .get(&TypeId::of::<T>())
20341 .map_or(&[] as &[_], |vec| vec.as_slice())
20342 .iter()
20343 .map(|highlight| (highlight.range.clone(), highlight.color))
20344 }
20345
20346 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20347 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20348 /// Allows to ignore certain kinds of highlights.
20349 pub fn highlighted_display_rows(
20350 &self,
20351 window: &mut Window,
20352 cx: &mut App,
20353 ) -> BTreeMap<DisplayRow, LineHighlight> {
20354 let snapshot = self.snapshot(window, cx);
20355 let mut used_highlight_orders = HashMap::default();
20356 self.highlighted_rows
20357 .iter()
20358 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20359 .fold(
20360 BTreeMap::<DisplayRow, LineHighlight>::new(),
20361 |mut unique_rows, highlight| {
20362 let start = highlight.range.start.to_display_point(&snapshot);
20363 let end = highlight.range.end.to_display_point(&snapshot);
20364 let start_row = start.row().0;
20365 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20366 && end.column() == 0
20367 {
20368 end.row().0.saturating_sub(1)
20369 } else {
20370 end.row().0
20371 };
20372 for row in start_row..=end_row {
20373 let used_index =
20374 used_highlight_orders.entry(row).or_insert(highlight.index);
20375 if highlight.index >= *used_index {
20376 *used_index = highlight.index;
20377 unique_rows.insert(
20378 DisplayRow(row),
20379 LineHighlight {
20380 include_gutter: highlight.options.include_gutter,
20381 border: None,
20382 background: highlight.color.into(),
20383 type_id: Some(highlight.type_id),
20384 },
20385 );
20386 }
20387 }
20388 unique_rows
20389 },
20390 )
20391 }
20392
20393 pub fn highlighted_display_row_for_autoscroll(
20394 &self,
20395 snapshot: &DisplaySnapshot,
20396 ) -> Option<DisplayRow> {
20397 self.highlighted_rows
20398 .values()
20399 .flat_map(|highlighted_rows| highlighted_rows.iter())
20400 .filter_map(|highlight| {
20401 if highlight.options.autoscroll {
20402 Some(highlight.range.start.to_display_point(snapshot).row())
20403 } else {
20404 None
20405 }
20406 })
20407 .min()
20408 }
20409
20410 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20411 self.highlight_background::<SearchWithinRange>(
20412 ranges,
20413 |colors| colors.colors().editor_document_highlight_read_background,
20414 cx,
20415 )
20416 }
20417
20418 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20419 self.breadcrumb_header = Some(new_header);
20420 }
20421
20422 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20423 self.clear_background_highlights::<SearchWithinRange>(cx);
20424 }
20425
20426 pub fn highlight_background<T: 'static>(
20427 &mut self,
20428 ranges: &[Range<Anchor>],
20429 color_fetcher: fn(&Theme) -> Hsla,
20430 cx: &mut Context<Self>,
20431 ) {
20432 self.background_highlights.insert(
20433 HighlightKey::Type(TypeId::of::<T>()),
20434 (color_fetcher, Arc::from(ranges)),
20435 );
20436 self.scrollbar_marker_state.dirty = true;
20437 cx.notify();
20438 }
20439
20440 pub fn highlight_background_key<T: 'static>(
20441 &mut self,
20442 key: usize,
20443 ranges: &[Range<Anchor>],
20444 color_fetcher: fn(&Theme) -> Hsla,
20445 cx: &mut Context<Self>,
20446 ) {
20447 self.background_highlights.insert(
20448 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20449 (color_fetcher, Arc::from(ranges)),
20450 );
20451 self.scrollbar_marker_state.dirty = true;
20452 cx.notify();
20453 }
20454
20455 pub fn clear_background_highlights<T: 'static>(
20456 &mut self,
20457 cx: &mut Context<Self>,
20458 ) -> Option<BackgroundHighlight> {
20459 let text_highlights = self
20460 .background_highlights
20461 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20462 if !text_highlights.1.is_empty() {
20463 self.scrollbar_marker_state.dirty = true;
20464 cx.notify();
20465 }
20466 Some(text_highlights)
20467 }
20468
20469 pub fn highlight_gutter<T: 'static>(
20470 &mut self,
20471 ranges: impl Into<Vec<Range<Anchor>>>,
20472 color_fetcher: fn(&App) -> Hsla,
20473 cx: &mut Context<Self>,
20474 ) {
20475 self.gutter_highlights
20476 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20477 cx.notify();
20478 }
20479
20480 pub fn clear_gutter_highlights<T: 'static>(
20481 &mut self,
20482 cx: &mut Context<Self>,
20483 ) -> Option<GutterHighlight> {
20484 cx.notify();
20485 self.gutter_highlights.remove(&TypeId::of::<T>())
20486 }
20487
20488 pub fn insert_gutter_highlight<T: 'static>(
20489 &mut self,
20490 range: Range<Anchor>,
20491 color_fetcher: fn(&App) -> Hsla,
20492 cx: &mut Context<Self>,
20493 ) {
20494 let snapshot = self.buffer().read(cx).snapshot(cx);
20495 let mut highlights = self
20496 .gutter_highlights
20497 .remove(&TypeId::of::<T>())
20498 .map(|(_, highlights)| highlights)
20499 .unwrap_or_default();
20500 let ix = highlights.binary_search_by(|highlight| {
20501 Ordering::Equal
20502 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20503 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20504 });
20505 if let Err(ix) = ix {
20506 highlights.insert(ix, range);
20507 }
20508 self.gutter_highlights
20509 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20510 }
20511
20512 pub fn remove_gutter_highlights<T: 'static>(
20513 &mut self,
20514 ranges_to_remove: Vec<Range<Anchor>>,
20515 cx: &mut Context<Self>,
20516 ) {
20517 let snapshot = self.buffer().read(cx).snapshot(cx);
20518 let Some((color_fetcher, mut gutter_highlights)) =
20519 self.gutter_highlights.remove(&TypeId::of::<T>())
20520 else {
20521 return;
20522 };
20523 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20524 gutter_highlights.retain(|highlight| {
20525 while let Some(range_to_remove) = ranges_to_remove.peek() {
20526 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20527 Ordering::Less | Ordering::Equal => {
20528 ranges_to_remove.next();
20529 }
20530 Ordering::Greater => {
20531 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20532 Ordering::Less | Ordering::Equal => {
20533 return false;
20534 }
20535 Ordering::Greater => break,
20536 }
20537 }
20538 }
20539 }
20540
20541 true
20542 });
20543 self.gutter_highlights
20544 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20545 }
20546
20547 #[cfg(feature = "test-support")]
20548 pub fn all_text_highlights(
20549 &self,
20550 window: &mut Window,
20551 cx: &mut Context<Self>,
20552 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20553 let snapshot = self.snapshot(window, cx);
20554 self.display_map.update(cx, |display_map, _| {
20555 display_map
20556 .all_text_highlights()
20557 .map(|highlight| {
20558 let (style, ranges) = highlight.as_ref();
20559 (
20560 *style,
20561 ranges
20562 .iter()
20563 .map(|range| range.clone().to_display_points(&snapshot))
20564 .collect(),
20565 )
20566 })
20567 .collect()
20568 })
20569 }
20570
20571 #[cfg(feature = "test-support")]
20572 pub fn all_text_background_highlights(
20573 &self,
20574 window: &mut Window,
20575 cx: &mut Context<Self>,
20576 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20577 let snapshot = self.snapshot(window, cx);
20578 let buffer = &snapshot.buffer_snapshot();
20579 let start = buffer.anchor_before(0);
20580 let end = buffer.anchor_after(buffer.len());
20581 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20582 }
20583
20584 #[cfg(any(test, feature = "test-support"))]
20585 pub fn sorted_background_highlights_in_range(
20586 &self,
20587 search_range: Range<Anchor>,
20588 display_snapshot: &DisplaySnapshot,
20589 theme: &Theme,
20590 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20591 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20592 res.sort_by(|a, b| {
20593 a.0.start
20594 .cmp(&b.0.start)
20595 .then_with(|| a.0.end.cmp(&b.0.end))
20596 .then_with(|| a.1.cmp(&b.1))
20597 });
20598 res
20599 }
20600
20601 #[cfg(feature = "test-support")]
20602 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20603 let snapshot = self.buffer().read(cx).snapshot(cx);
20604
20605 let highlights = self
20606 .background_highlights
20607 .get(&HighlightKey::Type(TypeId::of::<
20608 items::BufferSearchHighlights,
20609 >()));
20610
20611 if let Some((_color, ranges)) = highlights {
20612 ranges
20613 .iter()
20614 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20615 .collect_vec()
20616 } else {
20617 vec![]
20618 }
20619 }
20620
20621 fn document_highlights_for_position<'a>(
20622 &'a self,
20623 position: Anchor,
20624 buffer: &'a MultiBufferSnapshot,
20625 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20626 let read_highlights = self
20627 .background_highlights
20628 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20629 .map(|h| &h.1);
20630 let write_highlights = self
20631 .background_highlights
20632 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20633 .map(|h| &h.1);
20634 let left_position = position.bias_left(buffer);
20635 let right_position = position.bias_right(buffer);
20636 read_highlights
20637 .into_iter()
20638 .chain(write_highlights)
20639 .flat_map(move |ranges| {
20640 let start_ix = match ranges.binary_search_by(|probe| {
20641 let cmp = probe.end.cmp(&left_position, buffer);
20642 if cmp.is_ge() {
20643 Ordering::Greater
20644 } else {
20645 Ordering::Less
20646 }
20647 }) {
20648 Ok(i) | Err(i) => i,
20649 };
20650
20651 ranges[start_ix..]
20652 .iter()
20653 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20654 })
20655 }
20656
20657 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20658 self.background_highlights
20659 .get(&HighlightKey::Type(TypeId::of::<T>()))
20660 .is_some_and(|(_, highlights)| !highlights.is_empty())
20661 }
20662
20663 /// Returns all background highlights for a given range.
20664 ///
20665 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20666 pub fn background_highlights_in_range(
20667 &self,
20668 search_range: Range<Anchor>,
20669 display_snapshot: &DisplaySnapshot,
20670 theme: &Theme,
20671 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20672 let mut results = Vec::new();
20673 for (color_fetcher, ranges) in self.background_highlights.values() {
20674 let color = color_fetcher(theme);
20675 let start_ix = match ranges.binary_search_by(|probe| {
20676 let cmp = probe
20677 .end
20678 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20679 if cmp.is_gt() {
20680 Ordering::Greater
20681 } else {
20682 Ordering::Less
20683 }
20684 }) {
20685 Ok(i) | Err(i) => i,
20686 };
20687 for range in &ranges[start_ix..] {
20688 if range
20689 .start
20690 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20691 .is_ge()
20692 {
20693 break;
20694 }
20695
20696 let start = range.start.to_display_point(display_snapshot);
20697 let end = range.end.to_display_point(display_snapshot);
20698 results.push((start..end, color))
20699 }
20700 }
20701 results
20702 }
20703
20704 pub fn gutter_highlights_in_range(
20705 &self,
20706 search_range: Range<Anchor>,
20707 display_snapshot: &DisplaySnapshot,
20708 cx: &App,
20709 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20710 let mut results = Vec::new();
20711 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20712 let color = color_fetcher(cx);
20713 let start_ix = match ranges.binary_search_by(|probe| {
20714 let cmp = probe
20715 .end
20716 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20717 if cmp.is_gt() {
20718 Ordering::Greater
20719 } else {
20720 Ordering::Less
20721 }
20722 }) {
20723 Ok(i) | Err(i) => i,
20724 };
20725 for range in &ranges[start_ix..] {
20726 if range
20727 .start
20728 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20729 .is_ge()
20730 {
20731 break;
20732 }
20733
20734 let start = range.start.to_display_point(display_snapshot);
20735 let end = range.end.to_display_point(display_snapshot);
20736 results.push((start..end, color))
20737 }
20738 }
20739 results
20740 }
20741
20742 /// Get the text ranges corresponding to the redaction query
20743 pub fn redacted_ranges(
20744 &self,
20745 search_range: Range<Anchor>,
20746 display_snapshot: &DisplaySnapshot,
20747 cx: &App,
20748 ) -> Vec<Range<DisplayPoint>> {
20749 display_snapshot
20750 .buffer_snapshot()
20751 .redacted_ranges(search_range, |file| {
20752 if let Some(file) = file {
20753 file.is_private()
20754 && EditorSettings::get(
20755 Some(SettingsLocation {
20756 worktree_id: file.worktree_id(cx),
20757 path: file.path().as_ref(),
20758 }),
20759 cx,
20760 )
20761 .redact_private_values
20762 } else {
20763 false
20764 }
20765 })
20766 .map(|range| {
20767 range.start.to_display_point(display_snapshot)
20768 ..range.end.to_display_point(display_snapshot)
20769 })
20770 .collect()
20771 }
20772
20773 pub fn highlight_text_key<T: 'static>(
20774 &mut self,
20775 key: usize,
20776 ranges: Vec<Range<Anchor>>,
20777 style: HighlightStyle,
20778 cx: &mut Context<Self>,
20779 ) {
20780 self.display_map.update(cx, |map, _| {
20781 map.highlight_text(
20782 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20783 ranges,
20784 style,
20785 );
20786 });
20787 cx.notify();
20788 }
20789
20790 pub fn highlight_text<T: 'static>(
20791 &mut self,
20792 ranges: Vec<Range<Anchor>>,
20793 style: HighlightStyle,
20794 cx: &mut Context<Self>,
20795 ) {
20796 self.display_map.update(cx, |map, _| {
20797 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20798 });
20799 cx.notify();
20800 }
20801
20802 pub(crate) fn highlight_inlays<T: 'static>(
20803 &mut self,
20804 highlights: Vec<InlayHighlight>,
20805 style: HighlightStyle,
20806 cx: &mut Context<Self>,
20807 ) {
20808 self.display_map.update(cx, |map, _| {
20809 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20810 });
20811 cx.notify();
20812 }
20813
20814 pub fn text_highlights<'a, T: 'static>(
20815 &'a self,
20816 cx: &'a App,
20817 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20818 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20819 }
20820
20821 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20822 let cleared = self
20823 .display_map
20824 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20825 if cleared {
20826 cx.notify();
20827 }
20828 }
20829
20830 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20831 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20832 && self.focus_handle.is_focused(window)
20833 }
20834
20835 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20836 self.show_cursor_when_unfocused = is_enabled;
20837 cx.notify();
20838 }
20839
20840 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20841 cx.notify();
20842 }
20843
20844 fn on_debug_session_event(
20845 &mut self,
20846 _session: Entity<Session>,
20847 event: &SessionEvent,
20848 cx: &mut Context<Self>,
20849 ) {
20850 if let SessionEvent::InvalidateInlineValue = event {
20851 self.refresh_inline_values(cx);
20852 }
20853 }
20854
20855 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20856 let Some(project) = self.project.clone() else {
20857 return;
20858 };
20859
20860 if !self.inline_value_cache.enabled {
20861 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20862 self.splice_inlays(&inlays, Vec::new(), cx);
20863 return;
20864 }
20865
20866 let current_execution_position = self
20867 .highlighted_rows
20868 .get(&TypeId::of::<ActiveDebugLine>())
20869 .and_then(|lines| lines.last().map(|line| line.range.end));
20870
20871 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20872 let inline_values = editor
20873 .update(cx, |editor, cx| {
20874 let Some(current_execution_position) = current_execution_position else {
20875 return Some(Task::ready(Ok(Vec::new())));
20876 };
20877
20878 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20879 let snapshot = buffer.snapshot(cx);
20880
20881 let excerpt = snapshot.excerpt_containing(
20882 current_execution_position..current_execution_position,
20883 )?;
20884
20885 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20886 })?;
20887
20888 let range =
20889 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20890
20891 project.inline_values(buffer, range, cx)
20892 })
20893 .ok()
20894 .flatten()?
20895 .await
20896 .context("refreshing debugger inlays")
20897 .log_err()?;
20898
20899 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20900
20901 for (buffer_id, inline_value) in inline_values
20902 .into_iter()
20903 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20904 {
20905 buffer_inline_values
20906 .entry(buffer_id)
20907 .or_default()
20908 .push(inline_value);
20909 }
20910
20911 editor
20912 .update(cx, |editor, cx| {
20913 let snapshot = editor.buffer.read(cx).snapshot(cx);
20914 let mut new_inlays = Vec::default();
20915
20916 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20917 let buffer_id = buffer_snapshot.remote_id();
20918 buffer_inline_values
20919 .get(&buffer_id)
20920 .into_iter()
20921 .flatten()
20922 .for_each(|hint| {
20923 let inlay = Inlay::debugger(
20924 post_inc(&mut editor.next_inlay_id),
20925 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20926 hint.text(),
20927 );
20928 if !inlay.text().chars().contains(&'\n') {
20929 new_inlays.push(inlay);
20930 }
20931 });
20932 }
20933
20934 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20935 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20936
20937 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20938 })
20939 .ok()?;
20940 Some(())
20941 });
20942 }
20943
20944 fn on_buffer_event(
20945 &mut self,
20946 multibuffer: &Entity<MultiBuffer>,
20947 event: &multi_buffer::Event,
20948 window: &mut Window,
20949 cx: &mut Context<Self>,
20950 ) {
20951 match event {
20952 multi_buffer::Event::Edited { edited_buffer } => {
20953 self.scrollbar_marker_state.dirty = true;
20954 self.active_indent_guides_state.dirty = true;
20955 self.refresh_active_diagnostics(cx);
20956 self.refresh_code_actions(window, cx);
20957 self.refresh_selected_text_highlights(true, window, cx);
20958 self.refresh_single_line_folds(window, cx);
20959 refresh_matching_bracket_highlights(self, cx);
20960 if self.has_active_edit_prediction() {
20961 self.update_visible_edit_prediction(window, cx);
20962 }
20963
20964 if let Some(edited_buffer) = edited_buffer {
20965 if edited_buffer.read(cx).file().is_none() {
20966 cx.emit(EditorEvent::TitleChanged);
20967 }
20968
20969 let buffer_id = edited_buffer.read(cx).remote_id();
20970 if let Some(project) = self.project.clone() {
20971 self.register_buffer(buffer_id, cx);
20972 self.update_lsp_data(Some(buffer_id), window, cx);
20973 #[allow(clippy::mutable_key_type)]
20974 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20975 multibuffer
20976 .all_buffers()
20977 .into_iter()
20978 .filter_map(|buffer| {
20979 buffer.update(cx, |buffer, cx| {
20980 let language = buffer.language()?;
20981 let should_discard = project.update(cx, |project, cx| {
20982 project.is_local()
20983 && !project.has_language_servers_for(buffer, cx)
20984 });
20985 should_discard.not().then_some(language.clone())
20986 })
20987 })
20988 .collect::<HashSet<_>>()
20989 });
20990 if !languages_affected.is_empty() {
20991 self.refresh_inlay_hints(
20992 InlayHintRefreshReason::BufferEdited(languages_affected),
20993 cx,
20994 );
20995 }
20996 }
20997 }
20998
20999 cx.emit(EditorEvent::BufferEdited);
21000 cx.emit(SearchEvent::MatchesInvalidated);
21001
21002 let Some(project) = &self.project else { return };
21003 let (telemetry, is_via_ssh) = {
21004 let project = project.read(cx);
21005 let telemetry = project.client().telemetry().clone();
21006 let is_via_ssh = project.is_via_remote_server();
21007 (telemetry, is_via_ssh)
21008 };
21009 telemetry.log_edit_event("editor", is_via_ssh);
21010 }
21011 multi_buffer::Event::ExcerptsAdded {
21012 buffer,
21013 predecessor,
21014 excerpts,
21015 } => {
21016 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21017 let buffer_id = buffer.read(cx).remote_id();
21018 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21019 && let Some(project) = &self.project
21020 {
21021 update_uncommitted_diff_for_buffer(
21022 cx.entity(),
21023 project,
21024 [buffer.clone()],
21025 self.buffer.clone(),
21026 cx,
21027 )
21028 .detach();
21029 }
21030 self.update_lsp_data(Some(buffer_id), window, cx);
21031 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21032 cx.emit(EditorEvent::ExcerptsAdded {
21033 buffer: buffer.clone(),
21034 predecessor: *predecessor,
21035 excerpts: excerpts.clone(),
21036 });
21037 }
21038 multi_buffer::Event::ExcerptsRemoved {
21039 ids,
21040 removed_buffer_ids,
21041 } => {
21042 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21043 for buffer_id in removed_buffer_ids {
21044 self.registered_buffers.remove(buffer_id);
21045 }
21046 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21047 cx.emit(EditorEvent::ExcerptsRemoved {
21048 ids: ids.clone(),
21049 removed_buffer_ids: removed_buffer_ids.clone(),
21050 });
21051 }
21052 multi_buffer::Event::ExcerptsEdited {
21053 excerpt_ids,
21054 buffer_ids,
21055 } => {
21056 self.display_map.update(cx, |map, cx| {
21057 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21058 });
21059 cx.emit(EditorEvent::ExcerptsEdited {
21060 ids: excerpt_ids.clone(),
21061 });
21062 }
21063 multi_buffer::Event::ExcerptsExpanded { ids } => {
21064 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21065 self.refresh_document_highlights(cx);
21066 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21067 }
21068 multi_buffer::Event::Reparsed(buffer_id) => {
21069 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21070 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21071
21072 cx.emit(EditorEvent::Reparsed(*buffer_id));
21073 }
21074 multi_buffer::Event::DiffHunksToggled => {
21075 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21076 }
21077 multi_buffer::Event::LanguageChanged(buffer_id) => {
21078 self.registered_buffers.remove(&buffer_id);
21079 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21080 cx.emit(EditorEvent::Reparsed(*buffer_id));
21081 cx.notify();
21082 }
21083 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21084 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21085 multi_buffer::Event::FileHandleChanged
21086 | multi_buffer::Event::Reloaded
21087 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21088 multi_buffer::Event::DiagnosticsUpdated => {
21089 self.update_diagnostics_state(window, cx);
21090 }
21091 _ => {}
21092 };
21093 }
21094
21095 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21096 if !self.diagnostics_enabled() {
21097 return;
21098 }
21099 self.refresh_active_diagnostics(cx);
21100 self.refresh_inline_diagnostics(true, window, cx);
21101 self.scrollbar_marker_state.dirty = true;
21102 cx.notify();
21103 }
21104
21105 pub fn start_temporary_diff_override(&mut self) {
21106 self.load_diff_task.take();
21107 self.temporary_diff_override = true;
21108 }
21109
21110 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21111 self.temporary_diff_override = false;
21112 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21113 self.buffer.update(cx, |buffer, cx| {
21114 buffer.set_all_diff_hunks_collapsed(cx);
21115 });
21116
21117 if let Some(project) = self.project.clone() {
21118 self.load_diff_task = Some(
21119 update_uncommitted_diff_for_buffer(
21120 cx.entity(),
21121 &project,
21122 self.buffer.read(cx).all_buffers(),
21123 self.buffer.clone(),
21124 cx,
21125 )
21126 .shared(),
21127 );
21128 }
21129 }
21130
21131 fn on_display_map_changed(
21132 &mut self,
21133 _: Entity<DisplayMap>,
21134 _: &mut Window,
21135 cx: &mut Context<Self>,
21136 ) {
21137 cx.notify();
21138 }
21139
21140 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21141 if self.diagnostics_enabled() {
21142 let new_severity = EditorSettings::get_global(cx)
21143 .diagnostics_max_severity
21144 .unwrap_or(DiagnosticSeverity::Hint);
21145 self.set_max_diagnostics_severity(new_severity, cx);
21146 }
21147 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21148 self.update_edit_prediction_settings(cx);
21149 self.refresh_edit_prediction(true, false, window, cx);
21150 self.refresh_inline_values(cx);
21151 self.refresh_inlay_hints(
21152 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21153 self.selections.newest_anchor().head(),
21154 &self.buffer.read(cx).snapshot(cx),
21155 cx,
21156 )),
21157 cx,
21158 );
21159
21160 let old_cursor_shape = self.cursor_shape;
21161 let old_show_breadcrumbs = self.show_breadcrumbs;
21162
21163 {
21164 let editor_settings = EditorSettings::get_global(cx);
21165 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21166 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21167 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21168 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21169 }
21170
21171 if old_cursor_shape != self.cursor_shape {
21172 cx.emit(EditorEvent::CursorShapeChanged);
21173 }
21174
21175 if old_show_breadcrumbs != self.show_breadcrumbs {
21176 cx.emit(EditorEvent::BreadcrumbsChanged);
21177 }
21178
21179 let project_settings = ProjectSettings::get_global(cx);
21180 self.serialize_dirty_buffers =
21181 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21182
21183 if self.mode.is_full() {
21184 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21185 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21186 if self.show_inline_diagnostics != show_inline_diagnostics {
21187 self.show_inline_diagnostics = show_inline_diagnostics;
21188 self.refresh_inline_diagnostics(false, window, cx);
21189 }
21190
21191 if self.git_blame_inline_enabled != inline_blame_enabled {
21192 self.toggle_git_blame_inline_internal(false, window, cx);
21193 }
21194
21195 let minimap_settings = EditorSettings::get_global(cx).minimap;
21196 if self.minimap_visibility != MinimapVisibility::Disabled {
21197 if self.minimap_visibility.settings_visibility()
21198 != minimap_settings.minimap_enabled()
21199 {
21200 self.set_minimap_visibility(
21201 MinimapVisibility::for_mode(self.mode(), cx),
21202 window,
21203 cx,
21204 );
21205 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21206 minimap_entity.update(cx, |minimap_editor, cx| {
21207 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21208 })
21209 }
21210 }
21211 }
21212
21213 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21214 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21215 }) {
21216 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
21217 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21218 }
21219 self.refresh_colors_for_visible_range(None, window, cx);
21220 }
21221
21222 cx.notify();
21223 }
21224
21225 pub fn set_searchable(&mut self, searchable: bool) {
21226 self.searchable = searchable;
21227 }
21228
21229 pub fn searchable(&self) -> bool {
21230 self.searchable
21231 }
21232
21233 fn open_proposed_changes_editor(
21234 &mut self,
21235 _: &OpenProposedChangesEditor,
21236 window: &mut Window,
21237 cx: &mut Context<Self>,
21238 ) {
21239 let Some(workspace) = self.workspace() else {
21240 cx.propagate();
21241 return;
21242 };
21243
21244 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21245 let multi_buffer = self.buffer.read(cx);
21246 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21247 let mut new_selections_by_buffer = HashMap::default();
21248 for selection in selections {
21249 for (buffer, range, _) in
21250 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21251 {
21252 let mut range = range.to_point(buffer);
21253 range.start.column = 0;
21254 range.end.column = buffer.line_len(range.end.row);
21255 new_selections_by_buffer
21256 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21257 .or_insert(Vec::new())
21258 .push(range)
21259 }
21260 }
21261
21262 let proposed_changes_buffers = new_selections_by_buffer
21263 .into_iter()
21264 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21265 .collect::<Vec<_>>();
21266 let proposed_changes_editor = cx.new(|cx| {
21267 ProposedChangesEditor::new(
21268 "Proposed changes",
21269 proposed_changes_buffers,
21270 self.project.clone(),
21271 window,
21272 cx,
21273 )
21274 });
21275
21276 window.defer(cx, move |window, cx| {
21277 workspace.update(cx, |workspace, cx| {
21278 workspace.active_pane().update(cx, |pane, cx| {
21279 pane.add_item(
21280 Box::new(proposed_changes_editor),
21281 true,
21282 true,
21283 None,
21284 window,
21285 cx,
21286 );
21287 });
21288 });
21289 });
21290 }
21291
21292 pub fn open_excerpts_in_split(
21293 &mut self,
21294 _: &OpenExcerptsSplit,
21295 window: &mut Window,
21296 cx: &mut Context<Self>,
21297 ) {
21298 self.open_excerpts_common(None, true, window, cx)
21299 }
21300
21301 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21302 self.open_excerpts_common(None, false, window, cx)
21303 }
21304
21305 fn open_excerpts_common(
21306 &mut self,
21307 jump_data: Option<JumpData>,
21308 split: bool,
21309 window: &mut Window,
21310 cx: &mut Context<Self>,
21311 ) {
21312 let Some(workspace) = self.workspace() else {
21313 cx.propagate();
21314 return;
21315 };
21316
21317 if self.buffer.read(cx).is_singleton() {
21318 cx.propagate();
21319 return;
21320 }
21321
21322 let mut new_selections_by_buffer = HashMap::default();
21323 match &jump_data {
21324 Some(JumpData::MultiBufferPoint {
21325 excerpt_id,
21326 position,
21327 anchor,
21328 line_offset_from_top,
21329 }) => {
21330 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21331 if let Some(buffer) = multi_buffer_snapshot
21332 .buffer_id_for_excerpt(*excerpt_id)
21333 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21334 {
21335 let buffer_snapshot = buffer.read(cx).snapshot();
21336 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21337 language::ToPoint::to_point(anchor, &buffer_snapshot)
21338 } else {
21339 buffer_snapshot.clip_point(*position, Bias::Left)
21340 };
21341 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21342 new_selections_by_buffer.insert(
21343 buffer,
21344 (
21345 vec![jump_to_offset..jump_to_offset],
21346 Some(*line_offset_from_top),
21347 ),
21348 );
21349 }
21350 }
21351 Some(JumpData::MultiBufferRow {
21352 row,
21353 line_offset_from_top,
21354 }) => {
21355 let point = MultiBufferPoint::new(row.0, 0);
21356 if let Some((buffer, buffer_point, _)) =
21357 self.buffer.read(cx).point_to_buffer_point(point, cx)
21358 {
21359 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21360 new_selections_by_buffer
21361 .entry(buffer)
21362 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21363 .0
21364 .push(buffer_offset..buffer_offset)
21365 }
21366 }
21367 None => {
21368 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21369 let multi_buffer = self.buffer.read(cx);
21370 for selection in selections {
21371 for (snapshot, range, _, anchor) in multi_buffer
21372 .snapshot(cx)
21373 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21374 {
21375 if let Some(anchor) = anchor {
21376 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21377 else {
21378 continue;
21379 };
21380 let offset = text::ToOffset::to_offset(
21381 &anchor.text_anchor,
21382 &buffer_handle.read(cx).snapshot(),
21383 );
21384 let range = offset..offset;
21385 new_selections_by_buffer
21386 .entry(buffer_handle)
21387 .or_insert((Vec::new(), None))
21388 .0
21389 .push(range)
21390 } else {
21391 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21392 else {
21393 continue;
21394 };
21395 new_selections_by_buffer
21396 .entry(buffer_handle)
21397 .or_insert((Vec::new(), None))
21398 .0
21399 .push(range)
21400 }
21401 }
21402 }
21403 }
21404 }
21405
21406 new_selections_by_buffer
21407 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21408
21409 if new_selections_by_buffer.is_empty() {
21410 return;
21411 }
21412
21413 // We defer the pane interaction because we ourselves are a workspace item
21414 // and activating a new item causes the pane to call a method on us reentrantly,
21415 // which panics if we're on the stack.
21416 window.defer(cx, move |window, cx| {
21417 workspace.update(cx, |workspace, cx| {
21418 let pane = if split {
21419 workspace.adjacent_pane(window, cx)
21420 } else {
21421 workspace.active_pane().clone()
21422 };
21423
21424 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21425 let editor = buffer
21426 .read(cx)
21427 .file()
21428 .is_none()
21429 .then(|| {
21430 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21431 // so `workspace.open_project_item` will never find them, always opening a new editor.
21432 // Instead, we try to activate the existing editor in the pane first.
21433 let (editor, pane_item_index) =
21434 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21435 let editor = item.downcast::<Editor>()?;
21436 let singleton_buffer =
21437 editor.read(cx).buffer().read(cx).as_singleton()?;
21438 if singleton_buffer == buffer {
21439 Some((editor, i))
21440 } else {
21441 None
21442 }
21443 })?;
21444 pane.update(cx, |pane, cx| {
21445 pane.activate_item(pane_item_index, true, true, window, cx)
21446 });
21447 Some(editor)
21448 })
21449 .flatten()
21450 .unwrap_or_else(|| {
21451 workspace.open_project_item::<Self>(
21452 pane.clone(),
21453 buffer,
21454 true,
21455 true,
21456 window,
21457 cx,
21458 )
21459 });
21460
21461 editor.update(cx, |editor, cx| {
21462 let autoscroll = match scroll_offset {
21463 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21464 None => Autoscroll::newest(),
21465 };
21466 let nav_history = editor.nav_history.take();
21467 editor.change_selections(
21468 SelectionEffects::scroll(autoscroll),
21469 window,
21470 cx,
21471 |s| {
21472 s.select_ranges(ranges);
21473 },
21474 );
21475 editor.nav_history = nav_history;
21476 });
21477 }
21478 })
21479 });
21480 }
21481
21482 // For now, don't allow opening excerpts in buffers that aren't backed by
21483 // regular project files.
21484 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21485 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21486 }
21487
21488 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21489 let snapshot = self.buffer.read(cx).read(cx);
21490 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21491 Some(
21492 ranges
21493 .iter()
21494 .map(move |range| {
21495 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21496 })
21497 .collect(),
21498 )
21499 }
21500
21501 fn selection_replacement_ranges(
21502 &self,
21503 range: Range<OffsetUtf16>,
21504 cx: &mut App,
21505 ) -> Vec<Range<OffsetUtf16>> {
21506 let selections = self
21507 .selections
21508 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21509 let newest_selection = selections
21510 .iter()
21511 .max_by_key(|selection| selection.id)
21512 .unwrap();
21513 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21514 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21515 let snapshot = self.buffer.read(cx).read(cx);
21516 selections
21517 .into_iter()
21518 .map(|mut selection| {
21519 selection.start.0 =
21520 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21521 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21522 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21523 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21524 })
21525 .collect()
21526 }
21527
21528 fn report_editor_event(
21529 &self,
21530 reported_event: ReportEditorEvent,
21531 file_extension: Option<String>,
21532 cx: &App,
21533 ) {
21534 if cfg!(any(test, feature = "test-support")) {
21535 return;
21536 }
21537
21538 let Some(project) = &self.project else { return };
21539
21540 // If None, we are in a file without an extension
21541 let file = self
21542 .buffer
21543 .read(cx)
21544 .as_singleton()
21545 .and_then(|b| b.read(cx).file());
21546 let file_extension = file_extension.or(file
21547 .as_ref()
21548 .and_then(|file| Path::new(file.file_name(cx)).extension())
21549 .and_then(|e| e.to_str())
21550 .map(|a| a.to_string()));
21551
21552 let vim_mode = vim_enabled(cx);
21553
21554 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21555 let copilot_enabled = edit_predictions_provider
21556 == language::language_settings::EditPredictionProvider::Copilot;
21557 let copilot_enabled_for_language = self
21558 .buffer
21559 .read(cx)
21560 .language_settings(cx)
21561 .show_edit_predictions;
21562
21563 let project = project.read(cx);
21564 let event_type = reported_event.event_type();
21565
21566 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21567 telemetry::event!(
21568 event_type,
21569 type = if auto_saved {"autosave"} else {"manual"},
21570 file_extension,
21571 vim_mode,
21572 copilot_enabled,
21573 copilot_enabled_for_language,
21574 edit_predictions_provider,
21575 is_via_ssh = project.is_via_remote_server(),
21576 );
21577 } else {
21578 telemetry::event!(
21579 event_type,
21580 file_extension,
21581 vim_mode,
21582 copilot_enabled,
21583 copilot_enabled_for_language,
21584 edit_predictions_provider,
21585 is_via_ssh = project.is_via_remote_server(),
21586 );
21587 };
21588 }
21589
21590 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21591 /// with each line being an array of {text, highlight} objects.
21592 fn copy_highlight_json(
21593 &mut self,
21594 _: &CopyHighlightJson,
21595 window: &mut Window,
21596 cx: &mut Context<Self>,
21597 ) {
21598 #[derive(Serialize)]
21599 struct Chunk<'a> {
21600 text: String,
21601 highlight: Option<&'a str>,
21602 }
21603
21604 let snapshot = self.buffer.read(cx).snapshot(cx);
21605 let range = self
21606 .selected_text_range(false, window, cx)
21607 .and_then(|selection| {
21608 if selection.range.is_empty() {
21609 None
21610 } else {
21611 Some(selection.range)
21612 }
21613 })
21614 .unwrap_or_else(|| 0..snapshot.len());
21615
21616 let chunks = snapshot.chunks(range, true);
21617 let mut lines = Vec::new();
21618 let mut line: VecDeque<Chunk> = VecDeque::new();
21619
21620 let Some(style) = self.style.as_ref() else {
21621 return;
21622 };
21623
21624 for chunk in chunks {
21625 let highlight = chunk
21626 .syntax_highlight_id
21627 .and_then(|id| id.name(&style.syntax));
21628 let mut chunk_lines = chunk.text.split('\n').peekable();
21629 while let Some(text) = chunk_lines.next() {
21630 let mut merged_with_last_token = false;
21631 if let Some(last_token) = line.back_mut()
21632 && last_token.highlight == highlight
21633 {
21634 last_token.text.push_str(text);
21635 merged_with_last_token = true;
21636 }
21637
21638 if !merged_with_last_token {
21639 line.push_back(Chunk {
21640 text: text.into(),
21641 highlight,
21642 });
21643 }
21644
21645 if chunk_lines.peek().is_some() {
21646 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21647 line.pop_front();
21648 }
21649 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21650 line.pop_back();
21651 }
21652
21653 lines.push(mem::take(&mut line));
21654 }
21655 }
21656 }
21657
21658 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21659 return;
21660 };
21661 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21662 }
21663
21664 pub fn open_context_menu(
21665 &mut self,
21666 _: &OpenContextMenu,
21667 window: &mut Window,
21668 cx: &mut Context<Self>,
21669 ) {
21670 self.request_autoscroll(Autoscroll::newest(), cx);
21671 let position = self
21672 .selections
21673 .newest_display(&self.display_snapshot(cx))
21674 .start;
21675 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21676 }
21677
21678 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21679 &self.inlay_hint_cache
21680 }
21681
21682 pub fn replay_insert_event(
21683 &mut self,
21684 text: &str,
21685 relative_utf16_range: Option<Range<isize>>,
21686 window: &mut Window,
21687 cx: &mut Context<Self>,
21688 ) {
21689 if !self.input_enabled {
21690 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21691 return;
21692 }
21693 if let Some(relative_utf16_range) = relative_utf16_range {
21694 let selections = self
21695 .selections
21696 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21697 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21698 let new_ranges = selections.into_iter().map(|range| {
21699 let start = OffsetUtf16(
21700 range
21701 .head()
21702 .0
21703 .saturating_add_signed(relative_utf16_range.start),
21704 );
21705 let end = OffsetUtf16(
21706 range
21707 .head()
21708 .0
21709 .saturating_add_signed(relative_utf16_range.end),
21710 );
21711 start..end
21712 });
21713 s.select_ranges(new_ranges);
21714 });
21715 }
21716
21717 self.handle_input(text, window, cx);
21718 }
21719
21720 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21721 let Some(provider) = self.semantics_provider.as_ref() else {
21722 return false;
21723 };
21724
21725 let mut supports = false;
21726 self.buffer().update(cx, |this, cx| {
21727 this.for_each_buffer(|buffer| {
21728 supports |= provider.supports_inlay_hints(buffer, cx);
21729 });
21730 });
21731
21732 supports
21733 }
21734
21735 pub fn is_focused(&self, window: &Window) -> bool {
21736 self.focus_handle.is_focused(window)
21737 }
21738
21739 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21740 cx.emit(EditorEvent::Focused);
21741
21742 if let Some(descendant) = self
21743 .last_focused_descendant
21744 .take()
21745 .and_then(|descendant| descendant.upgrade())
21746 {
21747 window.focus(&descendant);
21748 } else {
21749 if let Some(blame) = self.blame.as_ref() {
21750 blame.update(cx, GitBlame::focus)
21751 }
21752
21753 self.blink_manager.update(cx, BlinkManager::enable);
21754 self.show_cursor_names(window, cx);
21755 self.buffer.update(cx, |buffer, cx| {
21756 buffer.finalize_last_transaction(cx);
21757 if self.leader_id.is_none() {
21758 buffer.set_active_selections(
21759 &self.selections.disjoint_anchors_arc(),
21760 self.selections.line_mode(),
21761 self.cursor_shape,
21762 cx,
21763 );
21764 }
21765 });
21766 }
21767 }
21768
21769 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21770 cx.emit(EditorEvent::FocusedIn)
21771 }
21772
21773 fn handle_focus_out(
21774 &mut self,
21775 event: FocusOutEvent,
21776 _window: &mut Window,
21777 cx: &mut Context<Self>,
21778 ) {
21779 if event.blurred != self.focus_handle {
21780 self.last_focused_descendant = Some(event.blurred);
21781 }
21782 self.selection_drag_state = SelectionDragState::None;
21783 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21784 }
21785
21786 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21787 self.blink_manager.update(cx, BlinkManager::disable);
21788 self.buffer
21789 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21790
21791 if let Some(blame) = self.blame.as_ref() {
21792 blame.update(cx, GitBlame::blur)
21793 }
21794 if !self.hover_state.focused(window, cx) {
21795 hide_hover(self, cx);
21796 }
21797 if !self
21798 .context_menu
21799 .borrow()
21800 .as_ref()
21801 .is_some_and(|context_menu| context_menu.focused(window, cx))
21802 {
21803 self.hide_context_menu(window, cx);
21804 }
21805 self.take_active_edit_prediction(cx);
21806 cx.emit(EditorEvent::Blurred);
21807 cx.notify();
21808 }
21809
21810 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21811 let mut pending: String = window
21812 .pending_input_keystrokes()
21813 .into_iter()
21814 .flatten()
21815 .filter_map(|keystroke| {
21816 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21817 keystroke.key_char.clone()
21818 } else {
21819 None
21820 }
21821 })
21822 .collect();
21823
21824 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21825 pending = "".to_string();
21826 }
21827
21828 let existing_pending = self
21829 .text_highlights::<PendingInput>(cx)
21830 .map(|(_, ranges)| ranges.to_vec());
21831 if existing_pending.is_none() && pending.is_empty() {
21832 return;
21833 }
21834 let transaction =
21835 self.transact(window, cx, |this, window, cx| {
21836 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21837 let edits = selections
21838 .iter()
21839 .map(|selection| (selection.end..selection.end, pending.clone()));
21840 this.edit(edits, cx);
21841 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21842 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21843 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21844 }));
21845 });
21846 if let Some(existing_ranges) = existing_pending {
21847 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21848 this.edit(edits, cx);
21849 }
21850 });
21851
21852 let snapshot = self.snapshot(window, cx);
21853 let ranges = self
21854 .selections
21855 .all::<usize>(&snapshot.display_snapshot)
21856 .into_iter()
21857 .map(|selection| {
21858 snapshot.buffer_snapshot().anchor_after(selection.end)
21859 ..snapshot
21860 .buffer_snapshot()
21861 .anchor_before(selection.end + pending.len())
21862 })
21863 .collect();
21864
21865 if pending.is_empty() {
21866 self.clear_highlights::<PendingInput>(cx);
21867 } else {
21868 self.highlight_text::<PendingInput>(
21869 ranges,
21870 HighlightStyle {
21871 underline: Some(UnderlineStyle {
21872 thickness: px(1.),
21873 color: None,
21874 wavy: false,
21875 }),
21876 ..Default::default()
21877 },
21878 cx,
21879 );
21880 }
21881
21882 self.ime_transaction = self.ime_transaction.or(transaction);
21883 if let Some(transaction) = self.ime_transaction {
21884 self.buffer.update(cx, |buffer, cx| {
21885 buffer.group_until_transaction(transaction, cx);
21886 });
21887 }
21888
21889 if self.text_highlights::<PendingInput>(cx).is_none() {
21890 self.ime_transaction.take();
21891 }
21892 }
21893
21894 pub fn register_action_renderer(
21895 &mut self,
21896 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21897 ) -> Subscription {
21898 let id = self.next_editor_action_id.post_inc();
21899 self.editor_actions
21900 .borrow_mut()
21901 .insert(id, Box::new(listener));
21902
21903 let editor_actions = self.editor_actions.clone();
21904 Subscription::new(move || {
21905 editor_actions.borrow_mut().remove(&id);
21906 })
21907 }
21908
21909 pub fn register_action<A: Action>(
21910 &mut self,
21911 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21912 ) -> Subscription {
21913 let id = self.next_editor_action_id.post_inc();
21914 let listener = Arc::new(listener);
21915 self.editor_actions.borrow_mut().insert(
21916 id,
21917 Box::new(move |_, window, _| {
21918 let listener = listener.clone();
21919 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21920 let action = action.downcast_ref().unwrap();
21921 if phase == DispatchPhase::Bubble {
21922 listener(action, window, cx)
21923 }
21924 })
21925 }),
21926 );
21927
21928 let editor_actions = self.editor_actions.clone();
21929 Subscription::new(move || {
21930 editor_actions.borrow_mut().remove(&id);
21931 })
21932 }
21933
21934 pub fn file_header_size(&self) -> u32 {
21935 FILE_HEADER_HEIGHT
21936 }
21937
21938 pub fn restore(
21939 &mut self,
21940 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21941 window: &mut Window,
21942 cx: &mut Context<Self>,
21943 ) {
21944 let workspace = self.workspace();
21945 let project = self.project();
21946 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21947 let mut tasks = Vec::new();
21948 for (buffer_id, changes) in revert_changes {
21949 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21950 buffer.update(cx, |buffer, cx| {
21951 buffer.edit(
21952 changes
21953 .into_iter()
21954 .map(|(range, text)| (range, text.to_string())),
21955 None,
21956 cx,
21957 );
21958 });
21959
21960 if let Some(project) =
21961 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21962 {
21963 project.update(cx, |project, cx| {
21964 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21965 })
21966 }
21967 }
21968 }
21969 tasks
21970 });
21971 cx.spawn_in(window, async move |_, cx| {
21972 for (buffer, task) in save_tasks {
21973 let result = task.await;
21974 if result.is_err() {
21975 let Some(path) = buffer
21976 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21977 .ok()
21978 else {
21979 continue;
21980 };
21981 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21982 let Some(task) = cx
21983 .update_window_entity(workspace, |workspace, window, cx| {
21984 workspace
21985 .open_path_preview(path, None, false, false, false, window, cx)
21986 })
21987 .ok()
21988 else {
21989 continue;
21990 };
21991 task.await.log_err();
21992 }
21993 }
21994 }
21995 })
21996 .detach();
21997 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21998 selections.refresh()
21999 });
22000 }
22001
22002 pub fn to_pixel_point(
22003 &self,
22004 source: multi_buffer::Anchor,
22005 editor_snapshot: &EditorSnapshot,
22006 window: &mut Window,
22007 ) -> Option<gpui::Point<Pixels>> {
22008 let source_point = source.to_display_point(editor_snapshot);
22009 self.display_to_pixel_point(source_point, editor_snapshot, window)
22010 }
22011
22012 pub fn display_to_pixel_point(
22013 &self,
22014 source: DisplayPoint,
22015 editor_snapshot: &EditorSnapshot,
22016 window: &mut Window,
22017 ) -> Option<gpui::Point<Pixels>> {
22018 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22019 let text_layout_details = self.text_layout_details(window);
22020 let scroll_top = text_layout_details
22021 .scroll_anchor
22022 .scroll_position(editor_snapshot)
22023 .y;
22024
22025 if source.row().as_f64() < scroll_top.floor() {
22026 return None;
22027 }
22028 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22029 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22030 Some(gpui::Point::new(source_x, source_y))
22031 }
22032
22033 pub fn has_visible_completions_menu(&self) -> bool {
22034 !self.edit_prediction_preview_is_active()
22035 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22036 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22037 })
22038 }
22039
22040 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22041 if self.mode.is_minimap() {
22042 return;
22043 }
22044 self.addons
22045 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22046 }
22047
22048 pub fn unregister_addon<T: Addon>(&mut self) {
22049 self.addons.remove(&std::any::TypeId::of::<T>());
22050 }
22051
22052 pub fn addon<T: Addon>(&self) -> Option<&T> {
22053 let type_id = std::any::TypeId::of::<T>();
22054 self.addons
22055 .get(&type_id)
22056 .and_then(|item| item.to_any().downcast_ref::<T>())
22057 }
22058
22059 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22060 let type_id = std::any::TypeId::of::<T>();
22061 self.addons
22062 .get_mut(&type_id)
22063 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22064 }
22065
22066 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22067 let text_layout_details = self.text_layout_details(window);
22068 let style = &text_layout_details.editor_style;
22069 let font_id = window.text_system().resolve_font(&style.text.font());
22070 let font_size = style.text.font_size.to_pixels(window.rem_size());
22071 let line_height = style.text.line_height_in_pixels(window.rem_size());
22072 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22073 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22074
22075 CharacterDimensions {
22076 em_width,
22077 em_advance,
22078 line_height,
22079 }
22080 }
22081
22082 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22083 self.load_diff_task.clone()
22084 }
22085
22086 fn read_metadata_from_db(
22087 &mut self,
22088 item_id: u64,
22089 workspace_id: WorkspaceId,
22090 window: &mut Window,
22091 cx: &mut Context<Editor>,
22092 ) {
22093 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22094 && !self.mode.is_minimap()
22095 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22096 {
22097 let buffer_snapshot = OnceCell::new();
22098
22099 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22100 && !folds.is_empty()
22101 {
22102 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22103 self.fold_ranges(
22104 folds
22105 .into_iter()
22106 .map(|(start, end)| {
22107 snapshot.clip_offset(start, Bias::Left)
22108 ..snapshot.clip_offset(end, Bias::Right)
22109 })
22110 .collect(),
22111 false,
22112 window,
22113 cx,
22114 );
22115 }
22116
22117 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22118 && !selections.is_empty()
22119 {
22120 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22121 // skip adding the initial selection to selection history
22122 self.selection_history.mode = SelectionHistoryMode::Skipping;
22123 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22124 s.select_ranges(selections.into_iter().map(|(start, end)| {
22125 snapshot.clip_offset(start, Bias::Left)
22126 ..snapshot.clip_offset(end, Bias::Right)
22127 }));
22128 });
22129 self.selection_history.mode = SelectionHistoryMode::Normal;
22130 };
22131 }
22132
22133 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22134 }
22135
22136 fn update_lsp_data(
22137 &mut self,
22138 for_buffer: Option<BufferId>,
22139 window: &mut Window,
22140 cx: &mut Context<'_, Self>,
22141 ) {
22142 self.pull_diagnostics(for_buffer, window, cx);
22143 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22144 }
22145
22146 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22147 if self.ignore_lsp_data() {
22148 return;
22149 }
22150 for (_, (visible_buffer, _, _)) in self.visible_excerpts(None, cx) {
22151 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22152 }
22153 }
22154
22155 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) -> bool {
22156 if !self.registered_buffers.contains_key(&buffer_id)
22157 && let Some(project) = self.project.as_ref()
22158 {
22159 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22160 project.update(cx, |project, cx| {
22161 self.registered_buffers.insert(
22162 buffer_id,
22163 project.register_buffer_with_language_servers(&buffer, cx),
22164 );
22165 });
22166 return true;
22167 } else {
22168 self.registered_buffers.remove(&buffer_id);
22169 }
22170 }
22171
22172 false
22173 }
22174
22175 fn ignore_lsp_data(&self) -> bool {
22176 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22177 // skip any LSP updates for it.
22178 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22179 }
22180}
22181
22182fn edit_for_markdown_paste<'a>(
22183 buffer: &MultiBufferSnapshot,
22184 range: Range<usize>,
22185 to_insert: &'a str,
22186 url: Option<url::Url>,
22187) -> (Range<usize>, Cow<'a, str>) {
22188 if url.is_none() {
22189 return (range, Cow::Borrowed(to_insert));
22190 };
22191
22192 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22193
22194 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22195 Cow::Borrowed(to_insert)
22196 } else {
22197 Cow::Owned(format!("[{old_text}]({to_insert})"))
22198 };
22199 (range, new_text)
22200}
22201
22202fn vim_enabled(cx: &App) -> bool {
22203 vim_mode_setting::VimModeSetting::try_get(cx)
22204 .map(|vim_mode| vim_mode.0)
22205 .unwrap_or(false)
22206}
22207
22208fn process_completion_for_edit(
22209 completion: &Completion,
22210 intent: CompletionIntent,
22211 buffer: &Entity<Buffer>,
22212 cursor_position: &text::Anchor,
22213 cx: &mut Context<Editor>,
22214) -> CompletionEdit {
22215 let buffer = buffer.read(cx);
22216 let buffer_snapshot = buffer.snapshot();
22217 let (snippet, new_text) = if completion.is_snippet() {
22218 let mut snippet_source = completion.new_text.clone();
22219 // Workaround for typescript language server issues so that methods don't expand within
22220 // strings and functions with type expressions. The previous point is used because the query
22221 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22222 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22223 let previous_point = if previous_point.column > 0 {
22224 cursor_position.to_previous_offset(&buffer_snapshot)
22225 } else {
22226 cursor_position.to_offset(&buffer_snapshot)
22227 };
22228 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22229 && scope.prefers_label_for_snippet_in_completion()
22230 && let Some(label) = completion.label()
22231 && matches!(
22232 completion.kind(),
22233 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22234 )
22235 {
22236 snippet_source = label;
22237 }
22238 match Snippet::parse(&snippet_source).log_err() {
22239 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22240 None => (None, completion.new_text.clone()),
22241 }
22242 } else {
22243 (None, completion.new_text.clone())
22244 };
22245
22246 let mut range_to_replace = {
22247 let replace_range = &completion.replace_range;
22248 if let CompletionSource::Lsp {
22249 insert_range: Some(insert_range),
22250 ..
22251 } = &completion.source
22252 {
22253 debug_assert_eq!(
22254 insert_range.start, replace_range.start,
22255 "insert_range and replace_range should start at the same position"
22256 );
22257 debug_assert!(
22258 insert_range
22259 .start
22260 .cmp(cursor_position, &buffer_snapshot)
22261 .is_le(),
22262 "insert_range should start before or at cursor position"
22263 );
22264 debug_assert!(
22265 replace_range
22266 .start
22267 .cmp(cursor_position, &buffer_snapshot)
22268 .is_le(),
22269 "replace_range should start before or at cursor position"
22270 );
22271
22272 let should_replace = match intent {
22273 CompletionIntent::CompleteWithInsert => false,
22274 CompletionIntent::CompleteWithReplace => true,
22275 CompletionIntent::Complete | CompletionIntent::Compose => {
22276 let insert_mode =
22277 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22278 .completions
22279 .lsp_insert_mode;
22280 match insert_mode {
22281 LspInsertMode::Insert => false,
22282 LspInsertMode::Replace => true,
22283 LspInsertMode::ReplaceSubsequence => {
22284 let mut text_to_replace = buffer.chars_for_range(
22285 buffer.anchor_before(replace_range.start)
22286 ..buffer.anchor_after(replace_range.end),
22287 );
22288 let mut current_needle = text_to_replace.next();
22289 for haystack_ch in completion.label.text.chars() {
22290 if let Some(needle_ch) = current_needle
22291 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22292 {
22293 current_needle = text_to_replace.next();
22294 }
22295 }
22296 current_needle.is_none()
22297 }
22298 LspInsertMode::ReplaceSuffix => {
22299 if replace_range
22300 .end
22301 .cmp(cursor_position, &buffer_snapshot)
22302 .is_gt()
22303 {
22304 let range_after_cursor = *cursor_position..replace_range.end;
22305 let text_after_cursor = buffer
22306 .text_for_range(
22307 buffer.anchor_before(range_after_cursor.start)
22308 ..buffer.anchor_after(range_after_cursor.end),
22309 )
22310 .collect::<String>()
22311 .to_ascii_lowercase();
22312 completion
22313 .label
22314 .text
22315 .to_ascii_lowercase()
22316 .ends_with(&text_after_cursor)
22317 } else {
22318 true
22319 }
22320 }
22321 }
22322 }
22323 };
22324
22325 if should_replace {
22326 replace_range.clone()
22327 } else {
22328 insert_range.clone()
22329 }
22330 } else {
22331 replace_range.clone()
22332 }
22333 };
22334
22335 if range_to_replace
22336 .end
22337 .cmp(cursor_position, &buffer_snapshot)
22338 .is_lt()
22339 {
22340 range_to_replace.end = *cursor_position;
22341 }
22342
22343 CompletionEdit {
22344 new_text,
22345 replace_range: range_to_replace.to_offset(buffer),
22346 snippet,
22347 }
22348}
22349
22350struct CompletionEdit {
22351 new_text: String,
22352 replace_range: Range<usize>,
22353 snippet: Option<Snippet>,
22354}
22355
22356fn insert_extra_newline_brackets(
22357 buffer: &MultiBufferSnapshot,
22358 range: Range<usize>,
22359 language: &language::LanguageScope,
22360) -> bool {
22361 let leading_whitespace_len = buffer
22362 .reversed_chars_at(range.start)
22363 .take_while(|c| c.is_whitespace() && *c != '\n')
22364 .map(|c| c.len_utf8())
22365 .sum::<usize>();
22366 let trailing_whitespace_len = buffer
22367 .chars_at(range.end)
22368 .take_while(|c| c.is_whitespace() && *c != '\n')
22369 .map(|c| c.len_utf8())
22370 .sum::<usize>();
22371 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22372
22373 language.brackets().any(|(pair, enabled)| {
22374 let pair_start = pair.start.trim_end();
22375 let pair_end = pair.end.trim_start();
22376
22377 enabled
22378 && pair.newline
22379 && buffer.contains_str_at(range.end, pair_end)
22380 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22381 })
22382}
22383
22384fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22385 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22386 [(buffer, range, _)] => (*buffer, range.clone()),
22387 _ => return false,
22388 };
22389 let pair = {
22390 let mut result: Option<BracketMatch> = None;
22391
22392 for pair in buffer
22393 .all_bracket_ranges(range.clone())
22394 .filter(move |pair| {
22395 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22396 })
22397 {
22398 let len = pair.close_range.end - pair.open_range.start;
22399
22400 if let Some(existing) = &result {
22401 let existing_len = existing.close_range.end - existing.open_range.start;
22402 if len > existing_len {
22403 continue;
22404 }
22405 }
22406
22407 result = Some(pair);
22408 }
22409
22410 result
22411 };
22412 let Some(pair) = pair else {
22413 return false;
22414 };
22415 pair.newline_only
22416 && buffer
22417 .chars_for_range(pair.open_range.end..range.start)
22418 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22419 .all(|c| c.is_whitespace() && c != '\n')
22420}
22421
22422fn update_uncommitted_diff_for_buffer(
22423 editor: Entity<Editor>,
22424 project: &Entity<Project>,
22425 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22426 buffer: Entity<MultiBuffer>,
22427 cx: &mut App,
22428) -> Task<()> {
22429 let mut tasks = Vec::new();
22430 project.update(cx, |project, cx| {
22431 for buffer in buffers {
22432 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22433 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22434 }
22435 }
22436 });
22437 cx.spawn(async move |cx| {
22438 let diffs = future::join_all(tasks).await;
22439 if editor
22440 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22441 .unwrap_or(false)
22442 {
22443 return;
22444 }
22445
22446 buffer
22447 .update(cx, |buffer, cx| {
22448 for diff in diffs.into_iter().flatten() {
22449 buffer.add_diff(diff, cx);
22450 }
22451 })
22452 .ok();
22453 })
22454}
22455
22456fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22457 let tab_size = tab_size.get() as usize;
22458 let mut width = offset;
22459
22460 for ch in text.chars() {
22461 width += if ch == '\t' {
22462 tab_size - (width % tab_size)
22463 } else {
22464 1
22465 };
22466 }
22467
22468 width - offset
22469}
22470
22471#[cfg(test)]
22472mod tests {
22473 use super::*;
22474
22475 #[test]
22476 fn test_string_size_with_expanded_tabs() {
22477 let nz = |val| NonZeroU32::new(val).unwrap();
22478 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22479 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22480 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22481 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22482 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22483 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22484 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22485 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22486 }
22487}
22488
22489/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22490struct WordBreakingTokenizer<'a> {
22491 input: &'a str,
22492}
22493
22494impl<'a> WordBreakingTokenizer<'a> {
22495 fn new(input: &'a str) -> Self {
22496 Self { input }
22497 }
22498}
22499
22500fn is_char_ideographic(ch: char) -> bool {
22501 use unicode_script::Script::*;
22502 use unicode_script::UnicodeScript;
22503 matches!(ch.script(), Han | Tangut | Yi)
22504}
22505
22506fn is_grapheme_ideographic(text: &str) -> bool {
22507 text.chars().any(is_char_ideographic)
22508}
22509
22510fn is_grapheme_whitespace(text: &str) -> bool {
22511 text.chars().any(|x| x.is_whitespace())
22512}
22513
22514fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22515 text.chars()
22516 .next()
22517 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22518}
22519
22520#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22521enum WordBreakToken<'a> {
22522 Word { token: &'a str, grapheme_len: usize },
22523 InlineWhitespace { token: &'a str, grapheme_len: usize },
22524 Newline,
22525}
22526
22527impl<'a> Iterator for WordBreakingTokenizer<'a> {
22528 /// Yields a span, the count of graphemes in the token, and whether it was
22529 /// whitespace. Note that it also breaks at word boundaries.
22530 type Item = WordBreakToken<'a>;
22531
22532 fn next(&mut self) -> Option<Self::Item> {
22533 use unicode_segmentation::UnicodeSegmentation;
22534 if self.input.is_empty() {
22535 return None;
22536 }
22537
22538 let mut iter = self.input.graphemes(true).peekable();
22539 let mut offset = 0;
22540 let mut grapheme_len = 0;
22541 if let Some(first_grapheme) = iter.next() {
22542 let is_newline = first_grapheme == "\n";
22543 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22544 offset += first_grapheme.len();
22545 grapheme_len += 1;
22546 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22547 if let Some(grapheme) = iter.peek().copied()
22548 && should_stay_with_preceding_ideograph(grapheme)
22549 {
22550 offset += grapheme.len();
22551 grapheme_len += 1;
22552 }
22553 } else {
22554 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22555 let mut next_word_bound = words.peek().copied();
22556 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22557 next_word_bound = words.next();
22558 }
22559 while let Some(grapheme) = iter.peek().copied() {
22560 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22561 break;
22562 };
22563 if is_grapheme_whitespace(grapheme) != is_whitespace
22564 || (grapheme == "\n") != is_newline
22565 {
22566 break;
22567 };
22568 offset += grapheme.len();
22569 grapheme_len += 1;
22570 iter.next();
22571 }
22572 }
22573 let token = &self.input[..offset];
22574 self.input = &self.input[offset..];
22575 if token == "\n" {
22576 Some(WordBreakToken::Newline)
22577 } else if is_whitespace {
22578 Some(WordBreakToken::InlineWhitespace {
22579 token,
22580 grapheme_len,
22581 })
22582 } else {
22583 Some(WordBreakToken::Word {
22584 token,
22585 grapheme_len,
22586 })
22587 }
22588 } else {
22589 None
22590 }
22591 }
22592}
22593
22594#[test]
22595fn test_word_breaking_tokenizer() {
22596 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22597 ("", &[]),
22598 (" ", &[whitespace(" ", 2)]),
22599 ("Ʒ", &[word("Ʒ", 1)]),
22600 ("Ǽ", &[word("Ǽ", 1)]),
22601 ("⋑", &[word("⋑", 1)]),
22602 ("⋑⋑", &[word("⋑⋑", 2)]),
22603 (
22604 "原理,进而",
22605 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22606 ),
22607 (
22608 "hello world",
22609 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22610 ),
22611 (
22612 "hello, world",
22613 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22614 ),
22615 (
22616 " hello world",
22617 &[
22618 whitespace(" ", 2),
22619 word("hello", 5),
22620 whitespace(" ", 1),
22621 word("world", 5),
22622 ],
22623 ),
22624 (
22625 "这是什么 \n 钢笔",
22626 &[
22627 word("这", 1),
22628 word("是", 1),
22629 word("什", 1),
22630 word("么", 1),
22631 whitespace(" ", 1),
22632 newline(),
22633 whitespace(" ", 1),
22634 word("钢", 1),
22635 word("笔", 1),
22636 ],
22637 ),
22638 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22639 ];
22640
22641 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22642 WordBreakToken::Word {
22643 token,
22644 grapheme_len,
22645 }
22646 }
22647
22648 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22649 WordBreakToken::InlineWhitespace {
22650 token,
22651 grapheme_len,
22652 }
22653 }
22654
22655 fn newline() -> WordBreakToken<'static> {
22656 WordBreakToken::Newline
22657 }
22658
22659 for (input, result) in tests {
22660 assert_eq!(
22661 WordBreakingTokenizer::new(input)
22662 .collect::<Vec<_>>()
22663 .as_slice(),
22664 *result,
22665 );
22666 }
22667}
22668
22669fn wrap_with_prefix(
22670 first_line_prefix: String,
22671 subsequent_lines_prefix: String,
22672 unwrapped_text: String,
22673 wrap_column: usize,
22674 tab_size: NonZeroU32,
22675 preserve_existing_whitespace: bool,
22676) -> String {
22677 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22678 let subsequent_lines_prefix_len =
22679 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22680 let mut wrapped_text = String::new();
22681 let mut current_line = first_line_prefix;
22682 let mut is_first_line = true;
22683
22684 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22685 let mut current_line_len = first_line_prefix_len;
22686 let mut in_whitespace = false;
22687 for token in tokenizer {
22688 let have_preceding_whitespace = in_whitespace;
22689 match token {
22690 WordBreakToken::Word {
22691 token,
22692 grapheme_len,
22693 } => {
22694 in_whitespace = false;
22695 let current_prefix_len = if is_first_line {
22696 first_line_prefix_len
22697 } else {
22698 subsequent_lines_prefix_len
22699 };
22700 if current_line_len + grapheme_len > wrap_column
22701 && current_line_len != current_prefix_len
22702 {
22703 wrapped_text.push_str(current_line.trim_end());
22704 wrapped_text.push('\n');
22705 is_first_line = false;
22706 current_line = subsequent_lines_prefix.clone();
22707 current_line_len = subsequent_lines_prefix_len;
22708 }
22709 current_line.push_str(token);
22710 current_line_len += grapheme_len;
22711 }
22712 WordBreakToken::InlineWhitespace {
22713 mut token,
22714 mut grapheme_len,
22715 } => {
22716 in_whitespace = true;
22717 if have_preceding_whitespace && !preserve_existing_whitespace {
22718 continue;
22719 }
22720 if !preserve_existing_whitespace {
22721 // Keep a single whitespace grapheme as-is
22722 if let Some(first) =
22723 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22724 {
22725 token = first;
22726 } else {
22727 token = " ";
22728 }
22729 grapheme_len = 1;
22730 }
22731 let current_prefix_len = if is_first_line {
22732 first_line_prefix_len
22733 } else {
22734 subsequent_lines_prefix_len
22735 };
22736 if current_line_len + grapheme_len > wrap_column {
22737 wrapped_text.push_str(current_line.trim_end());
22738 wrapped_text.push('\n');
22739 is_first_line = false;
22740 current_line = subsequent_lines_prefix.clone();
22741 current_line_len = subsequent_lines_prefix_len;
22742 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22743 current_line.push_str(token);
22744 current_line_len += grapheme_len;
22745 }
22746 }
22747 WordBreakToken::Newline => {
22748 in_whitespace = true;
22749 let current_prefix_len = if is_first_line {
22750 first_line_prefix_len
22751 } else {
22752 subsequent_lines_prefix_len
22753 };
22754 if preserve_existing_whitespace {
22755 wrapped_text.push_str(current_line.trim_end());
22756 wrapped_text.push('\n');
22757 is_first_line = false;
22758 current_line = subsequent_lines_prefix.clone();
22759 current_line_len = subsequent_lines_prefix_len;
22760 } else if have_preceding_whitespace {
22761 continue;
22762 } else if current_line_len + 1 > wrap_column
22763 && current_line_len != current_prefix_len
22764 {
22765 wrapped_text.push_str(current_line.trim_end());
22766 wrapped_text.push('\n');
22767 is_first_line = false;
22768 current_line = subsequent_lines_prefix.clone();
22769 current_line_len = subsequent_lines_prefix_len;
22770 } else if current_line_len != current_prefix_len {
22771 current_line.push(' ');
22772 current_line_len += 1;
22773 }
22774 }
22775 }
22776 }
22777
22778 if !current_line.is_empty() {
22779 wrapped_text.push_str(¤t_line);
22780 }
22781 wrapped_text
22782}
22783
22784#[test]
22785fn test_wrap_with_prefix() {
22786 assert_eq!(
22787 wrap_with_prefix(
22788 "# ".to_string(),
22789 "# ".to_string(),
22790 "abcdefg".to_string(),
22791 4,
22792 NonZeroU32::new(4).unwrap(),
22793 false,
22794 ),
22795 "# abcdefg"
22796 );
22797 assert_eq!(
22798 wrap_with_prefix(
22799 "".to_string(),
22800 "".to_string(),
22801 "\thello world".to_string(),
22802 8,
22803 NonZeroU32::new(4).unwrap(),
22804 false,
22805 ),
22806 "hello\nworld"
22807 );
22808 assert_eq!(
22809 wrap_with_prefix(
22810 "// ".to_string(),
22811 "// ".to_string(),
22812 "xx \nyy zz aa bb cc".to_string(),
22813 12,
22814 NonZeroU32::new(4).unwrap(),
22815 false,
22816 ),
22817 "// xx yy zz\n// aa bb cc"
22818 );
22819 assert_eq!(
22820 wrap_with_prefix(
22821 String::new(),
22822 String::new(),
22823 "这是什么 \n 钢笔".to_string(),
22824 3,
22825 NonZeroU32::new(4).unwrap(),
22826 false,
22827 ),
22828 "这是什\n么 钢\n笔"
22829 );
22830 assert_eq!(
22831 wrap_with_prefix(
22832 String::new(),
22833 String::new(),
22834 format!("foo{}bar", '\u{2009}'), // thin space
22835 80,
22836 NonZeroU32::new(4).unwrap(),
22837 false,
22838 ),
22839 format!("foo{}bar", '\u{2009}')
22840 );
22841}
22842
22843pub trait CollaborationHub {
22844 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22845 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22846 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22847}
22848
22849impl CollaborationHub for Entity<Project> {
22850 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22851 self.read(cx).collaborators()
22852 }
22853
22854 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22855 self.read(cx).user_store().read(cx).participant_indices()
22856 }
22857
22858 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22859 let this = self.read(cx);
22860 let user_ids = this.collaborators().values().map(|c| c.user_id);
22861 this.user_store().read(cx).participant_names(user_ids, cx)
22862 }
22863}
22864
22865pub trait SemanticsProvider {
22866 fn hover(
22867 &self,
22868 buffer: &Entity<Buffer>,
22869 position: text::Anchor,
22870 cx: &mut App,
22871 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22872
22873 fn inline_values(
22874 &self,
22875 buffer_handle: Entity<Buffer>,
22876 range: Range<text::Anchor>,
22877 cx: &mut App,
22878 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22879
22880 fn inlay_hints(
22881 &self,
22882 buffer_handle: Entity<Buffer>,
22883 range: Range<text::Anchor>,
22884 cx: &mut App,
22885 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22886
22887 fn resolve_inlay_hint(
22888 &self,
22889 hint: InlayHint,
22890 buffer_handle: Entity<Buffer>,
22891 server_id: LanguageServerId,
22892 cx: &mut App,
22893 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22894
22895 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22896
22897 fn document_highlights(
22898 &self,
22899 buffer: &Entity<Buffer>,
22900 position: text::Anchor,
22901 cx: &mut App,
22902 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22903
22904 fn definitions(
22905 &self,
22906 buffer: &Entity<Buffer>,
22907 position: text::Anchor,
22908 kind: GotoDefinitionKind,
22909 cx: &mut App,
22910 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22911
22912 fn range_for_rename(
22913 &self,
22914 buffer: &Entity<Buffer>,
22915 position: text::Anchor,
22916 cx: &mut App,
22917 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22918
22919 fn perform_rename(
22920 &self,
22921 buffer: &Entity<Buffer>,
22922 position: text::Anchor,
22923 new_name: String,
22924 cx: &mut App,
22925 ) -> Option<Task<Result<ProjectTransaction>>>;
22926}
22927
22928pub trait CompletionProvider {
22929 fn completions(
22930 &self,
22931 excerpt_id: ExcerptId,
22932 buffer: &Entity<Buffer>,
22933 buffer_position: text::Anchor,
22934 trigger: CompletionContext,
22935 window: &mut Window,
22936 cx: &mut Context<Editor>,
22937 ) -> Task<Result<Vec<CompletionResponse>>>;
22938
22939 fn resolve_completions(
22940 &self,
22941 _buffer: Entity<Buffer>,
22942 _completion_indices: Vec<usize>,
22943 _completions: Rc<RefCell<Box<[Completion]>>>,
22944 _cx: &mut Context<Editor>,
22945 ) -> Task<Result<bool>> {
22946 Task::ready(Ok(false))
22947 }
22948
22949 fn apply_additional_edits_for_completion(
22950 &self,
22951 _buffer: Entity<Buffer>,
22952 _completions: Rc<RefCell<Box<[Completion]>>>,
22953 _completion_index: usize,
22954 _push_to_history: bool,
22955 _cx: &mut Context<Editor>,
22956 ) -> Task<Result<Option<language::Transaction>>> {
22957 Task::ready(Ok(None))
22958 }
22959
22960 fn is_completion_trigger(
22961 &self,
22962 buffer: &Entity<Buffer>,
22963 position: language::Anchor,
22964 text: &str,
22965 trigger_in_words: bool,
22966 menu_is_open: bool,
22967 cx: &mut Context<Editor>,
22968 ) -> bool;
22969
22970 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22971
22972 fn sort_completions(&self) -> bool {
22973 true
22974 }
22975
22976 fn filter_completions(&self) -> bool {
22977 true
22978 }
22979}
22980
22981pub trait CodeActionProvider {
22982 fn id(&self) -> Arc<str>;
22983
22984 fn code_actions(
22985 &self,
22986 buffer: &Entity<Buffer>,
22987 range: Range<text::Anchor>,
22988 window: &mut Window,
22989 cx: &mut App,
22990 ) -> Task<Result<Vec<CodeAction>>>;
22991
22992 fn apply_code_action(
22993 &self,
22994 buffer_handle: Entity<Buffer>,
22995 action: CodeAction,
22996 excerpt_id: ExcerptId,
22997 push_to_history: bool,
22998 window: &mut Window,
22999 cx: &mut App,
23000 ) -> Task<Result<ProjectTransaction>>;
23001}
23002
23003impl CodeActionProvider for Entity<Project> {
23004 fn id(&self) -> Arc<str> {
23005 "project".into()
23006 }
23007
23008 fn code_actions(
23009 &self,
23010 buffer: &Entity<Buffer>,
23011 range: Range<text::Anchor>,
23012 _window: &mut Window,
23013 cx: &mut App,
23014 ) -> Task<Result<Vec<CodeAction>>> {
23015 self.update(cx, |project, cx| {
23016 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23017 let code_actions = project.code_actions(buffer, range, None, cx);
23018 cx.background_spawn(async move {
23019 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23020 Ok(code_lens_actions
23021 .context("code lens fetch")?
23022 .into_iter()
23023 .flatten()
23024 .chain(
23025 code_actions
23026 .context("code action fetch")?
23027 .into_iter()
23028 .flatten(),
23029 )
23030 .collect())
23031 })
23032 })
23033 }
23034
23035 fn apply_code_action(
23036 &self,
23037 buffer_handle: Entity<Buffer>,
23038 action: CodeAction,
23039 _excerpt_id: ExcerptId,
23040 push_to_history: bool,
23041 _window: &mut Window,
23042 cx: &mut App,
23043 ) -> Task<Result<ProjectTransaction>> {
23044 self.update(cx, |project, cx| {
23045 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23046 })
23047 }
23048}
23049
23050fn snippet_completions(
23051 project: &Project,
23052 buffer: &Entity<Buffer>,
23053 buffer_position: text::Anchor,
23054 cx: &mut App,
23055) -> Task<Result<CompletionResponse>> {
23056 let languages = buffer.read(cx).languages_at(buffer_position);
23057 let snippet_store = project.snippets().read(cx);
23058
23059 let scopes: Vec<_> = languages
23060 .iter()
23061 .filter_map(|language| {
23062 let language_name = language.lsp_id();
23063 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23064
23065 if snippets.is_empty() {
23066 None
23067 } else {
23068 Some((language.default_scope(), snippets))
23069 }
23070 })
23071 .collect();
23072
23073 if scopes.is_empty() {
23074 return Task::ready(Ok(CompletionResponse {
23075 completions: vec![],
23076 display_options: CompletionDisplayOptions::default(),
23077 is_incomplete: false,
23078 }));
23079 }
23080
23081 let snapshot = buffer.read(cx).text_snapshot();
23082 let executor = cx.background_executor().clone();
23083
23084 cx.background_spawn(async move {
23085 let mut is_incomplete = false;
23086 let mut completions: Vec<Completion> = Vec::new();
23087 for (scope, snippets) in scopes.into_iter() {
23088 let classifier =
23089 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23090
23091 const MAX_WORD_PREFIX_LEN: usize = 128;
23092 let last_word: String = snapshot
23093 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23094 .take(MAX_WORD_PREFIX_LEN)
23095 .take_while(|c| classifier.is_word(*c))
23096 .collect::<String>()
23097 .chars()
23098 .rev()
23099 .collect();
23100
23101 if last_word.is_empty() {
23102 return Ok(CompletionResponse {
23103 completions: vec![],
23104 display_options: CompletionDisplayOptions::default(),
23105 is_incomplete: true,
23106 });
23107 }
23108
23109 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23110 let to_lsp = |point: &text::Anchor| {
23111 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23112 point_to_lsp(end)
23113 };
23114 let lsp_end = to_lsp(&buffer_position);
23115
23116 let candidates = snippets
23117 .iter()
23118 .enumerate()
23119 .flat_map(|(ix, snippet)| {
23120 snippet
23121 .prefix
23122 .iter()
23123 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23124 })
23125 .collect::<Vec<StringMatchCandidate>>();
23126
23127 const MAX_RESULTS: usize = 100;
23128 let mut matches = fuzzy::match_strings(
23129 &candidates,
23130 &last_word,
23131 last_word.chars().any(|c| c.is_uppercase()),
23132 true,
23133 MAX_RESULTS,
23134 &Default::default(),
23135 executor.clone(),
23136 )
23137 .await;
23138
23139 if matches.len() >= MAX_RESULTS {
23140 is_incomplete = true;
23141 }
23142
23143 // Remove all candidates where the query's start does not match the start of any word in the candidate
23144 if let Some(query_start) = last_word.chars().next() {
23145 matches.retain(|string_match| {
23146 split_words(&string_match.string).any(|word| {
23147 // Check that the first codepoint of the word as lowercase matches the first
23148 // codepoint of the query as lowercase
23149 word.chars()
23150 .flat_map(|codepoint| codepoint.to_lowercase())
23151 .zip(query_start.to_lowercase())
23152 .all(|(word_cp, query_cp)| word_cp == query_cp)
23153 })
23154 });
23155 }
23156
23157 let matched_strings = matches
23158 .into_iter()
23159 .map(|m| m.string)
23160 .collect::<HashSet<_>>();
23161
23162 completions.extend(snippets.iter().filter_map(|snippet| {
23163 let matching_prefix = snippet
23164 .prefix
23165 .iter()
23166 .find(|prefix| matched_strings.contains(*prefix))?;
23167 let start = as_offset - last_word.len();
23168 let start = snapshot.anchor_before(start);
23169 let range = start..buffer_position;
23170 let lsp_start = to_lsp(&start);
23171 let lsp_range = lsp::Range {
23172 start: lsp_start,
23173 end: lsp_end,
23174 };
23175 Some(Completion {
23176 replace_range: range,
23177 new_text: snippet.body.clone(),
23178 source: CompletionSource::Lsp {
23179 insert_range: None,
23180 server_id: LanguageServerId(usize::MAX),
23181 resolved: true,
23182 lsp_completion: Box::new(lsp::CompletionItem {
23183 label: snippet.prefix.first().unwrap().clone(),
23184 kind: Some(CompletionItemKind::SNIPPET),
23185 label_details: snippet.description.as_ref().map(|description| {
23186 lsp::CompletionItemLabelDetails {
23187 detail: Some(description.clone()),
23188 description: None,
23189 }
23190 }),
23191 insert_text_format: Some(InsertTextFormat::SNIPPET),
23192 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23193 lsp::InsertReplaceEdit {
23194 new_text: snippet.body.clone(),
23195 insert: lsp_range,
23196 replace: lsp_range,
23197 },
23198 )),
23199 filter_text: Some(snippet.body.clone()),
23200 sort_text: Some(char::MAX.to_string()),
23201 ..lsp::CompletionItem::default()
23202 }),
23203 lsp_defaults: None,
23204 },
23205 label: CodeLabel::plain(matching_prefix.clone(), None),
23206 icon_path: None,
23207 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23208 single_line: snippet.name.clone().into(),
23209 plain_text: snippet
23210 .description
23211 .clone()
23212 .map(|description| description.into()),
23213 }),
23214 insert_text_mode: None,
23215 confirm: None,
23216 })
23217 }))
23218 }
23219
23220 Ok(CompletionResponse {
23221 completions,
23222 display_options: CompletionDisplayOptions::default(),
23223 is_incomplete,
23224 })
23225 })
23226}
23227
23228impl CompletionProvider for Entity<Project> {
23229 fn completions(
23230 &self,
23231 _excerpt_id: ExcerptId,
23232 buffer: &Entity<Buffer>,
23233 buffer_position: text::Anchor,
23234 options: CompletionContext,
23235 _window: &mut Window,
23236 cx: &mut Context<Editor>,
23237 ) -> Task<Result<Vec<CompletionResponse>>> {
23238 self.update(cx, |project, cx| {
23239 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23240 let project_completions = project.completions(buffer, buffer_position, options, cx);
23241 cx.background_spawn(async move {
23242 let mut responses = project_completions.await?;
23243 let snippets = snippets.await?;
23244 if !snippets.completions.is_empty() {
23245 responses.push(snippets);
23246 }
23247 Ok(responses)
23248 })
23249 })
23250 }
23251
23252 fn resolve_completions(
23253 &self,
23254 buffer: Entity<Buffer>,
23255 completion_indices: Vec<usize>,
23256 completions: Rc<RefCell<Box<[Completion]>>>,
23257 cx: &mut Context<Editor>,
23258 ) -> Task<Result<bool>> {
23259 self.update(cx, |project, cx| {
23260 project.lsp_store().update(cx, |lsp_store, cx| {
23261 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23262 })
23263 })
23264 }
23265
23266 fn apply_additional_edits_for_completion(
23267 &self,
23268 buffer: Entity<Buffer>,
23269 completions: Rc<RefCell<Box<[Completion]>>>,
23270 completion_index: usize,
23271 push_to_history: bool,
23272 cx: &mut Context<Editor>,
23273 ) -> Task<Result<Option<language::Transaction>>> {
23274 self.update(cx, |project, cx| {
23275 project.lsp_store().update(cx, |lsp_store, cx| {
23276 lsp_store.apply_additional_edits_for_completion(
23277 buffer,
23278 completions,
23279 completion_index,
23280 push_to_history,
23281 cx,
23282 )
23283 })
23284 })
23285 }
23286
23287 fn is_completion_trigger(
23288 &self,
23289 buffer: &Entity<Buffer>,
23290 position: language::Anchor,
23291 text: &str,
23292 trigger_in_words: bool,
23293 menu_is_open: bool,
23294 cx: &mut Context<Editor>,
23295 ) -> bool {
23296 let mut chars = text.chars();
23297 let char = if let Some(char) = chars.next() {
23298 char
23299 } else {
23300 return false;
23301 };
23302 if chars.next().is_some() {
23303 return false;
23304 }
23305
23306 let buffer = buffer.read(cx);
23307 let snapshot = buffer.snapshot();
23308 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23309 return false;
23310 }
23311 let classifier = snapshot
23312 .char_classifier_at(position)
23313 .scope_context(Some(CharScopeContext::Completion));
23314 if trigger_in_words && classifier.is_word(char) {
23315 return true;
23316 }
23317
23318 buffer.completion_triggers().contains(text)
23319 }
23320}
23321
23322impl SemanticsProvider for Entity<Project> {
23323 fn hover(
23324 &self,
23325 buffer: &Entity<Buffer>,
23326 position: text::Anchor,
23327 cx: &mut App,
23328 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23329 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23330 }
23331
23332 fn document_highlights(
23333 &self,
23334 buffer: &Entity<Buffer>,
23335 position: text::Anchor,
23336 cx: &mut App,
23337 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23338 Some(self.update(cx, |project, cx| {
23339 project.document_highlights(buffer, position, cx)
23340 }))
23341 }
23342
23343 fn definitions(
23344 &self,
23345 buffer: &Entity<Buffer>,
23346 position: text::Anchor,
23347 kind: GotoDefinitionKind,
23348 cx: &mut App,
23349 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23350 Some(self.update(cx, |project, cx| match kind {
23351 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23352 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23353 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23354 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23355 }))
23356 }
23357
23358 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23359 self.update(cx, |project, cx| {
23360 if project
23361 .active_debug_session(cx)
23362 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23363 {
23364 return true;
23365 }
23366
23367 buffer.update(cx, |buffer, cx| {
23368 project.any_language_server_supports_inlay_hints(buffer, cx)
23369 })
23370 })
23371 }
23372
23373 fn inline_values(
23374 &self,
23375 buffer_handle: Entity<Buffer>,
23376 range: Range<text::Anchor>,
23377 cx: &mut App,
23378 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23379 self.update(cx, |project, cx| {
23380 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23381
23382 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23383 })
23384 }
23385
23386 fn inlay_hints(
23387 &self,
23388 buffer_handle: Entity<Buffer>,
23389 range: Range<text::Anchor>,
23390 cx: &mut App,
23391 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23392 Some(self.update(cx, |project, cx| {
23393 project.inlay_hints(buffer_handle, range, cx)
23394 }))
23395 }
23396
23397 fn resolve_inlay_hint(
23398 &self,
23399 hint: InlayHint,
23400 buffer_handle: Entity<Buffer>,
23401 server_id: LanguageServerId,
23402 cx: &mut App,
23403 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23404 Some(self.update(cx, |project, cx| {
23405 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23406 }))
23407 }
23408
23409 fn range_for_rename(
23410 &self,
23411 buffer: &Entity<Buffer>,
23412 position: text::Anchor,
23413 cx: &mut App,
23414 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23415 Some(self.update(cx, |project, cx| {
23416 let buffer = buffer.clone();
23417 let task = project.prepare_rename(buffer.clone(), position, cx);
23418 cx.spawn(async move |_, cx| {
23419 Ok(match task.await? {
23420 PrepareRenameResponse::Success(range) => Some(range),
23421 PrepareRenameResponse::InvalidPosition => None,
23422 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23423 // Fallback on using TreeSitter info to determine identifier range
23424 buffer.read_with(cx, |buffer, _| {
23425 let snapshot = buffer.snapshot();
23426 let (range, kind) = snapshot.surrounding_word(position, None);
23427 if kind != Some(CharKind::Word) {
23428 return None;
23429 }
23430 Some(
23431 snapshot.anchor_before(range.start)
23432 ..snapshot.anchor_after(range.end),
23433 )
23434 })?
23435 }
23436 })
23437 })
23438 }))
23439 }
23440
23441 fn perform_rename(
23442 &self,
23443 buffer: &Entity<Buffer>,
23444 position: text::Anchor,
23445 new_name: String,
23446 cx: &mut App,
23447 ) -> Option<Task<Result<ProjectTransaction>>> {
23448 Some(self.update(cx, |project, cx| {
23449 project.perform_rename(buffer.clone(), position, new_name, cx)
23450 }))
23451 }
23452}
23453
23454fn inlay_hint_settings(
23455 location: Anchor,
23456 snapshot: &MultiBufferSnapshot,
23457 cx: &mut Context<Editor>,
23458) -> InlayHintSettings {
23459 let file = snapshot.file_at(location);
23460 let language = snapshot.language_at(location).map(|l| l.name());
23461 language_settings(language, file, cx).inlay_hints
23462}
23463
23464fn consume_contiguous_rows(
23465 contiguous_row_selections: &mut Vec<Selection<Point>>,
23466 selection: &Selection<Point>,
23467 display_map: &DisplaySnapshot,
23468 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23469) -> (MultiBufferRow, MultiBufferRow) {
23470 contiguous_row_selections.push(selection.clone());
23471 let start_row = starting_row(selection, display_map);
23472 let mut end_row = ending_row(selection, display_map);
23473
23474 while let Some(next_selection) = selections.peek() {
23475 if next_selection.start.row <= end_row.0 {
23476 end_row = ending_row(next_selection, display_map);
23477 contiguous_row_selections.push(selections.next().unwrap().clone());
23478 } else {
23479 break;
23480 }
23481 }
23482 (start_row, end_row)
23483}
23484
23485fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23486 if selection.start.column > 0 {
23487 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23488 } else {
23489 MultiBufferRow(selection.start.row)
23490 }
23491}
23492
23493fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23494 if next_selection.end.column > 0 || next_selection.is_empty() {
23495 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23496 } else {
23497 MultiBufferRow(next_selection.end.row)
23498 }
23499}
23500
23501impl EditorSnapshot {
23502 pub fn remote_selections_in_range<'a>(
23503 &'a self,
23504 range: &'a Range<Anchor>,
23505 collaboration_hub: &dyn CollaborationHub,
23506 cx: &'a App,
23507 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23508 let participant_names = collaboration_hub.user_names(cx);
23509 let participant_indices = collaboration_hub.user_participant_indices(cx);
23510 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23511 let collaborators_by_replica_id = collaborators_by_peer_id
23512 .values()
23513 .map(|collaborator| (collaborator.replica_id, collaborator))
23514 .collect::<HashMap<_, _>>();
23515 self.buffer_snapshot()
23516 .selections_in_range(range, false)
23517 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23518 if replica_id == ReplicaId::AGENT {
23519 Some(RemoteSelection {
23520 replica_id,
23521 selection,
23522 cursor_shape,
23523 line_mode,
23524 collaborator_id: CollaboratorId::Agent,
23525 user_name: Some("Agent".into()),
23526 color: cx.theme().players().agent(),
23527 })
23528 } else {
23529 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23530 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23531 let user_name = participant_names.get(&collaborator.user_id).cloned();
23532 Some(RemoteSelection {
23533 replica_id,
23534 selection,
23535 cursor_shape,
23536 line_mode,
23537 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23538 user_name,
23539 color: if let Some(index) = participant_index {
23540 cx.theme().players().color_for_participant(index.0)
23541 } else {
23542 cx.theme().players().absent()
23543 },
23544 })
23545 }
23546 })
23547 }
23548
23549 pub fn hunks_for_ranges(
23550 &self,
23551 ranges: impl IntoIterator<Item = Range<Point>>,
23552 ) -> Vec<MultiBufferDiffHunk> {
23553 let mut hunks = Vec::new();
23554 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23555 HashMap::default();
23556 for query_range in ranges {
23557 let query_rows =
23558 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23559 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23560 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23561 ) {
23562 // Include deleted hunks that are adjacent to the query range, because
23563 // otherwise they would be missed.
23564 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23565 if hunk.status().is_deleted() {
23566 intersects_range |= hunk.row_range.start == query_rows.end;
23567 intersects_range |= hunk.row_range.end == query_rows.start;
23568 }
23569 if intersects_range {
23570 if !processed_buffer_rows
23571 .entry(hunk.buffer_id)
23572 .or_default()
23573 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23574 {
23575 continue;
23576 }
23577 hunks.push(hunk);
23578 }
23579 }
23580 }
23581
23582 hunks
23583 }
23584
23585 fn display_diff_hunks_for_rows<'a>(
23586 &'a self,
23587 display_rows: Range<DisplayRow>,
23588 folded_buffers: &'a HashSet<BufferId>,
23589 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23590 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23591 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23592
23593 self.buffer_snapshot()
23594 .diff_hunks_in_range(buffer_start..buffer_end)
23595 .filter_map(|hunk| {
23596 if folded_buffers.contains(&hunk.buffer_id) {
23597 return None;
23598 }
23599
23600 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23601 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23602
23603 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23604 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23605
23606 let display_hunk = if hunk_display_start.column() != 0 {
23607 DisplayDiffHunk::Folded {
23608 display_row: hunk_display_start.row(),
23609 }
23610 } else {
23611 let mut end_row = hunk_display_end.row();
23612 if hunk_display_end.column() > 0 {
23613 end_row.0 += 1;
23614 }
23615 let is_created_file = hunk.is_created_file();
23616 DisplayDiffHunk::Unfolded {
23617 status: hunk.status(),
23618 diff_base_byte_range: hunk.diff_base_byte_range,
23619 display_row_range: hunk_display_start.row()..end_row,
23620 multi_buffer_range: Anchor::range_in_buffer(
23621 hunk.excerpt_id,
23622 hunk.buffer_id,
23623 hunk.buffer_range,
23624 ),
23625 is_created_file,
23626 }
23627 };
23628
23629 Some(display_hunk)
23630 })
23631 }
23632
23633 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23634 self.display_snapshot
23635 .buffer_snapshot()
23636 .language_at(position)
23637 }
23638
23639 pub fn is_focused(&self) -> bool {
23640 self.is_focused
23641 }
23642
23643 pub fn placeholder_text(&self) -> Option<String> {
23644 self.placeholder_display_snapshot
23645 .as_ref()
23646 .map(|display_map| display_map.text())
23647 }
23648
23649 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23650 self.scroll_anchor.scroll_position(&self.display_snapshot)
23651 }
23652
23653 fn gutter_dimensions(
23654 &self,
23655 font_id: FontId,
23656 font_size: Pixels,
23657 max_line_number_width: Pixels,
23658 cx: &App,
23659 ) -> Option<GutterDimensions> {
23660 if !self.show_gutter {
23661 return None;
23662 }
23663
23664 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23665 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23666
23667 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23668 matches!(
23669 ProjectSettings::get_global(cx).git.git_gutter,
23670 GitGutterSetting::TrackedFiles
23671 )
23672 });
23673 let gutter_settings = EditorSettings::get_global(cx).gutter;
23674 let show_line_numbers = self
23675 .show_line_numbers
23676 .unwrap_or(gutter_settings.line_numbers);
23677 let line_gutter_width = if show_line_numbers {
23678 // Avoid flicker-like gutter resizes when the line number gains another digit by
23679 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23680 let min_width_for_number_on_gutter =
23681 ch_advance * gutter_settings.min_line_number_digits as f32;
23682 max_line_number_width.max(min_width_for_number_on_gutter)
23683 } else {
23684 0.0.into()
23685 };
23686
23687 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23688 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23689
23690 let git_blame_entries_width =
23691 self.git_blame_gutter_max_author_length
23692 .map(|max_author_length| {
23693 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23694 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23695
23696 /// The number of characters to dedicate to gaps and margins.
23697 const SPACING_WIDTH: usize = 4;
23698
23699 let max_char_count = max_author_length.min(renderer.max_author_length())
23700 + ::git::SHORT_SHA_LENGTH
23701 + MAX_RELATIVE_TIMESTAMP.len()
23702 + SPACING_WIDTH;
23703
23704 ch_advance * max_char_count
23705 });
23706
23707 let is_singleton = self.buffer_snapshot().is_singleton();
23708
23709 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23710 left_padding += if !is_singleton {
23711 ch_width * 4.0
23712 } else if show_runnables || show_breakpoints {
23713 ch_width * 3.0
23714 } else if show_git_gutter && show_line_numbers {
23715 ch_width * 2.0
23716 } else if show_git_gutter || show_line_numbers {
23717 ch_width
23718 } else {
23719 px(0.)
23720 };
23721
23722 let shows_folds = is_singleton && gutter_settings.folds;
23723
23724 let right_padding = if shows_folds && show_line_numbers {
23725 ch_width * 4.0
23726 } else if shows_folds || (!is_singleton && show_line_numbers) {
23727 ch_width * 3.0
23728 } else if show_line_numbers {
23729 ch_width
23730 } else {
23731 px(0.)
23732 };
23733
23734 Some(GutterDimensions {
23735 left_padding,
23736 right_padding,
23737 width: line_gutter_width + left_padding + right_padding,
23738 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23739 git_blame_entries_width,
23740 })
23741 }
23742
23743 pub fn render_crease_toggle(
23744 &self,
23745 buffer_row: MultiBufferRow,
23746 row_contains_cursor: bool,
23747 editor: Entity<Editor>,
23748 window: &mut Window,
23749 cx: &mut App,
23750 ) -> Option<AnyElement> {
23751 let folded = self.is_line_folded(buffer_row);
23752 let mut is_foldable = false;
23753
23754 if let Some(crease) = self
23755 .crease_snapshot
23756 .query_row(buffer_row, self.buffer_snapshot())
23757 {
23758 is_foldable = true;
23759 match crease {
23760 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23761 if let Some(render_toggle) = render_toggle {
23762 let toggle_callback =
23763 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23764 if folded {
23765 editor.update(cx, |editor, cx| {
23766 editor.fold_at(buffer_row, window, cx)
23767 });
23768 } else {
23769 editor.update(cx, |editor, cx| {
23770 editor.unfold_at(buffer_row, window, cx)
23771 });
23772 }
23773 });
23774 return Some((render_toggle)(
23775 buffer_row,
23776 folded,
23777 toggle_callback,
23778 window,
23779 cx,
23780 ));
23781 }
23782 }
23783 }
23784 }
23785
23786 is_foldable |= self.starts_indent(buffer_row);
23787
23788 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23789 Some(
23790 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23791 .toggle_state(folded)
23792 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23793 if folded {
23794 this.unfold_at(buffer_row, window, cx);
23795 } else {
23796 this.fold_at(buffer_row, window, cx);
23797 }
23798 }))
23799 .into_any_element(),
23800 )
23801 } else {
23802 None
23803 }
23804 }
23805
23806 pub fn render_crease_trailer(
23807 &self,
23808 buffer_row: MultiBufferRow,
23809 window: &mut Window,
23810 cx: &mut App,
23811 ) -> Option<AnyElement> {
23812 let folded = self.is_line_folded(buffer_row);
23813 if let Crease::Inline { render_trailer, .. } = self
23814 .crease_snapshot
23815 .query_row(buffer_row, self.buffer_snapshot())?
23816 {
23817 let render_trailer = render_trailer.as_ref()?;
23818 Some(render_trailer(buffer_row, folded, window, cx))
23819 } else {
23820 None
23821 }
23822 }
23823}
23824
23825impl Deref for EditorSnapshot {
23826 type Target = DisplaySnapshot;
23827
23828 fn deref(&self) -> &Self::Target {
23829 &self.display_snapshot
23830 }
23831}
23832
23833#[derive(Clone, Debug, PartialEq, Eq)]
23834pub enum EditorEvent {
23835 InputIgnored {
23836 text: Arc<str>,
23837 },
23838 InputHandled {
23839 utf16_range_to_replace: Option<Range<isize>>,
23840 text: Arc<str>,
23841 },
23842 ExcerptsAdded {
23843 buffer: Entity<Buffer>,
23844 predecessor: ExcerptId,
23845 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23846 },
23847 ExcerptsRemoved {
23848 ids: Vec<ExcerptId>,
23849 removed_buffer_ids: Vec<BufferId>,
23850 },
23851 BufferFoldToggled {
23852 ids: Vec<ExcerptId>,
23853 folded: bool,
23854 },
23855 ExcerptsEdited {
23856 ids: Vec<ExcerptId>,
23857 },
23858 ExcerptsExpanded {
23859 ids: Vec<ExcerptId>,
23860 },
23861 BufferEdited,
23862 Edited {
23863 transaction_id: clock::Lamport,
23864 },
23865 Reparsed(BufferId),
23866 Focused,
23867 FocusedIn,
23868 Blurred,
23869 DirtyChanged,
23870 Saved,
23871 TitleChanged,
23872 SelectionsChanged {
23873 local: bool,
23874 },
23875 ScrollPositionChanged {
23876 local: bool,
23877 autoscroll: bool,
23878 },
23879 TransactionUndone {
23880 transaction_id: clock::Lamport,
23881 },
23882 TransactionBegun {
23883 transaction_id: clock::Lamport,
23884 },
23885 CursorShapeChanged,
23886 BreadcrumbsChanged,
23887 PushedToNavHistory {
23888 anchor: Anchor,
23889 is_deactivate: bool,
23890 },
23891}
23892
23893impl EventEmitter<EditorEvent> for Editor {}
23894
23895impl Focusable for Editor {
23896 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23897 self.focus_handle.clone()
23898 }
23899}
23900
23901impl Render for Editor {
23902 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23903 let settings = ThemeSettings::get_global(cx);
23904
23905 let mut text_style = match self.mode {
23906 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23907 color: cx.theme().colors().editor_foreground,
23908 font_family: settings.ui_font.family.clone(),
23909 font_features: settings.ui_font.features.clone(),
23910 font_fallbacks: settings.ui_font.fallbacks.clone(),
23911 font_size: rems(0.875).into(),
23912 font_weight: settings.ui_font.weight,
23913 line_height: relative(settings.buffer_line_height.value()),
23914 ..Default::default()
23915 },
23916 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23917 color: cx.theme().colors().editor_foreground,
23918 font_family: settings.buffer_font.family.clone(),
23919 font_features: settings.buffer_font.features.clone(),
23920 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23921 font_size: settings.buffer_font_size(cx).into(),
23922 font_weight: settings.buffer_font.weight,
23923 line_height: relative(settings.buffer_line_height.value()),
23924 ..Default::default()
23925 },
23926 };
23927 if let Some(text_style_refinement) = &self.text_style_refinement {
23928 text_style.refine(text_style_refinement)
23929 }
23930
23931 let background = match self.mode {
23932 EditorMode::SingleLine => cx.theme().system().transparent,
23933 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23934 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23935 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23936 };
23937
23938 EditorElement::new(
23939 &cx.entity(),
23940 EditorStyle {
23941 background,
23942 border: cx.theme().colors().border,
23943 local_player: cx.theme().players().local(),
23944 text: text_style,
23945 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23946 syntax: cx.theme().syntax().clone(),
23947 status: cx.theme().status().clone(),
23948 inlay_hints_style: make_inlay_hints_style(cx),
23949 edit_prediction_styles: make_suggestion_styles(cx),
23950 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23951 show_underlines: self.diagnostics_enabled(),
23952 },
23953 )
23954 }
23955}
23956
23957impl EntityInputHandler for Editor {
23958 fn text_for_range(
23959 &mut self,
23960 range_utf16: Range<usize>,
23961 adjusted_range: &mut Option<Range<usize>>,
23962 _: &mut Window,
23963 cx: &mut Context<Self>,
23964 ) -> Option<String> {
23965 let snapshot = self.buffer.read(cx).read(cx);
23966 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23967 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23968 if (start.0..end.0) != range_utf16 {
23969 adjusted_range.replace(start.0..end.0);
23970 }
23971 Some(snapshot.text_for_range(start..end).collect())
23972 }
23973
23974 fn selected_text_range(
23975 &mut self,
23976 ignore_disabled_input: bool,
23977 _: &mut Window,
23978 cx: &mut Context<Self>,
23979 ) -> Option<UTF16Selection> {
23980 // Prevent the IME menu from appearing when holding down an alphabetic key
23981 // while input is disabled.
23982 if !ignore_disabled_input && !self.input_enabled {
23983 return None;
23984 }
23985
23986 let selection = self
23987 .selections
23988 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23989 let range = selection.range();
23990
23991 Some(UTF16Selection {
23992 range: range.start.0..range.end.0,
23993 reversed: selection.reversed,
23994 })
23995 }
23996
23997 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23998 let snapshot = self.buffer.read(cx).read(cx);
23999 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24000 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
24001 }
24002
24003 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24004 self.clear_highlights::<InputComposition>(cx);
24005 self.ime_transaction.take();
24006 }
24007
24008 fn replace_text_in_range(
24009 &mut self,
24010 range_utf16: Option<Range<usize>>,
24011 text: &str,
24012 window: &mut Window,
24013 cx: &mut Context<Self>,
24014 ) {
24015 if !self.input_enabled {
24016 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24017 return;
24018 }
24019
24020 self.transact(window, cx, |this, window, cx| {
24021 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24022 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24023 Some(this.selection_replacement_ranges(range_utf16, cx))
24024 } else {
24025 this.marked_text_ranges(cx)
24026 };
24027
24028 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24029 let newest_selection_id = this.selections.newest_anchor().id;
24030 this.selections
24031 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24032 .iter()
24033 .zip(ranges_to_replace.iter())
24034 .find_map(|(selection, range)| {
24035 if selection.id == newest_selection_id {
24036 Some(
24037 (range.start.0 as isize - selection.head().0 as isize)
24038 ..(range.end.0 as isize - selection.head().0 as isize),
24039 )
24040 } else {
24041 None
24042 }
24043 })
24044 });
24045
24046 cx.emit(EditorEvent::InputHandled {
24047 utf16_range_to_replace: range_to_replace,
24048 text: text.into(),
24049 });
24050
24051 if let Some(new_selected_ranges) = new_selected_ranges {
24052 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24053 selections.select_ranges(new_selected_ranges)
24054 });
24055 this.backspace(&Default::default(), window, cx);
24056 }
24057
24058 this.handle_input(text, window, cx);
24059 });
24060
24061 if let Some(transaction) = self.ime_transaction {
24062 self.buffer.update(cx, |buffer, cx| {
24063 buffer.group_until_transaction(transaction, cx);
24064 });
24065 }
24066
24067 self.unmark_text(window, cx);
24068 }
24069
24070 fn replace_and_mark_text_in_range(
24071 &mut self,
24072 range_utf16: Option<Range<usize>>,
24073 text: &str,
24074 new_selected_range_utf16: Option<Range<usize>>,
24075 window: &mut Window,
24076 cx: &mut Context<Self>,
24077 ) {
24078 if !self.input_enabled {
24079 return;
24080 }
24081
24082 let transaction = self.transact(window, cx, |this, window, cx| {
24083 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24084 let snapshot = this.buffer.read(cx).read(cx);
24085 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24086 for marked_range in &mut marked_ranges {
24087 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24088 marked_range.start.0 += relative_range_utf16.start;
24089 marked_range.start =
24090 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24091 marked_range.end =
24092 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24093 }
24094 }
24095 Some(marked_ranges)
24096 } else if let Some(range_utf16) = range_utf16 {
24097 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24098 Some(this.selection_replacement_ranges(range_utf16, cx))
24099 } else {
24100 None
24101 };
24102
24103 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24104 let newest_selection_id = this.selections.newest_anchor().id;
24105 this.selections
24106 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24107 .iter()
24108 .zip(ranges_to_replace.iter())
24109 .find_map(|(selection, range)| {
24110 if selection.id == newest_selection_id {
24111 Some(
24112 (range.start.0 as isize - selection.head().0 as isize)
24113 ..(range.end.0 as isize - selection.head().0 as isize),
24114 )
24115 } else {
24116 None
24117 }
24118 })
24119 });
24120
24121 cx.emit(EditorEvent::InputHandled {
24122 utf16_range_to_replace: range_to_replace,
24123 text: text.into(),
24124 });
24125
24126 if let Some(ranges) = ranges_to_replace {
24127 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24128 s.select_ranges(ranges)
24129 });
24130 }
24131
24132 let marked_ranges = {
24133 let snapshot = this.buffer.read(cx).read(cx);
24134 this.selections
24135 .disjoint_anchors_arc()
24136 .iter()
24137 .map(|selection| {
24138 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24139 })
24140 .collect::<Vec<_>>()
24141 };
24142
24143 if text.is_empty() {
24144 this.unmark_text(window, cx);
24145 } else {
24146 this.highlight_text::<InputComposition>(
24147 marked_ranges.clone(),
24148 HighlightStyle {
24149 underline: Some(UnderlineStyle {
24150 thickness: px(1.),
24151 color: None,
24152 wavy: false,
24153 }),
24154 ..Default::default()
24155 },
24156 cx,
24157 );
24158 }
24159
24160 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24161 let use_autoclose = this.use_autoclose;
24162 let use_auto_surround = this.use_auto_surround;
24163 this.set_use_autoclose(false);
24164 this.set_use_auto_surround(false);
24165 this.handle_input(text, window, cx);
24166 this.set_use_autoclose(use_autoclose);
24167 this.set_use_auto_surround(use_auto_surround);
24168
24169 if let Some(new_selected_range) = new_selected_range_utf16 {
24170 let snapshot = this.buffer.read(cx).read(cx);
24171 let new_selected_ranges = marked_ranges
24172 .into_iter()
24173 .map(|marked_range| {
24174 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24175 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24176 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24177 snapshot.clip_offset_utf16(new_start, Bias::Left)
24178 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24179 })
24180 .collect::<Vec<_>>();
24181
24182 drop(snapshot);
24183 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24184 selections.select_ranges(new_selected_ranges)
24185 });
24186 }
24187 });
24188
24189 self.ime_transaction = self.ime_transaction.or(transaction);
24190 if let Some(transaction) = self.ime_transaction {
24191 self.buffer.update(cx, |buffer, cx| {
24192 buffer.group_until_transaction(transaction, cx);
24193 });
24194 }
24195
24196 if self.text_highlights::<InputComposition>(cx).is_none() {
24197 self.ime_transaction.take();
24198 }
24199 }
24200
24201 fn bounds_for_range(
24202 &mut self,
24203 range_utf16: Range<usize>,
24204 element_bounds: gpui::Bounds<Pixels>,
24205 window: &mut Window,
24206 cx: &mut Context<Self>,
24207 ) -> Option<gpui::Bounds<Pixels>> {
24208 let text_layout_details = self.text_layout_details(window);
24209 let CharacterDimensions {
24210 em_width,
24211 em_advance,
24212 line_height,
24213 } = self.character_dimensions(window);
24214
24215 let snapshot = self.snapshot(window, cx);
24216 let scroll_position = snapshot.scroll_position();
24217 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24218
24219 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24220 let x = Pixels::from(
24221 ScrollOffset::from(
24222 snapshot.x_for_display_point(start, &text_layout_details)
24223 + self.gutter_dimensions.full_width(),
24224 ) - scroll_left,
24225 );
24226 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24227
24228 Some(Bounds {
24229 origin: element_bounds.origin + point(x, y),
24230 size: size(em_width, line_height),
24231 })
24232 }
24233
24234 fn character_index_for_point(
24235 &mut self,
24236 point: gpui::Point<Pixels>,
24237 _window: &mut Window,
24238 _cx: &mut Context<Self>,
24239 ) -> Option<usize> {
24240 let position_map = self.last_position_map.as_ref()?;
24241 if !position_map.text_hitbox.contains(&point) {
24242 return None;
24243 }
24244 let display_point = position_map.point_for_position(point).previous_valid;
24245 let anchor = position_map
24246 .snapshot
24247 .display_point_to_anchor(display_point, Bias::Left);
24248 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24249 Some(utf16_offset.0)
24250 }
24251}
24252
24253trait SelectionExt {
24254 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24255 fn spanned_rows(
24256 &self,
24257 include_end_if_at_line_start: bool,
24258 map: &DisplaySnapshot,
24259 ) -> Range<MultiBufferRow>;
24260}
24261
24262impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24263 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24264 let start = self
24265 .start
24266 .to_point(map.buffer_snapshot())
24267 .to_display_point(map);
24268 let end = self
24269 .end
24270 .to_point(map.buffer_snapshot())
24271 .to_display_point(map);
24272 if self.reversed {
24273 end..start
24274 } else {
24275 start..end
24276 }
24277 }
24278
24279 fn spanned_rows(
24280 &self,
24281 include_end_if_at_line_start: bool,
24282 map: &DisplaySnapshot,
24283 ) -> Range<MultiBufferRow> {
24284 let start = self.start.to_point(map.buffer_snapshot());
24285 let mut end = self.end.to_point(map.buffer_snapshot());
24286 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24287 end.row -= 1;
24288 }
24289
24290 let buffer_start = map.prev_line_boundary(start).0;
24291 let buffer_end = map.next_line_boundary(end).0;
24292 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24293 }
24294}
24295
24296impl<T: InvalidationRegion> InvalidationStack<T> {
24297 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24298 where
24299 S: Clone + ToOffset,
24300 {
24301 while let Some(region) = self.last() {
24302 let all_selections_inside_invalidation_ranges =
24303 if selections.len() == region.ranges().len() {
24304 selections
24305 .iter()
24306 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24307 .all(|(selection, invalidation_range)| {
24308 let head = selection.head().to_offset(buffer);
24309 invalidation_range.start <= head && invalidation_range.end >= head
24310 })
24311 } else {
24312 false
24313 };
24314
24315 if all_selections_inside_invalidation_ranges {
24316 break;
24317 } else {
24318 self.pop();
24319 }
24320 }
24321 }
24322}
24323
24324impl<T> Default for InvalidationStack<T> {
24325 fn default() -> Self {
24326 Self(Default::default())
24327 }
24328}
24329
24330impl<T> Deref for InvalidationStack<T> {
24331 type Target = Vec<T>;
24332
24333 fn deref(&self) -> &Self::Target {
24334 &self.0
24335 }
24336}
24337
24338impl<T> DerefMut for InvalidationStack<T> {
24339 fn deref_mut(&mut self) -> &mut Self::Target {
24340 &mut self.0
24341 }
24342}
24343
24344impl InvalidationRegion for SnippetState {
24345 fn ranges(&self) -> &[Range<Anchor>] {
24346 &self.ranges[self.active_index]
24347 }
24348}
24349
24350fn edit_prediction_edit_text(
24351 current_snapshot: &BufferSnapshot,
24352 edits: &[(Range<Anchor>, String)],
24353 edit_preview: &EditPreview,
24354 include_deletions: bool,
24355 cx: &App,
24356) -> HighlightedText {
24357 let edits = edits
24358 .iter()
24359 .map(|(anchor, text)| {
24360 (
24361 anchor.start.text_anchor..anchor.end.text_anchor,
24362 text.clone(),
24363 )
24364 })
24365 .collect::<Vec<_>>();
24366
24367 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24368}
24369
24370fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24371 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24372 // Just show the raw edit text with basic styling
24373 let mut text = String::new();
24374 let mut highlights = Vec::new();
24375
24376 let insertion_highlight_style = HighlightStyle {
24377 color: Some(cx.theme().colors().text),
24378 ..Default::default()
24379 };
24380
24381 for (_, edit_text) in edits {
24382 let start_offset = text.len();
24383 text.push_str(edit_text);
24384 let end_offset = text.len();
24385
24386 if start_offset < end_offset {
24387 highlights.push((start_offset..end_offset, insertion_highlight_style));
24388 }
24389 }
24390
24391 HighlightedText {
24392 text: text.into(),
24393 highlights,
24394 }
24395}
24396
24397pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24398 match severity {
24399 lsp::DiagnosticSeverity::ERROR => colors.error,
24400 lsp::DiagnosticSeverity::WARNING => colors.warning,
24401 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24402 lsp::DiagnosticSeverity::HINT => colors.info,
24403 _ => colors.ignored,
24404 }
24405}
24406
24407pub fn styled_runs_for_code_label<'a>(
24408 label: &'a CodeLabel,
24409 syntax_theme: &'a theme::SyntaxTheme,
24410) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24411 let fade_out = HighlightStyle {
24412 fade_out: Some(0.35),
24413 ..Default::default()
24414 };
24415
24416 let mut prev_end = label.filter_range.end;
24417 label
24418 .runs
24419 .iter()
24420 .enumerate()
24421 .flat_map(move |(ix, (range, highlight_id))| {
24422 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24423 style
24424 } else {
24425 return Default::default();
24426 };
24427 let muted_style = style.highlight(fade_out);
24428
24429 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24430 if range.start >= label.filter_range.end {
24431 if range.start > prev_end {
24432 runs.push((prev_end..range.start, fade_out));
24433 }
24434 runs.push((range.clone(), muted_style));
24435 } else if range.end <= label.filter_range.end {
24436 runs.push((range.clone(), style));
24437 } else {
24438 runs.push((range.start..label.filter_range.end, style));
24439 runs.push((label.filter_range.end..range.end, muted_style));
24440 }
24441 prev_end = cmp::max(prev_end, range.end);
24442
24443 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24444 runs.push((prev_end..label.text.len(), fade_out));
24445 }
24446
24447 runs
24448 })
24449}
24450
24451pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24452 let mut prev_index = 0;
24453 let mut prev_codepoint: Option<char> = None;
24454 text.char_indices()
24455 .chain([(text.len(), '\0')])
24456 .filter_map(move |(index, codepoint)| {
24457 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24458 let is_boundary = index == text.len()
24459 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24460 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24461 if is_boundary {
24462 let chunk = &text[prev_index..index];
24463 prev_index = index;
24464 Some(chunk)
24465 } else {
24466 None
24467 }
24468 })
24469}
24470
24471pub trait RangeToAnchorExt: Sized {
24472 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24473
24474 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24475 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24476 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24477 }
24478}
24479
24480impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24481 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24482 let start_offset = self.start.to_offset(snapshot);
24483 let end_offset = self.end.to_offset(snapshot);
24484 if start_offset == end_offset {
24485 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24486 } else {
24487 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24488 }
24489 }
24490}
24491
24492pub trait RowExt {
24493 fn as_f64(&self) -> f64;
24494
24495 fn next_row(&self) -> Self;
24496
24497 fn previous_row(&self) -> Self;
24498
24499 fn minus(&self, other: Self) -> u32;
24500}
24501
24502impl RowExt for DisplayRow {
24503 fn as_f64(&self) -> f64 {
24504 self.0 as _
24505 }
24506
24507 fn next_row(&self) -> Self {
24508 Self(self.0 + 1)
24509 }
24510
24511 fn previous_row(&self) -> Self {
24512 Self(self.0.saturating_sub(1))
24513 }
24514
24515 fn minus(&self, other: Self) -> u32 {
24516 self.0 - other.0
24517 }
24518}
24519
24520impl RowExt for MultiBufferRow {
24521 fn as_f64(&self) -> f64 {
24522 self.0 as _
24523 }
24524
24525 fn next_row(&self) -> Self {
24526 Self(self.0 + 1)
24527 }
24528
24529 fn previous_row(&self) -> Self {
24530 Self(self.0.saturating_sub(1))
24531 }
24532
24533 fn minus(&self, other: Self) -> u32 {
24534 self.0 - other.0
24535 }
24536}
24537
24538trait RowRangeExt {
24539 type Row;
24540
24541 fn len(&self) -> usize;
24542
24543 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24544}
24545
24546impl RowRangeExt for Range<MultiBufferRow> {
24547 type Row = MultiBufferRow;
24548
24549 fn len(&self) -> usize {
24550 (self.end.0 - self.start.0) as usize
24551 }
24552
24553 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24554 (self.start.0..self.end.0).map(MultiBufferRow)
24555 }
24556}
24557
24558impl RowRangeExt for Range<DisplayRow> {
24559 type Row = DisplayRow;
24560
24561 fn len(&self) -> usize {
24562 (self.end.0 - self.start.0) as usize
24563 }
24564
24565 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24566 (self.start.0..self.end.0).map(DisplayRow)
24567 }
24568}
24569
24570/// If select range has more than one line, we
24571/// just point the cursor to range.start.
24572fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24573 if range.start.row == range.end.row {
24574 range
24575 } else {
24576 range.start..range.start
24577 }
24578}
24579pub struct KillRing(ClipboardItem);
24580impl Global for KillRing {}
24581
24582const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24583
24584enum BreakpointPromptEditAction {
24585 Log,
24586 Condition,
24587 HitCondition,
24588}
24589
24590struct BreakpointPromptEditor {
24591 pub(crate) prompt: Entity<Editor>,
24592 editor: WeakEntity<Editor>,
24593 breakpoint_anchor: Anchor,
24594 breakpoint: Breakpoint,
24595 edit_action: BreakpointPromptEditAction,
24596 block_ids: HashSet<CustomBlockId>,
24597 editor_margins: Arc<Mutex<EditorMargins>>,
24598 _subscriptions: Vec<Subscription>,
24599}
24600
24601impl BreakpointPromptEditor {
24602 const MAX_LINES: u8 = 4;
24603
24604 fn new(
24605 editor: WeakEntity<Editor>,
24606 breakpoint_anchor: Anchor,
24607 breakpoint: Breakpoint,
24608 edit_action: BreakpointPromptEditAction,
24609 window: &mut Window,
24610 cx: &mut Context<Self>,
24611 ) -> Self {
24612 let base_text = match edit_action {
24613 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24614 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24615 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24616 }
24617 .map(|msg| msg.to_string())
24618 .unwrap_or_default();
24619
24620 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24621 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24622
24623 let prompt = cx.new(|cx| {
24624 let mut prompt = Editor::new(
24625 EditorMode::AutoHeight {
24626 min_lines: 1,
24627 max_lines: Some(Self::MAX_LINES as usize),
24628 },
24629 buffer,
24630 None,
24631 window,
24632 cx,
24633 );
24634 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24635 prompt.set_show_cursor_when_unfocused(false, cx);
24636 prompt.set_placeholder_text(
24637 match edit_action {
24638 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24639 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24640 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24641 },
24642 window,
24643 cx,
24644 );
24645
24646 prompt
24647 });
24648
24649 Self {
24650 prompt,
24651 editor,
24652 breakpoint_anchor,
24653 breakpoint,
24654 edit_action,
24655 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24656 block_ids: Default::default(),
24657 _subscriptions: vec![],
24658 }
24659 }
24660
24661 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24662 self.block_ids.extend(block_ids)
24663 }
24664
24665 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24666 if let Some(editor) = self.editor.upgrade() {
24667 let message = self
24668 .prompt
24669 .read(cx)
24670 .buffer
24671 .read(cx)
24672 .as_singleton()
24673 .expect("A multi buffer in breakpoint prompt isn't possible")
24674 .read(cx)
24675 .as_rope()
24676 .to_string();
24677
24678 editor.update(cx, |editor, cx| {
24679 editor.edit_breakpoint_at_anchor(
24680 self.breakpoint_anchor,
24681 self.breakpoint.clone(),
24682 match self.edit_action {
24683 BreakpointPromptEditAction::Log => {
24684 BreakpointEditAction::EditLogMessage(message.into())
24685 }
24686 BreakpointPromptEditAction::Condition => {
24687 BreakpointEditAction::EditCondition(message.into())
24688 }
24689 BreakpointPromptEditAction::HitCondition => {
24690 BreakpointEditAction::EditHitCondition(message.into())
24691 }
24692 },
24693 cx,
24694 );
24695
24696 editor.remove_blocks(self.block_ids.clone(), None, cx);
24697 cx.focus_self(window);
24698 });
24699 }
24700 }
24701
24702 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24703 self.editor
24704 .update(cx, |editor, cx| {
24705 editor.remove_blocks(self.block_ids.clone(), None, cx);
24706 window.focus(&editor.focus_handle);
24707 })
24708 .log_err();
24709 }
24710
24711 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24712 let settings = ThemeSettings::get_global(cx);
24713 let text_style = TextStyle {
24714 color: if self.prompt.read(cx).read_only(cx) {
24715 cx.theme().colors().text_disabled
24716 } else {
24717 cx.theme().colors().text
24718 },
24719 font_family: settings.buffer_font.family.clone(),
24720 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24721 font_size: settings.buffer_font_size(cx).into(),
24722 font_weight: settings.buffer_font.weight,
24723 line_height: relative(settings.buffer_line_height.value()),
24724 ..Default::default()
24725 };
24726 EditorElement::new(
24727 &self.prompt,
24728 EditorStyle {
24729 background: cx.theme().colors().editor_background,
24730 local_player: cx.theme().players().local(),
24731 text: text_style,
24732 ..Default::default()
24733 },
24734 )
24735 }
24736}
24737
24738impl Render for BreakpointPromptEditor {
24739 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24740 let editor_margins = *self.editor_margins.lock();
24741 let gutter_dimensions = editor_margins.gutter;
24742 h_flex()
24743 .key_context("Editor")
24744 .bg(cx.theme().colors().editor_background)
24745 .border_y_1()
24746 .border_color(cx.theme().status().info_border)
24747 .size_full()
24748 .py(window.line_height() / 2.5)
24749 .on_action(cx.listener(Self::confirm))
24750 .on_action(cx.listener(Self::cancel))
24751 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24752 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24753 }
24754}
24755
24756impl Focusable for BreakpointPromptEditor {
24757 fn focus_handle(&self, cx: &App) -> FocusHandle {
24758 self.prompt.focus_handle(cx)
24759 }
24760}
24761
24762fn all_edits_insertions_or_deletions(
24763 edits: &Vec<(Range<Anchor>, String)>,
24764 snapshot: &MultiBufferSnapshot,
24765) -> bool {
24766 let mut all_insertions = true;
24767 let mut all_deletions = true;
24768
24769 for (range, new_text) in edits.iter() {
24770 let range_is_empty = range.to_offset(snapshot).is_empty();
24771 let text_is_empty = new_text.is_empty();
24772
24773 if range_is_empty != text_is_empty {
24774 if range_is_empty {
24775 all_deletions = false;
24776 } else {
24777 all_insertions = false;
24778 }
24779 } else {
24780 return false;
24781 }
24782
24783 if !all_insertions && !all_deletions {
24784 return false;
24785 }
24786 }
24787 all_insertions || all_deletions
24788}
24789
24790struct MissingEditPredictionKeybindingTooltip;
24791
24792impl Render for MissingEditPredictionKeybindingTooltip {
24793 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24794 ui::tooltip_container(cx, |container, cx| {
24795 container
24796 .flex_shrink_0()
24797 .max_w_80()
24798 .min_h(rems_from_px(124.))
24799 .justify_between()
24800 .child(
24801 v_flex()
24802 .flex_1()
24803 .text_ui_sm(cx)
24804 .child(Label::new("Conflict with Accept Keybinding"))
24805 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24806 )
24807 .child(
24808 h_flex()
24809 .pb_1()
24810 .gap_1()
24811 .items_end()
24812 .w_full()
24813 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24814 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24815 }))
24816 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24817 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24818 })),
24819 )
24820 })
24821 }
24822}
24823
24824#[derive(Debug, Clone, Copy, PartialEq)]
24825pub struct LineHighlight {
24826 pub background: Background,
24827 pub border: Option<gpui::Hsla>,
24828 pub include_gutter: bool,
24829 pub type_id: Option<TypeId>,
24830}
24831
24832struct LineManipulationResult {
24833 pub new_text: String,
24834 pub line_count_before: usize,
24835 pub line_count_after: usize,
24836}
24837
24838fn render_diff_hunk_controls(
24839 row: u32,
24840 status: &DiffHunkStatus,
24841 hunk_range: Range<Anchor>,
24842 is_created_file: bool,
24843 line_height: Pixels,
24844 editor: &Entity<Editor>,
24845 _window: &mut Window,
24846 cx: &mut App,
24847) -> AnyElement {
24848 h_flex()
24849 .h(line_height)
24850 .mr_1()
24851 .gap_1()
24852 .px_0p5()
24853 .pb_1()
24854 .border_x_1()
24855 .border_b_1()
24856 .border_color(cx.theme().colors().border_variant)
24857 .rounded_b_lg()
24858 .bg(cx.theme().colors().editor_background)
24859 .gap_1()
24860 .block_mouse_except_scroll()
24861 .shadow_md()
24862 .child(if status.has_secondary_hunk() {
24863 Button::new(("stage", row as u64), "Stage")
24864 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24865 .tooltip({
24866 let focus_handle = editor.focus_handle(cx);
24867 move |window, cx| {
24868 Tooltip::for_action_in(
24869 "Stage Hunk",
24870 &::git::ToggleStaged,
24871 &focus_handle,
24872 window,
24873 cx,
24874 )
24875 }
24876 })
24877 .on_click({
24878 let editor = editor.clone();
24879 move |_event, _window, cx| {
24880 editor.update(cx, |editor, cx| {
24881 editor.stage_or_unstage_diff_hunks(
24882 true,
24883 vec![hunk_range.start..hunk_range.start],
24884 cx,
24885 );
24886 });
24887 }
24888 })
24889 } else {
24890 Button::new(("unstage", row as u64), "Unstage")
24891 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24892 .tooltip({
24893 let focus_handle = editor.focus_handle(cx);
24894 move |window, cx| {
24895 Tooltip::for_action_in(
24896 "Unstage Hunk",
24897 &::git::ToggleStaged,
24898 &focus_handle,
24899 window,
24900 cx,
24901 )
24902 }
24903 })
24904 .on_click({
24905 let editor = editor.clone();
24906 move |_event, _window, cx| {
24907 editor.update(cx, |editor, cx| {
24908 editor.stage_or_unstage_diff_hunks(
24909 false,
24910 vec![hunk_range.start..hunk_range.start],
24911 cx,
24912 );
24913 });
24914 }
24915 })
24916 })
24917 .child(
24918 Button::new(("restore", row as u64), "Restore")
24919 .tooltip({
24920 let focus_handle = editor.focus_handle(cx);
24921 move |window, cx| {
24922 Tooltip::for_action_in(
24923 "Restore Hunk",
24924 &::git::Restore,
24925 &focus_handle,
24926 window,
24927 cx,
24928 )
24929 }
24930 })
24931 .on_click({
24932 let editor = editor.clone();
24933 move |_event, window, cx| {
24934 editor.update(cx, |editor, cx| {
24935 let snapshot = editor.snapshot(window, cx);
24936 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24937 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24938 });
24939 }
24940 })
24941 .disabled(is_created_file),
24942 )
24943 .when(
24944 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24945 |el| {
24946 el.child(
24947 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24948 .shape(IconButtonShape::Square)
24949 .icon_size(IconSize::Small)
24950 // .disabled(!has_multiple_hunks)
24951 .tooltip({
24952 let focus_handle = editor.focus_handle(cx);
24953 move |window, cx| {
24954 Tooltip::for_action_in(
24955 "Next Hunk",
24956 &GoToHunk,
24957 &focus_handle,
24958 window,
24959 cx,
24960 )
24961 }
24962 })
24963 .on_click({
24964 let editor = editor.clone();
24965 move |_event, window, cx| {
24966 editor.update(cx, |editor, cx| {
24967 let snapshot = editor.snapshot(window, cx);
24968 let position =
24969 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24970 editor.go_to_hunk_before_or_after_position(
24971 &snapshot,
24972 position,
24973 Direction::Next,
24974 window,
24975 cx,
24976 );
24977 editor.expand_selected_diff_hunks(cx);
24978 });
24979 }
24980 }),
24981 )
24982 .child(
24983 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24984 .shape(IconButtonShape::Square)
24985 .icon_size(IconSize::Small)
24986 // .disabled(!has_multiple_hunks)
24987 .tooltip({
24988 let focus_handle = editor.focus_handle(cx);
24989 move |window, cx| {
24990 Tooltip::for_action_in(
24991 "Previous Hunk",
24992 &GoToPreviousHunk,
24993 &focus_handle,
24994 window,
24995 cx,
24996 )
24997 }
24998 })
24999 .on_click({
25000 let editor = editor.clone();
25001 move |_event, window, cx| {
25002 editor.update(cx, |editor, cx| {
25003 let snapshot = editor.snapshot(window, cx);
25004 let point =
25005 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25006 editor.go_to_hunk_before_or_after_position(
25007 &snapshot,
25008 point,
25009 Direction::Prev,
25010 window,
25011 cx,
25012 );
25013 editor.expand_selected_diff_hunks(cx);
25014 });
25015 }
25016 }),
25017 )
25018 },
25019 )
25020 .into_any_element()
25021}
25022
25023pub fn multibuffer_context_lines(cx: &App) -> u32 {
25024 EditorSettings::try_get(cx)
25025 .map(|settings| settings.excerpt_context_lines)
25026 .unwrap_or(2)
25027 .min(32)
25028}