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::{AGENT_REPLICA_ID, 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);
229
230pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
231pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
232pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
233
234pub type RenderDiffHunkControlsFn = Arc<
235 dyn Fn(
236 u32,
237 &DiffHunkStatus,
238 Range<Anchor>,
239 bool,
240 Pixels,
241 &Entity<Editor>,
242 &mut Window,
243 &mut App,
244 ) -> AnyElement,
245>;
246
247enum ReportEditorEvent {
248 Saved { auto_saved: bool },
249 EditorOpened,
250 Closed,
251}
252
253impl ReportEditorEvent {
254 pub fn event_type(&self) -> &'static str {
255 match self {
256 Self::Saved { .. } => "Editor Saved",
257 Self::EditorOpened => "Editor Opened",
258 Self::Closed => "Editor Closed",
259 }
260 }
261}
262
263struct InlineValueCache {
264 enabled: bool,
265 inlays: Vec<InlayId>,
266 refresh_task: Task<Option<()>>,
267}
268
269impl InlineValueCache {
270 fn new(enabled: bool) -> Self {
271 Self {
272 enabled,
273 inlays: Vec::new(),
274 refresh_task: Task::ready(None),
275 }
276 }
277}
278
279#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
280pub enum InlayId {
281 EditPrediction(u32),
282 DebuggerValue(u32),
283 // LSP
284 Hint(u32),
285 Color(u32),
286}
287
288impl InlayId {
289 fn id(&self) -> u32 {
290 match self {
291 Self::EditPrediction(id) => *id,
292 Self::DebuggerValue(id) => *id,
293 Self::Hint(id) => *id,
294 Self::Color(id) => *id,
295 }
296 }
297}
298
299pub enum ActiveDebugLine {}
300pub enum DebugStackFrameLine {}
301enum DocumentHighlightRead {}
302enum DocumentHighlightWrite {}
303enum InputComposition {}
304pub enum PendingInput {}
305enum SelectedTextHighlight {}
306
307pub enum ConflictsOuter {}
308pub enum ConflictsOurs {}
309pub enum ConflictsTheirs {}
310pub enum ConflictsOursMarker {}
311pub enum ConflictsTheirsMarker {}
312
313#[derive(Debug, Copy, Clone, PartialEq, Eq)]
314pub enum Navigated {
315 Yes,
316 No,
317}
318
319impl Navigated {
320 pub fn from_bool(yes: bool) -> Navigated {
321 if yes { Navigated::Yes } else { Navigated::No }
322 }
323}
324
325#[derive(Debug, Clone, PartialEq, Eq)]
326enum DisplayDiffHunk {
327 Folded {
328 display_row: DisplayRow,
329 },
330 Unfolded {
331 is_created_file: bool,
332 diff_base_byte_range: Range<usize>,
333 display_row_range: Range<DisplayRow>,
334 multi_buffer_range: Range<Anchor>,
335 status: DiffHunkStatus,
336 },
337}
338
339pub enum HideMouseCursorOrigin {
340 TypingAction,
341 MovementAction,
342}
343
344pub fn init_settings(cx: &mut App) {
345 EditorSettings::register(cx);
346}
347
348pub fn init(cx: &mut App) {
349 init_settings(cx);
350
351 cx.set_global(GlobalBlameRenderer(Arc::new(())));
352
353 workspace::register_project_item::<Editor>(cx);
354 workspace::FollowableViewRegistry::register::<Editor>(cx);
355 workspace::register_serializable_item::<Editor>(cx);
356
357 cx.observe_new(
358 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
359 workspace.register_action(Editor::new_file);
360 workspace.register_action(Editor::new_file_vertical);
361 workspace.register_action(Editor::new_file_horizontal);
362 workspace.register_action(Editor::cancel_language_server_work);
363 workspace.register_action(Editor::toggle_focus);
364 },
365 )
366 .detach();
367
368 cx.on_action(move |_: &workspace::NewFile, cx| {
369 let app_state = workspace::AppState::global(cx);
370 if let Some(app_state) = app_state.upgrade() {
371 workspace::open_new(
372 Default::default(),
373 app_state,
374 cx,
375 |workspace, window, cx| {
376 Editor::new_file(workspace, &Default::default(), window, cx)
377 },
378 )
379 .detach();
380 }
381 });
382 cx.on_action(move |_: &workspace::NewWindow, cx| {
383 let app_state = workspace::AppState::global(cx);
384 if let Some(app_state) = app_state.upgrade() {
385 workspace::open_new(
386 Default::default(),
387 app_state,
388 cx,
389 |workspace, window, cx| {
390 cx.activate(true);
391 Editor::new_file(workspace, &Default::default(), window, cx)
392 },
393 )
394 .detach();
395 }
396 });
397}
398
399pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
400 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
401}
402
403pub trait DiagnosticRenderer {
404 fn render_group(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
407 buffer_id: BufferId,
408 snapshot: EditorSnapshot,
409 editor: WeakEntity<Editor>,
410 cx: &mut App,
411 ) -> Vec<BlockProperties<Anchor>>;
412
413 fn render_hover(
414 &self,
415 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
416 range: Range<Point>,
417 buffer_id: BufferId,
418 cx: &mut App,
419 ) -> Option<Entity<markdown::Markdown>>;
420
421 fn open_link(
422 &self,
423 editor: &mut Editor,
424 link: SharedString,
425 window: &mut Window,
426 cx: &mut Context<Editor>,
427 );
428}
429
430pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
431
432impl GlobalDiagnosticRenderer {
433 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
434 cx.try_global::<Self>().map(|g| g.0.clone())
435 }
436}
437
438impl gpui::Global for GlobalDiagnosticRenderer {}
439pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
440 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
441}
442
443pub struct SearchWithinRange;
444
445trait InvalidationRegion {
446 fn ranges(&self) -> &[Range<Anchor>];
447}
448
449#[derive(Clone, Debug, PartialEq)]
450pub enum SelectPhase {
451 Begin {
452 position: DisplayPoint,
453 add: bool,
454 click_count: usize,
455 },
456 BeginColumnar {
457 position: DisplayPoint,
458 reset: bool,
459 mode: ColumnarMode,
460 goal_column: u32,
461 },
462 Extend {
463 position: DisplayPoint,
464 click_count: usize,
465 },
466 Update {
467 position: DisplayPoint,
468 goal_column: u32,
469 scroll_delta: gpui::Point<f32>,
470 },
471 End,
472}
473
474#[derive(Clone, Debug, PartialEq)]
475pub enum ColumnarMode {
476 FromMouse,
477 FromSelection,
478}
479
480#[derive(Clone, Debug)]
481pub enum SelectMode {
482 Character,
483 Word(Range<Anchor>),
484 Line(Range<Anchor>),
485 All,
486}
487
488#[derive(Clone, PartialEq, Eq, Debug)]
489pub enum EditorMode {
490 SingleLine,
491 AutoHeight {
492 min_lines: usize,
493 max_lines: Option<usize>,
494 },
495 Full {
496 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
497 scale_ui_elements_with_buffer_font_size: bool,
498 /// When set to `true`, the editor will render a background for the active line.
499 show_active_line_background: bool,
500 /// When set to `true`, the editor's height will be determined by its content.
501 sized_by_content: bool,
502 },
503 Minimap {
504 parent: WeakEntity<Editor>,
505 },
506}
507
508impl EditorMode {
509 pub fn full() -> Self {
510 Self::Full {
511 scale_ui_elements_with_buffer_font_size: true,
512 show_active_line_background: true,
513 sized_by_content: false,
514 }
515 }
516
517 #[inline]
518 pub fn is_full(&self) -> bool {
519 matches!(self, Self::Full { .. })
520 }
521
522 #[inline]
523 pub fn is_single_line(&self) -> bool {
524 matches!(self, Self::SingleLine { .. })
525 }
526
527 #[inline]
528 fn is_minimap(&self) -> bool {
529 matches!(self, Self::Minimap { .. })
530 }
531}
532
533#[derive(Copy, Clone, Debug)]
534pub enum SoftWrap {
535 /// Prefer not to wrap at all.
536 ///
537 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
538 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
539 GitDiff,
540 /// Prefer a single line generally, unless an overly long line is encountered.
541 None,
542 /// Soft wrap lines that exceed the editor width.
543 EditorWidth,
544 /// Soft wrap lines at the preferred line length.
545 Column(u32),
546 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
547 Bounded(u32),
548}
549
550#[derive(Clone)]
551pub struct EditorStyle {
552 pub background: Hsla,
553 pub border: Hsla,
554 pub local_player: PlayerColor,
555 pub text: TextStyle,
556 pub scrollbar_width: Pixels,
557 pub syntax: Arc<SyntaxTheme>,
558 pub status: StatusColors,
559 pub inlay_hints_style: HighlightStyle,
560 pub edit_prediction_styles: EditPredictionStyles,
561 pub unnecessary_code_fade: f32,
562 pub show_underlines: bool,
563}
564
565impl Default for EditorStyle {
566 fn default() -> Self {
567 Self {
568 background: Hsla::default(),
569 border: Hsla::default(),
570 local_player: PlayerColor::default(),
571 text: TextStyle::default(),
572 scrollbar_width: Pixels::default(),
573 syntax: Default::default(),
574 // HACK: Status colors don't have a real default.
575 // We should look into removing the status colors from the editor
576 // style and retrieve them directly from the theme.
577 status: StatusColors::dark(),
578 inlay_hints_style: HighlightStyle::default(),
579 edit_prediction_styles: EditPredictionStyles {
580 insertion: HighlightStyle::default(),
581 whitespace: HighlightStyle::default(),
582 },
583 unnecessary_code_fade: Default::default(),
584 show_underlines: true,
585 }
586 }
587}
588
589pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
590 let show_background = language_settings::language_settings(None, None, cx)
591 .inlay_hints
592 .show_background;
593
594 let mut style = cx.theme().syntax().get("hint");
595
596 if style.color.is_none() {
597 style.color = Some(cx.theme().status().hint);
598 }
599
600 if !show_background {
601 style.background_color = None;
602 return style;
603 }
604
605 if style.background_color.is_none() {
606 style.background_color = Some(cx.theme().status().hint_background);
607 }
608
609 style
610}
611
612pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
613 EditPredictionStyles {
614 insertion: HighlightStyle {
615 color: Some(cx.theme().status().predictive),
616 ..HighlightStyle::default()
617 },
618 whitespace: HighlightStyle {
619 background_color: Some(cx.theme().status().created_background),
620 ..HighlightStyle::default()
621 },
622 }
623}
624
625type CompletionId = usize;
626
627pub(crate) enum EditDisplayMode {
628 TabAccept,
629 DiffPopover,
630 Inline,
631}
632
633enum EditPrediction {
634 Edit {
635 edits: Vec<(Range<Anchor>, String)>,
636 edit_preview: Option<EditPreview>,
637 display_mode: EditDisplayMode,
638 snapshot: BufferSnapshot,
639 },
640 /// Move to a specific location in the active editor
641 MoveWithin {
642 target: Anchor,
643 snapshot: BufferSnapshot,
644 },
645 /// Move to a specific location in a different editor (not the active one)
646 MoveOutside {
647 target: language::Anchor,
648 snapshot: BufferSnapshot,
649 },
650}
651
652struct EditPredictionState {
653 inlay_ids: Vec<InlayId>,
654 completion: EditPrediction,
655 completion_id: Option<SharedString>,
656 invalidation_range: Option<Range<Anchor>>,
657}
658
659enum EditPredictionSettings {
660 Disabled,
661 Enabled {
662 show_in_menu: bool,
663 preview_requires_modifier: bool,
664 },
665}
666
667enum EditPredictionHighlight {}
668
669#[derive(Debug, Clone)]
670struct InlineDiagnostic {
671 message: SharedString,
672 group_id: usize,
673 is_primary: bool,
674 start: Point,
675 severity: lsp::DiagnosticSeverity,
676}
677
678pub enum MenuEditPredictionsPolicy {
679 Never,
680 ByProvider,
681}
682
683pub enum EditPredictionPreview {
684 /// Modifier is not pressed
685 Inactive { released_too_fast: bool },
686 /// Modifier pressed
687 Active {
688 since: Instant,
689 previous_scroll_position: Option<ScrollAnchor>,
690 },
691}
692
693impl EditPredictionPreview {
694 pub fn released_too_fast(&self) -> bool {
695 match self {
696 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
697 EditPredictionPreview::Active { .. } => false,
698 }
699 }
700
701 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
702 if let EditPredictionPreview::Active {
703 previous_scroll_position,
704 ..
705 } = self
706 {
707 *previous_scroll_position = scroll_position;
708 }
709 }
710}
711
712pub struct ContextMenuOptions {
713 pub min_entries_visible: usize,
714 pub max_entries_visible: usize,
715 pub placement: Option<ContextMenuPlacement>,
716}
717
718#[derive(Debug, Clone, PartialEq, Eq)]
719pub enum ContextMenuPlacement {
720 Above,
721 Below,
722}
723
724#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
725struct EditorActionId(usize);
726
727impl EditorActionId {
728 pub fn post_inc(&mut self) -> Self {
729 let answer = self.0;
730
731 *self = Self(answer + 1);
732
733 Self(answer)
734 }
735}
736
737// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
738// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
739
740type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
741type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
742
743#[derive(Default)]
744struct ScrollbarMarkerState {
745 scrollbar_size: Size<Pixels>,
746 dirty: bool,
747 markers: Arc<[PaintQuad]>,
748 pending_refresh: Option<Task<Result<()>>>,
749}
750
751impl ScrollbarMarkerState {
752 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
753 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
754 }
755}
756
757#[derive(Clone, Copy, PartialEq, Eq)]
758pub enum MinimapVisibility {
759 Disabled,
760 Enabled {
761 /// The configuration currently present in the users settings.
762 setting_configuration: bool,
763 /// Whether to override the currently set visibility from the users setting.
764 toggle_override: bool,
765 },
766}
767
768impl MinimapVisibility {
769 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
770 if mode.is_full() {
771 Self::Enabled {
772 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
773 toggle_override: false,
774 }
775 } else {
776 Self::Disabled
777 }
778 }
779
780 fn hidden(&self) -> Self {
781 match *self {
782 Self::Enabled {
783 setting_configuration,
784 ..
785 } => Self::Enabled {
786 setting_configuration,
787 toggle_override: setting_configuration,
788 },
789 Self::Disabled => Self::Disabled,
790 }
791 }
792
793 fn disabled(&self) -> bool {
794 matches!(*self, Self::Disabled)
795 }
796
797 fn settings_visibility(&self) -> bool {
798 match *self {
799 Self::Enabled {
800 setting_configuration,
801 ..
802 } => setting_configuration,
803 _ => false,
804 }
805 }
806
807 fn visible(&self) -> bool {
808 match *self {
809 Self::Enabled {
810 setting_configuration,
811 toggle_override,
812 } => setting_configuration ^ toggle_override,
813 _ => false,
814 }
815 }
816
817 fn toggle_visibility(&self) -> Self {
818 match *self {
819 Self::Enabled {
820 toggle_override,
821 setting_configuration,
822 } => Self::Enabled {
823 setting_configuration,
824 toggle_override: !toggle_override,
825 },
826 Self::Disabled => Self::Disabled,
827 }
828 }
829}
830
831#[derive(Clone, Debug)]
832struct RunnableTasks {
833 templates: Vec<(TaskSourceKind, TaskTemplate)>,
834 offset: multi_buffer::Anchor,
835 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
836 column: u32,
837 // Values of all named captures, including those starting with '_'
838 extra_variables: HashMap<String, String>,
839 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
840 context_range: Range<BufferOffset>,
841}
842
843impl RunnableTasks {
844 fn resolve<'a>(
845 &'a self,
846 cx: &'a task::TaskContext,
847 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
848 self.templates.iter().filter_map(|(kind, template)| {
849 template
850 .resolve_task(&kind.to_id_base(), cx)
851 .map(|task| (kind.clone(), task))
852 })
853 }
854}
855
856#[derive(Clone)]
857pub struct ResolvedTasks {
858 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
859 position: Anchor,
860}
861
862#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
863struct BufferOffset(usize);
864
865/// Addons allow storing per-editor state in other crates (e.g. Vim)
866pub trait Addon: 'static {
867 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
868
869 fn render_buffer_header_controls(
870 &self,
871 _: &ExcerptInfo,
872 _: &Window,
873 _: &App,
874 ) -> Option<AnyElement> {
875 None
876 }
877
878 fn to_any(&self) -> &dyn std::any::Any;
879
880 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
881 None
882 }
883}
884
885struct ChangeLocation {
886 current: Option<Vec<Anchor>>,
887 original: Vec<Anchor>,
888}
889impl ChangeLocation {
890 fn locations(&self) -> &[Anchor] {
891 self.current.as_ref().unwrap_or(&self.original)
892 }
893}
894
895/// A set of caret positions, registered when the editor was edited.
896pub struct ChangeList {
897 changes: Vec<ChangeLocation>,
898 /// Currently "selected" change.
899 position: Option<usize>,
900}
901
902impl ChangeList {
903 pub fn new() -> Self {
904 Self {
905 changes: Vec::new(),
906 position: None,
907 }
908 }
909
910 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
911 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
912 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
913 if self.changes.is_empty() {
914 return None;
915 }
916
917 let prev = self.position.unwrap_or(self.changes.len());
918 let next = if direction == Direction::Prev {
919 prev.saturating_sub(count)
920 } else {
921 (prev + count).min(self.changes.len() - 1)
922 };
923 self.position = Some(next);
924 self.changes.get(next).map(|change| change.locations())
925 }
926
927 /// Adds a new change to the list, resetting the change list position.
928 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
929 self.position.take();
930 if let Some(last) = self.changes.last_mut()
931 && group
932 {
933 last.current = Some(new_positions)
934 } else {
935 self.changes.push(ChangeLocation {
936 original: new_positions,
937 current: None,
938 });
939 }
940 }
941
942 pub fn last(&self) -> Option<&[Anchor]> {
943 self.changes.last().map(|change| change.locations())
944 }
945
946 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
947 self.changes.last().map(|change| change.original.as_slice())
948 }
949
950 pub fn invert_last_group(&mut self) {
951 if let Some(last) = self.changes.last_mut()
952 && let Some(current) = last.current.as_mut()
953 {
954 mem::swap(&mut last.original, current);
955 }
956 }
957}
958
959#[derive(Clone)]
960struct InlineBlamePopoverState {
961 scroll_handle: ScrollHandle,
962 commit_message: Option<ParsedCommitMessage>,
963 markdown: Entity<Markdown>,
964}
965
966struct InlineBlamePopover {
967 position: gpui::Point<Pixels>,
968 hide_task: Option<Task<()>>,
969 popover_bounds: Option<Bounds<Pixels>>,
970 popover_state: InlineBlamePopoverState,
971 keyboard_grace: bool,
972}
973
974enum SelectionDragState {
975 /// State when no drag related activity is detected.
976 None,
977 /// State when the mouse is down on a selection that is about to be dragged.
978 ReadyToDrag {
979 selection: Selection<Anchor>,
980 click_position: gpui::Point<Pixels>,
981 mouse_down_time: Instant,
982 },
983 /// State when the mouse is dragging the selection in the editor.
984 Dragging {
985 selection: Selection<Anchor>,
986 drop_cursor: Selection<Anchor>,
987 hide_drop_cursor: bool,
988 },
989}
990
991enum ColumnarSelectionState {
992 FromMouse {
993 selection_tail: Anchor,
994 display_point: Option<DisplayPoint>,
995 },
996 FromSelection {
997 selection_tail: Anchor,
998 },
999}
1000
1001/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1002/// a breakpoint on them.
1003#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1004struct PhantomBreakpointIndicator {
1005 display_row: DisplayRow,
1006 /// There's a small debounce between hovering over the line and showing the indicator.
1007 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1008 is_active: bool,
1009 collides_with_existing_breakpoint: bool,
1010}
1011
1012/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1013///
1014/// See the [module level documentation](self) for more information.
1015pub struct Editor {
1016 focus_handle: FocusHandle,
1017 last_focused_descendant: Option<WeakFocusHandle>,
1018 /// The text buffer being edited
1019 buffer: Entity<MultiBuffer>,
1020 /// Map of how text in the buffer should be displayed.
1021 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1022 pub display_map: Entity<DisplayMap>,
1023 placeholder_display_map: Option<Entity<DisplayMap>>,
1024 pub selections: SelectionsCollection,
1025 pub scroll_manager: ScrollManager,
1026 /// When inline assist editors are linked, they all render cursors because
1027 /// typing enters text into each of them, even the ones that aren't focused.
1028 pub(crate) show_cursor_when_unfocused: bool,
1029 columnar_selection_state: Option<ColumnarSelectionState>,
1030 add_selections_state: Option<AddSelectionsState>,
1031 select_next_state: Option<SelectNextState>,
1032 select_prev_state: Option<SelectNextState>,
1033 selection_history: SelectionHistory,
1034 defer_selection_effects: bool,
1035 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1036 autoclose_regions: Vec<AutocloseRegion>,
1037 snippet_stack: InvalidationStack<SnippetState>,
1038 select_syntax_node_history: SelectSyntaxNodeHistory,
1039 ime_transaction: Option<TransactionId>,
1040 pub diagnostics_max_severity: DiagnosticSeverity,
1041 active_diagnostics: ActiveDiagnostic,
1042 show_inline_diagnostics: bool,
1043 inline_diagnostics_update: Task<()>,
1044 inline_diagnostics_enabled: bool,
1045 diagnostics_enabled: bool,
1046 word_completions_enabled: bool,
1047 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1048 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1049 hard_wrap: Option<usize>,
1050 project: Option<Entity<Project>>,
1051 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1052 completion_provider: Option<Rc<dyn CompletionProvider>>,
1053 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1054 blink_manager: Entity<BlinkManager>,
1055 show_cursor_names: bool,
1056 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1057 pub show_local_selections: bool,
1058 mode: EditorMode,
1059 show_breadcrumbs: bool,
1060 show_gutter: bool,
1061 show_scrollbars: ScrollbarAxes,
1062 minimap_visibility: MinimapVisibility,
1063 offset_content: bool,
1064 disable_expand_excerpt_buttons: bool,
1065 show_line_numbers: Option<bool>,
1066 use_relative_line_numbers: Option<bool>,
1067 show_git_diff_gutter: Option<bool>,
1068 show_code_actions: Option<bool>,
1069 show_runnables: Option<bool>,
1070 show_breakpoints: Option<bool>,
1071 show_wrap_guides: Option<bool>,
1072 show_indent_guides: Option<bool>,
1073 highlight_order: usize,
1074 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1075 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1076 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1077 scrollbar_marker_state: ScrollbarMarkerState,
1078 active_indent_guides_state: ActiveIndentGuidesState,
1079 nav_history: Option<ItemNavHistory>,
1080 context_menu: RefCell<Option<CodeContextMenu>>,
1081 context_menu_options: Option<ContextMenuOptions>,
1082 mouse_context_menu: Option<MouseContextMenu>,
1083 completion_tasks: Vec<(CompletionId, Task<()>)>,
1084 inline_blame_popover: Option<InlineBlamePopover>,
1085 inline_blame_popover_show_task: Option<Task<()>>,
1086 signature_help_state: SignatureHelpState,
1087 auto_signature_help: Option<bool>,
1088 find_all_references_task_sources: Vec<Anchor>,
1089 next_completion_id: CompletionId,
1090 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1091 code_actions_task: Option<Task<Result<()>>>,
1092 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1093 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1094 document_highlights_task: Option<Task<()>>,
1095 linked_editing_range_task: Option<Task<Option<()>>>,
1096 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1097 pending_rename: Option<RenameState>,
1098 searchable: bool,
1099 cursor_shape: CursorShape,
1100 current_line_highlight: Option<CurrentLineHighlight>,
1101 collapse_matches: bool,
1102 autoindent_mode: Option<AutoindentMode>,
1103 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1104 input_enabled: bool,
1105 use_modal_editing: bool,
1106 read_only: bool,
1107 leader_id: Option<CollaboratorId>,
1108 remote_id: Option<ViewId>,
1109 pub hover_state: HoverState,
1110 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1111 gutter_hovered: bool,
1112 hovered_link_state: Option<HoveredLinkState>,
1113 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1114 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1115 active_edit_prediction: Option<EditPredictionState>,
1116 /// Used to prevent flickering as the user types while the menu is open
1117 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1118 edit_prediction_settings: EditPredictionSettings,
1119 edit_predictions_hidden_for_vim_mode: bool,
1120 show_edit_predictions_override: Option<bool>,
1121 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1122 edit_prediction_preview: EditPredictionPreview,
1123 edit_prediction_indent_conflict: bool,
1124 edit_prediction_requires_modifier_in_indent_conflict: bool,
1125 inlay_hint_cache: InlayHintCache,
1126 next_inlay_id: u32,
1127 next_color_inlay_id: u32,
1128 _subscriptions: Vec<Subscription>,
1129 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1130 gutter_dimensions: GutterDimensions,
1131 style: Option<EditorStyle>,
1132 text_style_refinement: Option<TextStyleRefinement>,
1133 next_editor_action_id: EditorActionId,
1134 editor_actions: Rc<
1135 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1136 >,
1137 use_autoclose: bool,
1138 use_auto_surround: bool,
1139 auto_replace_emoji_shortcode: bool,
1140 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1141 show_git_blame_gutter: bool,
1142 show_git_blame_inline: bool,
1143 show_git_blame_inline_delay_task: Option<Task<()>>,
1144 git_blame_inline_enabled: bool,
1145 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1146 serialize_dirty_buffers: bool,
1147 show_selection_menu: Option<bool>,
1148 blame: Option<Entity<GitBlame>>,
1149 blame_subscription: Option<Subscription>,
1150 custom_context_menu: Option<
1151 Box<
1152 dyn 'static
1153 + Fn(
1154 &mut Self,
1155 DisplayPoint,
1156 &mut Window,
1157 &mut Context<Self>,
1158 ) -> Option<Entity<ui::ContextMenu>>,
1159 >,
1160 >,
1161 last_bounds: Option<Bounds<Pixels>>,
1162 last_position_map: Option<Rc<PositionMap>>,
1163 expect_bounds_change: Option<Bounds<Pixels>>,
1164 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1165 tasks_update_task: Option<Task<()>>,
1166 breakpoint_store: Option<Entity<BreakpointStore>>,
1167 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1168 hovered_diff_hunk_row: Option<DisplayRow>,
1169 pull_diagnostics_task: Task<()>,
1170 in_project_search: bool,
1171 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1172 breadcrumb_header: Option<String>,
1173 focused_block: Option<FocusedBlock>,
1174 next_scroll_position: NextScrollCursorCenterTopBottom,
1175 addons: HashMap<TypeId, Box<dyn Addon>>,
1176 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1177 load_diff_task: Option<Shared<Task<()>>>,
1178 /// Whether we are temporarily displaying a diff other than git's
1179 temporary_diff_override: bool,
1180 selection_mark_mode: bool,
1181 toggle_fold_multiple_buffers: Task<()>,
1182 _scroll_cursor_center_top_bottom_task: Task<()>,
1183 serialize_selections: Task<()>,
1184 serialize_folds: Task<()>,
1185 mouse_cursor_hidden: bool,
1186 minimap: Option<Entity<Self>>,
1187 hide_mouse_mode: HideMouseMode,
1188 pub change_list: ChangeList,
1189 inline_value_cache: InlineValueCache,
1190 selection_drag_state: SelectionDragState,
1191 colors: Option<LspColorData>,
1192 folding_newlines: Task<()>,
1193 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1194}
1195
1196#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1197enum NextScrollCursorCenterTopBottom {
1198 #[default]
1199 Center,
1200 Top,
1201 Bottom,
1202}
1203
1204impl NextScrollCursorCenterTopBottom {
1205 fn next(&self) -> Self {
1206 match self {
1207 Self::Center => Self::Top,
1208 Self::Top => Self::Bottom,
1209 Self::Bottom => Self::Center,
1210 }
1211 }
1212}
1213
1214#[derive(Clone)]
1215pub struct EditorSnapshot {
1216 pub mode: EditorMode,
1217 show_gutter: bool,
1218 show_line_numbers: Option<bool>,
1219 show_git_diff_gutter: Option<bool>,
1220 show_code_actions: Option<bool>,
1221 show_runnables: Option<bool>,
1222 show_breakpoints: Option<bool>,
1223 git_blame_gutter_max_author_length: Option<usize>,
1224 pub display_snapshot: DisplaySnapshot,
1225 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1226 is_focused: bool,
1227 scroll_anchor: ScrollAnchor,
1228 ongoing_scroll: OngoingScroll,
1229 current_line_highlight: CurrentLineHighlight,
1230 gutter_hovered: bool,
1231}
1232
1233#[derive(Default, Debug, Clone, Copy)]
1234pub struct GutterDimensions {
1235 pub left_padding: Pixels,
1236 pub right_padding: Pixels,
1237 pub width: Pixels,
1238 pub margin: Pixels,
1239 pub git_blame_entries_width: Option<Pixels>,
1240}
1241
1242impl GutterDimensions {
1243 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1244 Self {
1245 margin: Self::default_gutter_margin(font_id, font_size, cx),
1246 ..Default::default()
1247 }
1248 }
1249
1250 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1251 -cx.text_system().descent(font_id, font_size)
1252 }
1253 /// The full width of the space taken up by the gutter.
1254 pub fn full_width(&self) -> Pixels {
1255 self.margin + self.width
1256 }
1257
1258 /// The width of the space reserved for the fold indicators,
1259 /// use alongside 'justify_end' and `gutter_width` to
1260 /// right align content with the line numbers
1261 pub fn fold_area_width(&self) -> Pixels {
1262 self.margin + self.right_padding
1263 }
1264}
1265
1266struct CharacterDimensions {
1267 em_width: Pixels,
1268 em_advance: Pixels,
1269 line_height: Pixels,
1270}
1271
1272#[derive(Debug)]
1273pub struct RemoteSelection {
1274 pub replica_id: ReplicaId,
1275 pub selection: Selection<Anchor>,
1276 pub cursor_shape: CursorShape,
1277 pub collaborator_id: CollaboratorId,
1278 pub line_mode: bool,
1279 pub user_name: Option<SharedString>,
1280 pub color: PlayerColor,
1281}
1282
1283#[derive(Clone, Debug)]
1284struct SelectionHistoryEntry {
1285 selections: Arc<[Selection<Anchor>]>,
1286 select_next_state: Option<SelectNextState>,
1287 select_prev_state: Option<SelectNextState>,
1288 add_selections_state: Option<AddSelectionsState>,
1289}
1290
1291#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1292enum SelectionHistoryMode {
1293 Normal,
1294 Undoing,
1295 Redoing,
1296 Skipping,
1297}
1298
1299#[derive(Clone, PartialEq, Eq, Hash)]
1300struct HoveredCursor {
1301 replica_id: u16,
1302 selection_id: usize,
1303}
1304
1305impl Default for SelectionHistoryMode {
1306 fn default() -> Self {
1307 Self::Normal
1308 }
1309}
1310
1311#[derive(Debug)]
1312/// SelectionEffects controls the side-effects of updating the selection.
1313///
1314/// The default behaviour does "what you mostly want":
1315/// - it pushes to the nav history if the cursor moved by >10 lines
1316/// - it re-triggers completion requests
1317/// - it scrolls to fit
1318///
1319/// You might want to modify these behaviours. For example when doing a "jump"
1320/// like go to definition, we always want to add to nav history; but when scrolling
1321/// in vim mode we never do.
1322///
1323/// Similarly, you might want to disable scrolling if you don't want the viewport to
1324/// move.
1325#[derive(Clone)]
1326pub struct SelectionEffects {
1327 nav_history: Option<bool>,
1328 completions: bool,
1329 scroll: Option<Autoscroll>,
1330}
1331
1332impl Default for SelectionEffects {
1333 fn default() -> Self {
1334 Self {
1335 nav_history: None,
1336 completions: true,
1337 scroll: Some(Autoscroll::fit()),
1338 }
1339 }
1340}
1341impl SelectionEffects {
1342 pub fn scroll(scroll: Autoscroll) -> Self {
1343 Self {
1344 scroll: Some(scroll),
1345 ..Default::default()
1346 }
1347 }
1348
1349 pub fn no_scroll() -> Self {
1350 Self {
1351 scroll: None,
1352 ..Default::default()
1353 }
1354 }
1355
1356 pub fn completions(self, completions: bool) -> Self {
1357 Self {
1358 completions,
1359 ..self
1360 }
1361 }
1362
1363 pub fn nav_history(self, nav_history: bool) -> Self {
1364 Self {
1365 nav_history: Some(nav_history),
1366 ..self
1367 }
1368 }
1369}
1370
1371struct DeferredSelectionEffectsState {
1372 changed: bool,
1373 effects: SelectionEffects,
1374 old_cursor_position: Anchor,
1375 history_entry: SelectionHistoryEntry,
1376}
1377
1378#[derive(Default)]
1379struct SelectionHistory {
1380 #[allow(clippy::type_complexity)]
1381 selections_by_transaction:
1382 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1383 mode: SelectionHistoryMode,
1384 undo_stack: VecDeque<SelectionHistoryEntry>,
1385 redo_stack: VecDeque<SelectionHistoryEntry>,
1386}
1387
1388impl SelectionHistory {
1389 #[track_caller]
1390 fn insert_transaction(
1391 &mut self,
1392 transaction_id: TransactionId,
1393 selections: Arc<[Selection<Anchor>]>,
1394 ) {
1395 if selections.is_empty() {
1396 log::error!(
1397 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1398 std::panic::Location::caller()
1399 );
1400 return;
1401 }
1402 self.selections_by_transaction
1403 .insert(transaction_id, (selections, None));
1404 }
1405
1406 #[allow(clippy::type_complexity)]
1407 fn transaction(
1408 &self,
1409 transaction_id: TransactionId,
1410 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1411 self.selections_by_transaction.get(&transaction_id)
1412 }
1413
1414 #[allow(clippy::type_complexity)]
1415 fn transaction_mut(
1416 &mut self,
1417 transaction_id: TransactionId,
1418 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1419 self.selections_by_transaction.get_mut(&transaction_id)
1420 }
1421
1422 fn push(&mut self, entry: SelectionHistoryEntry) {
1423 if !entry.selections.is_empty() {
1424 match self.mode {
1425 SelectionHistoryMode::Normal => {
1426 self.push_undo(entry);
1427 self.redo_stack.clear();
1428 }
1429 SelectionHistoryMode::Undoing => self.push_redo(entry),
1430 SelectionHistoryMode::Redoing => self.push_undo(entry),
1431 SelectionHistoryMode::Skipping => {}
1432 }
1433 }
1434 }
1435
1436 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1437 if self
1438 .undo_stack
1439 .back()
1440 .is_none_or(|e| e.selections != entry.selections)
1441 {
1442 self.undo_stack.push_back(entry);
1443 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1444 self.undo_stack.pop_front();
1445 }
1446 }
1447 }
1448
1449 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1450 if self
1451 .redo_stack
1452 .back()
1453 .is_none_or(|e| e.selections != entry.selections)
1454 {
1455 self.redo_stack.push_back(entry);
1456 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1457 self.redo_stack.pop_front();
1458 }
1459 }
1460 }
1461}
1462
1463#[derive(Clone, Copy)]
1464pub struct RowHighlightOptions {
1465 pub autoscroll: bool,
1466 pub include_gutter: bool,
1467}
1468
1469impl Default for RowHighlightOptions {
1470 fn default() -> Self {
1471 Self {
1472 autoscroll: Default::default(),
1473 include_gutter: true,
1474 }
1475 }
1476}
1477
1478struct RowHighlight {
1479 index: usize,
1480 range: Range<Anchor>,
1481 color: Hsla,
1482 options: RowHighlightOptions,
1483 type_id: TypeId,
1484}
1485
1486#[derive(Clone, Debug)]
1487struct AddSelectionsState {
1488 groups: Vec<AddSelectionsGroup>,
1489}
1490
1491#[derive(Clone, Debug)]
1492struct AddSelectionsGroup {
1493 above: bool,
1494 stack: Vec<usize>,
1495}
1496
1497#[derive(Clone)]
1498struct SelectNextState {
1499 query: AhoCorasick,
1500 wordwise: bool,
1501 done: bool,
1502}
1503
1504impl std::fmt::Debug for SelectNextState {
1505 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1506 f.debug_struct(std::any::type_name::<Self>())
1507 .field("wordwise", &self.wordwise)
1508 .field("done", &self.done)
1509 .finish()
1510 }
1511}
1512
1513#[derive(Debug)]
1514struct AutocloseRegion {
1515 selection_id: usize,
1516 range: Range<Anchor>,
1517 pair: BracketPair,
1518}
1519
1520#[derive(Debug)]
1521struct SnippetState {
1522 ranges: Vec<Vec<Range<Anchor>>>,
1523 active_index: usize,
1524 choices: Vec<Option<Vec<String>>>,
1525}
1526
1527#[doc(hidden)]
1528pub struct RenameState {
1529 pub range: Range<Anchor>,
1530 pub old_name: Arc<str>,
1531 pub editor: Entity<Editor>,
1532 block_id: CustomBlockId,
1533}
1534
1535struct InvalidationStack<T>(Vec<T>);
1536
1537struct RegisteredEditPredictionProvider {
1538 provider: Arc<dyn EditPredictionProviderHandle>,
1539 _subscription: Subscription,
1540}
1541
1542#[derive(Debug, PartialEq, Eq)]
1543pub struct ActiveDiagnosticGroup {
1544 pub active_range: Range<Anchor>,
1545 pub active_message: String,
1546 pub group_id: usize,
1547 pub blocks: HashSet<CustomBlockId>,
1548}
1549
1550#[derive(Debug, PartialEq, Eq)]
1551
1552pub(crate) enum ActiveDiagnostic {
1553 None,
1554 All,
1555 Group(ActiveDiagnosticGroup),
1556}
1557
1558#[derive(Serialize, Deserialize, Clone, Debug)]
1559pub struct ClipboardSelection {
1560 /// The number of bytes in this selection.
1561 pub len: usize,
1562 /// Whether this was a full-line selection.
1563 pub is_entire_line: bool,
1564 /// The indentation of the first line when this content was originally copied.
1565 pub first_line_indent: u32,
1566}
1567
1568// selections, scroll behavior, was newest selection reversed
1569type SelectSyntaxNodeHistoryState = (
1570 Box<[Selection<usize>]>,
1571 SelectSyntaxNodeScrollBehavior,
1572 bool,
1573);
1574
1575#[derive(Default)]
1576struct SelectSyntaxNodeHistory {
1577 stack: Vec<SelectSyntaxNodeHistoryState>,
1578 // disable temporarily to allow changing selections without losing the stack
1579 pub disable_clearing: bool,
1580}
1581
1582impl SelectSyntaxNodeHistory {
1583 pub fn try_clear(&mut self) {
1584 if !self.disable_clearing {
1585 self.stack.clear();
1586 }
1587 }
1588
1589 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1590 self.stack.push(selection);
1591 }
1592
1593 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1594 self.stack.pop()
1595 }
1596}
1597
1598enum SelectSyntaxNodeScrollBehavior {
1599 CursorTop,
1600 FitSelection,
1601 CursorBottom,
1602}
1603
1604#[derive(Debug)]
1605pub(crate) struct NavigationData {
1606 cursor_anchor: Anchor,
1607 cursor_position: Point,
1608 scroll_anchor: ScrollAnchor,
1609 scroll_top_row: u32,
1610}
1611
1612#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1613pub enum GotoDefinitionKind {
1614 Symbol,
1615 Declaration,
1616 Type,
1617 Implementation,
1618}
1619
1620#[derive(Debug, Clone)]
1621enum InlayHintRefreshReason {
1622 ModifiersChanged(bool),
1623 Toggle(bool),
1624 SettingsChange(InlayHintSettings),
1625 NewLinesShown,
1626 BufferEdited(HashSet<Arc<Language>>),
1627 RefreshRequested,
1628 ExcerptsRemoved(Vec<ExcerptId>),
1629}
1630
1631impl InlayHintRefreshReason {
1632 fn description(&self) -> &'static str {
1633 match self {
1634 Self::ModifiersChanged(_) => "modifiers changed",
1635 Self::Toggle(_) => "toggle",
1636 Self::SettingsChange(_) => "settings change",
1637 Self::NewLinesShown => "new lines shown",
1638 Self::BufferEdited(_) => "buffer edited",
1639 Self::RefreshRequested => "refresh requested",
1640 Self::ExcerptsRemoved(_) => "excerpts removed",
1641 }
1642 }
1643}
1644
1645pub enum FormatTarget {
1646 Buffers(HashSet<Entity<Buffer>>),
1647 Ranges(Vec<Range<MultiBufferPoint>>),
1648}
1649
1650pub(crate) struct FocusedBlock {
1651 id: BlockId,
1652 focus_handle: WeakFocusHandle,
1653}
1654
1655#[derive(Clone)]
1656enum JumpData {
1657 MultiBufferRow {
1658 row: MultiBufferRow,
1659 line_offset_from_top: u32,
1660 },
1661 MultiBufferPoint {
1662 excerpt_id: ExcerptId,
1663 position: Point,
1664 anchor: text::Anchor,
1665 line_offset_from_top: u32,
1666 },
1667}
1668
1669pub enum MultibufferSelectionMode {
1670 First,
1671 All,
1672}
1673
1674#[derive(Clone, Copy, Debug, Default)]
1675pub struct RewrapOptions {
1676 pub override_language_settings: bool,
1677 pub preserve_existing_whitespace: bool,
1678}
1679
1680impl Editor {
1681 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1682 let buffer = cx.new(|cx| Buffer::local("", cx));
1683 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1684 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1685 }
1686
1687 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1688 let buffer = cx.new(|cx| Buffer::local("", cx));
1689 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1690 Self::new(EditorMode::full(), buffer, None, window, cx)
1691 }
1692
1693 pub fn auto_height(
1694 min_lines: usize,
1695 max_lines: usize,
1696 window: &mut Window,
1697 cx: &mut Context<Self>,
1698 ) -> Self {
1699 let buffer = cx.new(|cx| Buffer::local("", cx));
1700 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1701 Self::new(
1702 EditorMode::AutoHeight {
1703 min_lines,
1704 max_lines: Some(max_lines),
1705 },
1706 buffer,
1707 None,
1708 window,
1709 cx,
1710 )
1711 }
1712
1713 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1714 /// The editor grows as tall as needed to fit its content.
1715 pub fn auto_height_unbounded(
1716 min_lines: usize,
1717 window: &mut Window,
1718 cx: &mut Context<Self>,
1719 ) -> Self {
1720 let buffer = cx.new(|cx| Buffer::local("", cx));
1721 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1722 Self::new(
1723 EditorMode::AutoHeight {
1724 min_lines,
1725 max_lines: None,
1726 },
1727 buffer,
1728 None,
1729 window,
1730 cx,
1731 )
1732 }
1733
1734 pub fn for_buffer(
1735 buffer: Entity<Buffer>,
1736 project: Option<Entity<Project>>,
1737 window: &mut Window,
1738 cx: &mut Context<Self>,
1739 ) -> Self {
1740 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1741 Self::new(EditorMode::full(), buffer, project, window, cx)
1742 }
1743
1744 pub fn for_multibuffer(
1745 buffer: Entity<MultiBuffer>,
1746 project: Option<Entity<Project>>,
1747 window: &mut Window,
1748 cx: &mut Context<Self>,
1749 ) -> Self {
1750 Self::new(EditorMode::full(), buffer, project, window, cx)
1751 }
1752
1753 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1754 let mut clone = Self::new(
1755 self.mode.clone(),
1756 self.buffer.clone(),
1757 self.project.clone(),
1758 window,
1759 cx,
1760 );
1761 self.display_map.update(cx, |display_map, cx| {
1762 let snapshot = display_map.snapshot(cx);
1763 clone.display_map.update(cx, |display_map, cx| {
1764 display_map.set_state(&snapshot, cx);
1765 });
1766 });
1767 clone.folds_did_change(cx);
1768 clone.selections.clone_state(&self.selections);
1769 clone.scroll_manager.clone_state(&self.scroll_manager);
1770 clone.searchable = self.searchable;
1771 clone.read_only = self.read_only;
1772 clone
1773 }
1774
1775 pub fn new(
1776 mode: EditorMode,
1777 buffer: Entity<MultiBuffer>,
1778 project: Option<Entity<Project>>,
1779 window: &mut Window,
1780 cx: &mut Context<Self>,
1781 ) -> Self {
1782 Editor::new_internal(mode, buffer, project, None, window, cx)
1783 }
1784
1785 fn new_internal(
1786 mode: EditorMode,
1787 buffer: Entity<MultiBuffer>,
1788 project: Option<Entity<Project>>,
1789 display_map: Option<Entity<DisplayMap>>,
1790 window: &mut Window,
1791 cx: &mut Context<Self>,
1792 ) -> Self {
1793 debug_assert!(
1794 display_map.is_none() || mode.is_minimap(),
1795 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1796 );
1797
1798 let full_mode = mode.is_full();
1799 let is_minimap = mode.is_minimap();
1800 let diagnostics_max_severity = if full_mode {
1801 EditorSettings::get_global(cx)
1802 .diagnostics_max_severity
1803 .unwrap_or(DiagnosticSeverity::Hint)
1804 } else {
1805 DiagnosticSeverity::Off
1806 };
1807 let style = window.text_style();
1808 let font_size = style.font_size.to_pixels(window.rem_size());
1809 let editor = cx.entity().downgrade();
1810 let fold_placeholder = FoldPlaceholder {
1811 constrain_width: false,
1812 render: Arc::new(move |fold_id, fold_range, cx| {
1813 let editor = editor.clone();
1814 div()
1815 .id(fold_id)
1816 .bg(cx.theme().colors().ghost_element_background)
1817 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1818 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1819 .rounded_xs()
1820 .size_full()
1821 .cursor_pointer()
1822 .child("⋯")
1823 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1824 .on_click(move |_, _window, cx| {
1825 editor
1826 .update(cx, |editor, cx| {
1827 editor.unfold_ranges(
1828 &[fold_range.start..fold_range.end],
1829 true,
1830 false,
1831 cx,
1832 );
1833 cx.stop_propagation();
1834 })
1835 .ok();
1836 })
1837 .into_any()
1838 }),
1839 merge_adjacent: true,
1840 ..FoldPlaceholder::default()
1841 };
1842 let display_map = display_map.unwrap_or_else(|| {
1843 cx.new(|cx| {
1844 DisplayMap::new(
1845 buffer.clone(),
1846 style.font(),
1847 font_size,
1848 None,
1849 FILE_HEADER_HEIGHT,
1850 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1851 fold_placeholder,
1852 diagnostics_max_severity,
1853 cx,
1854 )
1855 })
1856 });
1857
1858 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1859
1860 let blink_manager = cx.new(|cx| {
1861 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1862 if is_minimap {
1863 blink_manager.disable(cx);
1864 }
1865 blink_manager
1866 });
1867
1868 let soft_wrap_mode_override =
1869 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1870
1871 let mut project_subscriptions = Vec::new();
1872 if full_mode && let Some(project) = project.as_ref() {
1873 project_subscriptions.push(cx.subscribe_in(
1874 project,
1875 window,
1876 |editor, _, event, window, cx| match event {
1877 project::Event::RefreshCodeLens => {
1878 // we always query lens with actions, without storing them, always refreshing them
1879 }
1880 project::Event::RefreshInlayHints => {
1881 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1882 }
1883 project::Event::LanguageServerAdded(..)
1884 | project::Event::LanguageServerRemoved(..) => {
1885 if editor.tasks_update_task.is_none() {
1886 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1887 }
1888 }
1889 project::Event::SnippetEdit(id, snippet_edits) => {
1890 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1891 let focus_handle = editor.focus_handle(cx);
1892 if focus_handle.is_focused(window) {
1893 let snapshot = buffer.read(cx).snapshot();
1894 for (range, snippet) in snippet_edits {
1895 let editor_range =
1896 language::range_from_lsp(*range).to_offset(&snapshot);
1897 editor
1898 .insert_snippet(
1899 &[editor_range],
1900 snippet.clone(),
1901 window,
1902 cx,
1903 )
1904 .ok();
1905 }
1906 }
1907 }
1908 }
1909 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1910 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1911 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1912 }
1913 }
1914
1915 project::Event::EntryRenamed(transaction) => {
1916 let Some(workspace) = editor.workspace() else {
1917 return;
1918 };
1919 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1920 else {
1921 return;
1922 };
1923 if active_editor.entity_id() == cx.entity_id() {
1924 let edited_buffers_already_open = {
1925 let other_editors: Vec<Entity<Editor>> = workspace
1926 .read(cx)
1927 .panes()
1928 .iter()
1929 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1930 .filter(|editor| editor.entity_id() != cx.entity_id())
1931 .collect();
1932
1933 transaction.0.keys().all(|buffer| {
1934 other_editors.iter().any(|editor| {
1935 let multi_buffer = editor.read(cx).buffer();
1936 multi_buffer.read(cx).is_singleton()
1937 && multi_buffer.read(cx).as_singleton().map_or(
1938 false,
1939 |singleton| {
1940 singleton.entity_id() == buffer.entity_id()
1941 },
1942 )
1943 })
1944 })
1945 };
1946
1947 if !edited_buffers_already_open {
1948 let workspace = workspace.downgrade();
1949 let transaction = transaction.clone();
1950 cx.defer_in(window, move |_, window, cx| {
1951 cx.spawn_in(window, async move |editor, cx| {
1952 Self::open_project_transaction(
1953 &editor,
1954 workspace,
1955 transaction,
1956 "Rename".to_string(),
1957 cx,
1958 )
1959 .await
1960 .ok()
1961 })
1962 .detach();
1963 });
1964 }
1965 }
1966 }
1967
1968 _ => {}
1969 },
1970 ));
1971 if let Some(task_inventory) = project
1972 .read(cx)
1973 .task_store()
1974 .read(cx)
1975 .task_inventory()
1976 .cloned()
1977 {
1978 project_subscriptions.push(cx.observe_in(
1979 &task_inventory,
1980 window,
1981 |editor, _, window, cx| {
1982 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1983 },
1984 ));
1985 };
1986
1987 project_subscriptions.push(cx.subscribe_in(
1988 &project.read(cx).breakpoint_store(),
1989 window,
1990 |editor, _, event, window, cx| match event {
1991 BreakpointStoreEvent::ClearDebugLines => {
1992 editor.clear_row_highlights::<ActiveDebugLine>();
1993 editor.refresh_inline_values(cx);
1994 }
1995 BreakpointStoreEvent::SetDebugLine => {
1996 if editor.go_to_active_debug_line(window, cx) {
1997 cx.stop_propagation();
1998 }
1999
2000 editor.refresh_inline_values(cx);
2001 }
2002 _ => {}
2003 },
2004 ));
2005 let git_store = project.read(cx).git_store().clone();
2006 let project = project.clone();
2007 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2008 if let GitStoreEvent::RepositoryUpdated(
2009 _,
2010 RepositoryEvent::Updated {
2011 new_instance: true, ..
2012 },
2013 _,
2014 ) = event
2015 {
2016 this.load_diff_task = Some(
2017 update_uncommitted_diff_for_buffer(
2018 cx.entity(),
2019 &project,
2020 this.buffer.read(cx).all_buffers(),
2021 this.buffer.clone(),
2022 cx,
2023 )
2024 .shared(),
2025 );
2026 }
2027 }));
2028 }
2029
2030 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2031
2032 let inlay_hint_settings =
2033 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2034 let focus_handle = cx.focus_handle();
2035 if !is_minimap {
2036 cx.on_focus(&focus_handle, window, Self::handle_focus)
2037 .detach();
2038 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2039 .detach();
2040 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2041 .detach();
2042 cx.on_blur(&focus_handle, window, Self::handle_blur)
2043 .detach();
2044 cx.observe_pending_input(window, Self::observe_pending_input)
2045 .detach();
2046 }
2047
2048 let show_indent_guides =
2049 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2050 Some(false)
2051 } else {
2052 None
2053 };
2054
2055 let breakpoint_store = match (&mode, project.as_ref()) {
2056 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2057 _ => None,
2058 };
2059
2060 let mut code_action_providers = Vec::new();
2061 let mut load_uncommitted_diff = None;
2062 if let Some(project) = project.clone() {
2063 load_uncommitted_diff = Some(
2064 update_uncommitted_diff_for_buffer(
2065 cx.entity(),
2066 &project,
2067 buffer.read(cx).all_buffers(),
2068 buffer.clone(),
2069 cx,
2070 )
2071 .shared(),
2072 );
2073 code_action_providers.push(Rc::new(project) as Rc<_>);
2074 }
2075
2076 let mut editor = Self {
2077 focus_handle,
2078 show_cursor_when_unfocused: false,
2079 last_focused_descendant: None,
2080 buffer: buffer.clone(),
2081 display_map: display_map.clone(),
2082 placeholder_display_map: None,
2083 selections,
2084 scroll_manager: ScrollManager::new(cx),
2085 columnar_selection_state: None,
2086 add_selections_state: None,
2087 select_next_state: None,
2088 select_prev_state: None,
2089 selection_history: SelectionHistory::default(),
2090 defer_selection_effects: false,
2091 deferred_selection_effects_state: None,
2092 autoclose_regions: Vec::new(),
2093 snippet_stack: InvalidationStack::default(),
2094 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2095 ime_transaction: None,
2096 active_diagnostics: ActiveDiagnostic::None,
2097 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2098 inline_diagnostics_update: Task::ready(()),
2099 inline_diagnostics: Vec::new(),
2100 soft_wrap_mode_override,
2101 diagnostics_max_severity,
2102 hard_wrap: None,
2103 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2104 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2105 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2106 project,
2107 blink_manager: blink_manager.clone(),
2108 show_local_selections: true,
2109 show_scrollbars: ScrollbarAxes {
2110 horizontal: full_mode,
2111 vertical: full_mode,
2112 },
2113 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2114 offset_content: !matches!(mode, EditorMode::SingleLine),
2115 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2116 show_gutter: full_mode,
2117 show_line_numbers: (!full_mode).then_some(false),
2118 use_relative_line_numbers: None,
2119 disable_expand_excerpt_buttons: !full_mode,
2120 show_git_diff_gutter: None,
2121 show_code_actions: None,
2122 show_runnables: None,
2123 show_breakpoints: None,
2124 show_wrap_guides: None,
2125 show_indent_guides,
2126 highlight_order: 0,
2127 highlighted_rows: HashMap::default(),
2128 background_highlights: HashMap::default(),
2129 gutter_highlights: HashMap::default(),
2130 scrollbar_marker_state: ScrollbarMarkerState::default(),
2131 active_indent_guides_state: ActiveIndentGuidesState::default(),
2132 nav_history: None,
2133 context_menu: RefCell::new(None),
2134 context_menu_options: None,
2135 mouse_context_menu: None,
2136 completion_tasks: Vec::new(),
2137 inline_blame_popover: None,
2138 inline_blame_popover_show_task: None,
2139 signature_help_state: SignatureHelpState::default(),
2140 auto_signature_help: None,
2141 find_all_references_task_sources: Vec::new(),
2142 next_completion_id: 0,
2143 next_inlay_id: 0,
2144 code_action_providers,
2145 available_code_actions: None,
2146 code_actions_task: None,
2147 quick_selection_highlight_task: None,
2148 debounced_selection_highlight_task: None,
2149 document_highlights_task: None,
2150 linked_editing_range_task: None,
2151 pending_rename: None,
2152 searchable: !is_minimap,
2153 cursor_shape: EditorSettings::get_global(cx)
2154 .cursor_shape
2155 .unwrap_or_default(),
2156 current_line_highlight: None,
2157 autoindent_mode: Some(AutoindentMode::EachLine),
2158 collapse_matches: false,
2159 workspace: None,
2160 input_enabled: !is_minimap,
2161 use_modal_editing: full_mode,
2162 read_only: is_minimap,
2163 use_autoclose: true,
2164 use_auto_surround: true,
2165 auto_replace_emoji_shortcode: false,
2166 jsx_tag_auto_close_enabled_in_any_buffer: false,
2167 leader_id: None,
2168 remote_id: None,
2169 hover_state: HoverState::default(),
2170 pending_mouse_down: None,
2171 hovered_link_state: None,
2172 edit_prediction_provider: None,
2173 active_edit_prediction: None,
2174 stale_edit_prediction_in_menu: None,
2175 edit_prediction_preview: EditPredictionPreview::Inactive {
2176 released_too_fast: false,
2177 },
2178 inline_diagnostics_enabled: full_mode,
2179 diagnostics_enabled: full_mode,
2180 word_completions_enabled: full_mode,
2181 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2182 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2183 gutter_hovered: false,
2184 pixel_position_of_newest_cursor: None,
2185 last_bounds: None,
2186 last_position_map: None,
2187 expect_bounds_change: None,
2188 gutter_dimensions: GutterDimensions::default(),
2189 style: None,
2190 show_cursor_names: false,
2191 hovered_cursors: HashMap::default(),
2192 next_editor_action_id: EditorActionId::default(),
2193 editor_actions: Rc::default(),
2194 edit_predictions_hidden_for_vim_mode: false,
2195 show_edit_predictions_override: None,
2196 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2197 edit_prediction_settings: EditPredictionSettings::Disabled,
2198 edit_prediction_indent_conflict: false,
2199 edit_prediction_requires_modifier_in_indent_conflict: true,
2200 custom_context_menu: None,
2201 show_git_blame_gutter: false,
2202 show_git_blame_inline: false,
2203 show_selection_menu: None,
2204 show_git_blame_inline_delay_task: None,
2205 git_blame_inline_enabled: full_mode
2206 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2207 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2208 serialize_dirty_buffers: !is_minimap
2209 && ProjectSettings::get_global(cx)
2210 .session
2211 .restore_unsaved_buffers,
2212 blame: None,
2213 blame_subscription: None,
2214 tasks: BTreeMap::default(),
2215
2216 breakpoint_store,
2217 gutter_breakpoint_indicator: (None, None),
2218 hovered_diff_hunk_row: None,
2219 _subscriptions: (!is_minimap)
2220 .then(|| {
2221 vec![
2222 cx.observe(&buffer, Self::on_buffer_changed),
2223 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2224 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2225 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2226 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2227 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2228 cx.observe_window_activation(window, |editor, window, cx| {
2229 let active = window.is_window_active();
2230 editor.blink_manager.update(cx, |blink_manager, cx| {
2231 if active {
2232 blink_manager.enable(cx);
2233 } else {
2234 blink_manager.disable(cx);
2235 }
2236 });
2237 if active {
2238 editor.show_mouse_cursor(cx);
2239 }
2240 }),
2241 ]
2242 })
2243 .unwrap_or_default(),
2244 tasks_update_task: None,
2245 pull_diagnostics_task: Task::ready(()),
2246 colors: None,
2247 next_color_inlay_id: 0,
2248 linked_edit_ranges: Default::default(),
2249 in_project_search: false,
2250 previous_search_ranges: None,
2251 breadcrumb_header: None,
2252 focused_block: None,
2253 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2254 addons: HashMap::default(),
2255 registered_buffers: HashMap::default(),
2256 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2257 selection_mark_mode: false,
2258 toggle_fold_multiple_buffers: Task::ready(()),
2259 serialize_selections: Task::ready(()),
2260 serialize_folds: Task::ready(()),
2261 text_style_refinement: None,
2262 load_diff_task: load_uncommitted_diff,
2263 temporary_diff_override: false,
2264 mouse_cursor_hidden: false,
2265 minimap: None,
2266 hide_mouse_mode: EditorSettings::get_global(cx)
2267 .hide_mouse
2268 .unwrap_or_default(),
2269 change_list: ChangeList::new(),
2270 mode,
2271 selection_drag_state: SelectionDragState::None,
2272 folding_newlines: Task::ready(()),
2273 lookup_key: None,
2274 };
2275
2276 if is_minimap {
2277 return editor;
2278 }
2279
2280 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2281 editor
2282 ._subscriptions
2283 .push(cx.observe(breakpoints, |_, _, cx| {
2284 cx.notify();
2285 }));
2286 }
2287 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2288 editor._subscriptions.extend(project_subscriptions);
2289
2290 editor._subscriptions.push(cx.subscribe_in(
2291 &cx.entity(),
2292 window,
2293 |editor, _, e: &EditorEvent, window, cx| match e {
2294 EditorEvent::ScrollPositionChanged { local, .. } => {
2295 if *local {
2296 let new_anchor = editor.scroll_manager.anchor();
2297 let snapshot = editor.snapshot(window, cx);
2298 editor.update_restoration_data(cx, move |data| {
2299 data.scroll_position = (
2300 new_anchor.top_row(snapshot.buffer_snapshot()),
2301 new_anchor.offset,
2302 );
2303 });
2304 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2305 editor.inline_blame_popover.take();
2306 }
2307 }
2308 EditorEvent::Edited { .. } => {
2309 if !vim_enabled(cx) {
2310 let (map, selections) = editor.selections.all_adjusted_display(cx);
2311 let pop_state = editor
2312 .change_list
2313 .last()
2314 .map(|previous| {
2315 previous.len() == selections.len()
2316 && previous.iter().enumerate().all(|(ix, p)| {
2317 p.to_display_point(&map).row()
2318 == selections[ix].head().row()
2319 })
2320 })
2321 .unwrap_or(false);
2322 let new_positions = selections
2323 .into_iter()
2324 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2325 .collect();
2326 editor
2327 .change_list
2328 .push_to_change_list(pop_state, new_positions);
2329 }
2330 }
2331 _ => (),
2332 },
2333 ));
2334
2335 if let Some(dap_store) = editor
2336 .project
2337 .as_ref()
2338 .map(|project| project.read(cx).dap_store())
2339 {
2340 let weak_editor = cx.weak_entity();
2341
2342 editor
2343 ._subscriptions
2344 .push(
2345 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2346 let session_entity = cx.entity();
2347 weak_editor
2348 .update(cx, |editor, cx| {
2349 editor._subscriptions.push(
2350 cx.subscribe(&session_entity, Self::on_debug_session_event),
2351 );
2352 })
2353 .ok();
2354 }),
2355 );
2356
2357 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2358 editor
2359 ._subscriptions
2360 .push(cx.subscribe(&session, Self::on_debug_session_event));
2361 }
2362 }
2363
2364 // skip adding the initial selection to selection history
2365 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2366 editor.end_selection(window, cx);
2367 editor.selection_history.mode = SelectionHistoryMode::Normal;
2368
2369 editor.scroll_manager.show_scrollbars(window, cx);
2370 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2371
2372 if full_mode {
2373 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2374 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2375
2376 if editor.git_blame_inline_enabled {
2377 editor.start_git_blame_inline(false, window, cx);
2378 }
2379
2380 editor.go_to_active_debug_line(window, cx);
2381
2382 if let Some(buffer) = buffer.read(cx).as_singleton()
2383 && let Some(project) = editor.project()
2384 {
2385 let handle = project.update(cx, |project, cx| {
2386 project.register_buffer_with_language_servers(&buffer, cx)
2387 });
2388 editor
2389 .registered_buffers
2390 .insert(buffer.read(cx).remote_id(), handle);
2391 }
2392
2393 editor.minimap =
2394 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2395 editor.colors = Some(LspColorData::new(cx));
2396 editor.update_lsp_data(false, None, window, cx);
2397 }
2398
2399 if editor.mode.is_full() {
2400 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2401 }
2402
2403 editor
2404 }
2405
2406 pub fn deploy_mouse_context_menu(
2407 &mut self,
2408 position: gpui::Point<Pixels>,
2409 context_menu: Entity<ContextMenu>,
2410 window: &mut Window,
2411 cx: &mut Context<Self>,
2412 ) {
2413 self.mouse_context_menu = Some(MouseContextMenu::new(
2414 self,
2415 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2416 context_menu,
2417 window,
2418 cx,
2419 ));
2420 }
2421
2422 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2423 self.mouse_context_menu
2424 .as_ref()
2425 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2426 }
2427
2428 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2429 if self
2430 .selections
2431 .pending_anchor()
2432 .is_some_and(|pending_selection| {
2433 let snapshot = self.buffer().read(cx).snapshot(cx);
2434 pending_selection.range().includes(range, &snapshot)
2435 })
2436 {
2437 return true;
2438 }
2439
2440 self.selections
2441 .disjoint_in_range::<usize>(range.clone(), cx)
2442 .into_iter()
2443 .any(|selection| {
2444 // This is needed to cover a corner case, if we just check for an existing
2445 // selection in the fold range, having a cursor at the start of the fold
2446 // marks it as selected. Non-empty selections don't cause this.
2447 let length = selection.end - selection.start;
2448 length > 0
2449 })
2450 }
2451
2452 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2453 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2454 }
2455
2456 fn key_context_internal(
2457 &self,
2458 has_active_edit_prediction: bool,
2459 window: &Window,
2460 cx: &App,
2461 ) -> KeyContext {
2462 let mut key_context = KeyContext::new_with_defaults();
2463 key_context.add("Editor");
2464 let mode = match self.mode {
2465 EditorMode::SingleLine => "single_line",
2466 EditorMode::AutoHeight { .. } => "auto_height",
2467 EditorMode::Minimap { .. } => "minimap",
2468 EditorMode::Full { .. } => "full",
2469 };
2470
2471 if EditorSettings::jupyter_enabled(cx) {
2472 key_context.add("jupyter");
2473 }
2474
2475 key_context.set("mode", mode);
2476 if self.pending_rename.is_some() {
2477 key_context.add("renaming");
2478 }
2479
2480 match self.context_menu.borrow().as_ref() {
2481 Some(CodeContextMenu::Completions(menu)) => {
2482 if menu.visible() {
2483 key_context.add("menu");
2484 key_context.add("showing_completions");
2485 }
2486 }
2487 Some(CodeContextMenu::CodeActions(menu)) => {
2488 if menu.visible() {
2489 key_context.add("menu");
2490 key_context.add("showing_code_actions")
2491 }
2492 }
2493 None => {}
2494 }
2495
2496 if self.signature_help_state.has_multiple_signatures() {
2497 key_context.add("showing_signature_help");
2498 }
2499
2500 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2501 if !self.focus_handle(cx).contains_focused(window, cx)
2502 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2503 {
2504 for addon in self.addons.values() {
2505 addon.extend_key_context(&mut key_context, cx)
2506 }
2507 }
2508
2509 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2510 if let Some(extension) = singleton_buffer
2511 .read(cx)
2512 .file()
2513 .and_then(|file| file.path().extension())
2514 {
2515 key_context.set("extension", extension.to_string());
2516 }
2517 } else {
2518 key_context.add("multibuffer");
2519 }
2520
2521 if has_active_edit_prediction {
2522 if self.edit_prediction_in_conflict() {
2523 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2524 } else {
2525 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2526 key_context.add("copilot_suggestion");
2527 }
2528 }
2529
2530 if self.selection_mark_mode {
2531 key_context.add("selection_mode");
2532 }
2533
2534 key_context
2535 }
2536
2537 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2538 self.last_bounds.as_ref()
2539 }
2540
2541 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2542 if self.mouse_cursor_hidden {
2543 self.mouse_cursor_hidden = false;
2544 cx.notify();
2545 }
2546 }
2547
2548 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2549 let hide_mouse_cursor = match origin {
2550 HideMouseCursorOrigin::TypingAction => {
2551 matches!(
2552 self.hide_mouse_mode,
2553 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2554 )
2555 }
2556 HideMouseCursorOrigin::MovementAction => {
2557 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2558 }
2559 };
2560 if self.mouse_cursor_hidden != hide_mouse_cursor {
2561 self.mouse_cursor_hidden = hide_mouse_cursor;
2562 cx.notify();
2563 }
2564 }
2565
2566 pub fn edit_prediction_in_conflict(&self) -> bool {
2567 if !self.show_edit_predictions_in_menu() {
2568 return false;
2569 }
2570
2571 let showing_completions = self
2572 .context_menu
2573 .borrow()
2574 .as_ref()
2575 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2576
2577 showing_completions
2578 || self.edit_prediction_requires_modifier()
2579 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2580 // bindings to insert tab characters.
2581 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2582 }
2583
2584 pub fn accept_edit_prediction_keybind(
2585 &self,
2586 accept_partial: bool,
2587 window: &Window,
2588 cx: &App,
2589 ) -> AcceptEditPredictionBinding {
2590 let key_context = self.key_context_internal(true, window, cx);
2591 let in_conflict = self.edit_prediction_in_conflict();
2592
2593 let bindings = if accept_partial {
2594 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2595 } else {
2596 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2597 };
2598
2599 // TODO: if the binding contains multiple keystrokes, display all of them, not
2600 // just the first one.
2601 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2602 !in_conflict
2603 || binding
2604 .keystrokes()
2605 .first()
2606 .is_some_and(|keystroke| keystroke.modifiers().modified())
2607 }))
2608 }
2609
2610 pub fn new_file(
2611 workspace: &mut Workspace,
2612 _: &workspace::NewFile,
2613 window: &mut Window,
2614 cx: &mut Context<Workspace>,
2615 ) {
2616 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2617 "Failed to create buffer",
2618 window,
2619 cx,
2620 |e, _, _| match e.error_code() {
2621 ErrorCode::RemoteUpgradeRequired => Some(format!(
2622 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2623 e.error_tag("required").unwrap_or("the latest version")
2624 )),
2625 _ => None,
2626 },
2627 );
2628 }
2629
2630 pub fn new_in_workspace(
2631 workspace: &mut Workspace,
2632 window: &mut Window,
2633 cx: &mut Context<Workspace>,
2634 ) -> Task<Result<Entity<Editor>>> {
2635 let project = workspace.project().clone();
2636 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2637
2638 cx.spawn_in(window, async move |workspace, cx| {
2639 let buffer = create.await?;
2640 workspace.update_in(cx, |workspace, window, cx| {
2641 let editor =
2642 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2643 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2644 editor
2645 })
2646 })
2647 }
2648
2649 fn new_file_vertical(
2650 workspace: &mut Workspace,
2651 _: &workspace::NewFileSplitVertical,
2652 window: &mut Window,
2653 cx: &mut Context<Workspace>,
2654 ) {
2655 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2656 }
2657
2658 fn new_file_horizontal(
2659 workspace: &mut Workspace,
2660 _: &workspace::NewFileSplitHorizontal,
2661 window: &mut Window,
2662 cx: &mut Context<Workspace>,
2663 ) {
2664 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2665 }
2666
2667 fn new_file_in_direction(
2668 workspace: &mut Workspace,
2669 direction: SplitDirection,
2670 window: &mut Window,
2671 cx: &mut Context<Workspace>,
2672 ) {
2673 let project = workspace.project().clone();
2674 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2675
2676 cx.spawn_in(window, async move |workspace, cx| {
2677 let buffer = create.await?;
2678 workspace.update_in(cx, move |workspace, window, cx| {
2679 workspace.split_item(
2680 direction,
2681 Box::new(
2682 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2683 ),
2684 window,
2685 cx,
2686 )
2687 })?;
2688 anyhow::Ok(())
2689 })
2690 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2691 match e.error_code() {
2692 ErrorCode::RemoteUpgradeRequired => Some(format!(
2693 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2694 e.error_tag("required").unwrap_or("the latest version")
2695 )),
2696 _ => None,
2697 }
2698 });
2699 }
2700
2701 pub fn leader_id(&self) -> Option<CollaboratorId> {
2702 self.leader_id
2703 }
2704
2705 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2706 &self.buffer
2707 }
2708
2709 pub fn project(&self) -> Option<&Entity<Project>> {
2710 self.project.as_ref()
2711 }
2712
2713 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2714 self.workspace.as_ref()?.0.upgrade()
2715 }
2716
2717 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2718 self.buffer().read(cx).title(cx)
2719 }
2720
2721 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2722 let git_blame_gutter_max_author_length = self
2723 .render_git_blame_gutter(cx)
2724 .then(|| {
2725 if let Some(blame) = self.blame.as_ref() {
2726 let max_author_length =
2727 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2728 Some(max_author_length)
2729 } else {
2730 None
2731 }
2732 })
2733 .flatten();
2734
2735 EditorSnapshot {
2736 mode: self.mode.clone(),
2737 show_gutter: self.show_gutter,
2738 show_line_numbers: self.show_line_numbers,
2739 show_git_diff_gutter: self.show_git_diff_gutter,
2740 show_code_actions: self.show_code_actions,
2741 show_runnables: self.show_runnables,
2742 show_breakpoints: self.show_breakpoints,
2743 git_blame_gutter_max_author_length,
2744 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2745 placeholder_display_snapshot: self
2746 .placeholder_display_map
2747 .as_ref()
2748 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2749 scroll_anchor: self.scroll_manager.anchor(),
2750 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2751 is_focused: self.focus_handle.is_focused(window),
2752 current_line_highlight: self
2753 .current_line_highlight
2754 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2755 gutter_hovered: self.gutter_hovered,
2756 }
2757 }
2758
2759 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2760 self.buffer.read(cx).language_at(point, cx)
2761 }
2762
2763 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2764 self.buffer.read(cx).read(cx).file_at(point).cloned()
2765 }
2766
2767 pub fn active_excerpt(
2768 &self,
2769 cx: &App,
2770 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2771 self.buffer
2772 .read(cx)
2773 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2774 }
2775
2776 pub fn mode(&self) -> &EditorMode {
2777 &self.mode
2778 }
2779
2780 pub fn set_mode(&mut self, mode: EditorMode) {
2781 self.mode = mode;
2782 }
2783
2784 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2785 self.collaboration_hub.as_deref()
2786 }
2787
2788 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2789 self.collaboration_hub = Some(hub);
2790 }
2791
2792 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2793 self.in_project_search = in_project_search;
2794 }
2795
2796 pub fn set_custom_context_menu(
2797 &mut self,
2798 f: impl 'static
2799 + Fn(
2800 &mut Self,
2801 DisplayPoint,
2802 &mut Window,
2803 &mut Context<Self>,
2804 ) -> Option<Entity<ui::ContextMenu>>,
2805 ) {
2806 self.custom_context_menu = Some(Box::new(f))
2807 }
2808
2809 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2810 self.completion_provider = provider;
2811 }
2812
2813 #[cfg(any(test, feature = "test-support"))]
2814 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2815 self.completion_provider.clone()
2816 }
2817
2818 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2819 self.semantics_provider.clone()
2820 }
2821
2822 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2823 self.semantics_provider = provider;
2824 }
2825
2826 pub fn set_edit_prediction_provider<T>(
2827 &mut self,
2828 provider: Option<Entity<T>>,
2829 window: &mut Window,
2830 cx: &mut Context<Self>,
2831 ) where
2832 T: EditPredictionProvider,
2833 {
2834 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2835 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2836 if this.focus_handle.is_focused(window) {
2837 this.update_visible_edit_prediction(window, cx);
2838 }
2839 }),
2840 provider: Arc::new(provider),
2841 });
2842 self.update_edit_prediction_settings(cx);
2843 self.refresh_edit_prediction(false, false, window, cx);
2844 }
2845
2846 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2847 self.placeholder_display_map
2848 .as_ref()
2849 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2850 }
2851
2852 pub fn set_placeholder_text(
2853 &mut self,
2854 placeholder_text: &str,
2855 window: &mut Window,
2856 cx: &mut Context<Self>,
2857 ) {
2858 let multibuffer = cx
2859 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2860
2861 let style = window.text_style();
2862
2863 self.placeholder_display_map = Some(cx.new(|cx| {
2864 DisplayMap::new(
2865 multibuffer,
2866 style.font(),
2867 style.font_size.to_pixels(window.rem_size()),
2868 None,
2869 FILE_HEADER_HEIGHT,
2870 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2871 Default::default(),
2872 DiagnosticSeverity::Off,
2873 cx,
2874 )
2875 }));
2876 cx.notify();
2877 }
2878
2879 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2880 self.cursor_shape = cursor_shape;
2881
2882 // Disrupt blink for immediate user feedback that the cursor shape has changed
2883 self.blink_manager.update(cx, BlinkManager::show_cursor);
2884
2885 cx.notify();
2886 }
2887
2888 pub fn set_current_line_highlight(
2889 &mut self,
2890 current_line_highlight: Option<CurrentLineHighlight>,
2891 ) {
2892 self.current_line_highlight = current_line_highlight;
2893 }
2894
2895 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2896 self.collapse_matches = collapse_matches;
2897 }
2898
2899 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2900 let buffers = self.buffer.read(cx).all_buffers();
2901 let Some(project) = self.project.as_ref() else {
2902 return;
2903 };
2904 project.update(cx, |project, cx| {
2905 for buffer in buffers {
2906 self.registered_buffers
2907 .entry(buffer.read(cx).remote_id())
2908 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2909 }
2910 })
2911 }
2912
2913 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2914 if self.collapse_matches {
2915 return range.start..range.start;
2916 }
2917 range.clone()
2918 }
2919
2920 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2921 if self.display_map.read(cx).clip_at_line_ends != clip {
2922 self.display_map
2923 .update(cx, |map, _| map.clip_at_line_ends = clip);
2924 }
2925 }
2926
2927 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2928 self.input_enabled = input_enabled;
2929 }
2930
2931 pub fn set_edit_predictions_hidden_for_vim_mode(
2932 &mut self,
2933 hidden: bool,
2934 window: &mut Window,
2935 cx: &mut Context<Self>,
2936 ) {
2937 if hidden != self.edit_predictions_hidden_for_vim_mode {
2938 self.edit_predictions_hidden_for_vim_mode = hidden;
2939 if hidden {
2940 self.update_visible_edit_prediction(window, cx);
2941 } else {
2942 self.refresh_edit_prediction(true, false, window, cx);
2943 }
2944 }
2945 }
2946
2947 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2948 self.menu_edit_predictions_policy = value;
2949 }
2950
2951 pub fn set_autoindent(&mut self, autoindent: bool) {
2952 if autoindent {
2953 self.autoindent_mode = Some(AutoindentMode::EachLine);
2954 } else {
2955 self.autoindent_mode = None;
2956 }
2957 }
2958
2959 pub fn read_only(&self, cx: &App) -> bool {
2960 self.read_only || self.buffer.read(cx).read_only()
2961 }
2962
2963 pub fn set_read_only(&mut self, read_only: bool) {
2964 self.read_only = read_only;
2965 }
2966
2967 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2968 self.use_autoclose = autoclose;
2969 }
2970
2971 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2972 self.use_auto_surround = auto_surround;
2973 }
2974
2975 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2976 self.auto_replace_emoji_shortcode = auto_replace;
2977 }
2978
2979 pub fn toggle_edit_predictions(
2980 &mut self,
2981 _: &ToggleEditPrediction,
2982 window: &mut Window,
2983 cx: &mut Context<Self>,
2984 ) {
2985 if self.show_edit_predictions_override.is_some() {
2986 self.set_show_edit_predictions(None, window, cx);
2987 } else {
2988 let show_edit_predictions = !self.edit_predictions_enabled();
2989 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2990 }
2991 }
2992
2993 pub fn set_show_edit_predictions(
2994 &mut self,
2995 show_edit_predictions: Option<bool>,
2996 window: &mut Window,
2997 cx: &mut Context<Self>,
2998 ) {
2999 self.show_edit_predictions_override = show_edit_predictions;
3000 self.update_edit_prediction_settings(cx);
3001
3002 if let Some(false) = show_edit_predictions {
3003 self.discard_edit_prediction(false, cx);
3004 } else {
3005 self.refresh_edit_prediction(false, true, window, cx);
3006 }
3007 }
3008
3009 fn edit_predictions_disabled_in_scope(
3010 &self,
3011 buffer: &Entity<Buffer>,
3012 buffer_position: language::Anchor,
3013 cx: &App,
3014 ) -> bool {
3015 let snapshot = buffer.read(cx).snapshot();
3016 let settings = snapshot.settings_at(buffer_position, cx);
3017
3018 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3019 return false;
3020 };
3021
3022 scope.override_name().is_some_and(|scope_name| {
3023 settings
3024 .edit_predictions_disabled_in
3025 .iter()
3026 .any(|s| s == scope_name)
3027 })
3028 }
3029
3030 pub fn set_use_modal_editing(&mut self, to: bool) {
3031 self.use_modal_editing = to;
3032 }
3033
3034 pub fn use_modal_editing(&self) -> bool {
3035 self.use_modal_editing
3036 }
3037
3038 fn selections_did_change(
3039 &mut self,
3040 local: bool,
3041 old_cursor_position: &Anchor,
3042 effects: SelectionEffects,
3043 window: &mut Window,
3044 cx: &mut Context<Self>,
3045 ) {
3046 window.invalidate_character_coordinates();
3047
3048 // Copy selections to primary selection buffer
3049 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3050 if local {
3051 let selections = self.selections.all::<usize>(cx);
3052 let buffer_handle = self.buffer.read(cx).read(cx);
3053
3054 let mut text = String::new();
3055 for (index, selection) in selections.iter().enumerate() {
3056 let text_for_selection = buffer_handle
3057 .text_for_range(selection.start..selection.end)
3058 .collect::<String>();
3059
3060 text.push_str(&text_for_selection);
3061 if index != selections.len() - 1 {
3062 text.push('\n');
3063 }
3064 }
3065
3066 if !text.is_empty() {
3067 cx.write_to_primary(ClipboardItem::new_string(text));
3068 }
3069 }
3070
3071 let selection_anchors = self.selections.disjoint_anchors_arc();
3072
3073 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3074 self.buffer.update(cx, |buffer, cx| {
3075 buffer.set_active_selections(
3076 &selection_anchors,
3077 self.selections.line_mode(),
3078 self.cursor_shape,
3079 cx,
3080 )
3081 });
3082 }
3083 let display_map = self
3084 .display_map
3085 .update(cx, |display_map, cx| display_map.snapshot(cx));
3086 let buffer = display_map.buffer_snapshot();
3087 if self.selections.count() == 1 {
3088 self.add_selections_state = None;
3089 }
3090 self.select_next_state = None;
3091 self.select_prev_state = None;
3092 self.select_syntax_node_history.try_clear();
3093 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3094 self.snippet_stack.invalidate(&selection_anchors, buffer);
3095 self.take_rename(false, window, cx);
3096
3097 let newest_selection = self.selections.newest_anchor();
3098 let new_cursor_position = newest_selection.head();
3099 let selection_start = newest_selection.start;
3100
3101 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3102 self.push_to_nav_history(
3103 *old_cursor_position,
3104 Some(new_cursor_position.to_point(buffer)),
3105 false,
3106 effects.nav_history == Some(true),
3107 cx,
3108 );
3109 }
3110
3111 if local {
3112 if let Some(buffer_id) = new_cursor_position.buffer_id
3113 && !self.registered_buffers.contains_key(&buffer_id)
3114 && let Some(project) = self.project.as_ref()
3115 {
3116 project.update(cx, |project, cx| {
3117 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3118 return;
3119 };
3120 self.registered_buffers.insert(
3121 buffer_id,
3122 project.register_buffer_with_language_servers(&buffer, cx),
3123 );
3124 })
3125 }
3126
3127 let mut context_menu = self.context_menu.borrow_mut();
3128 let completion_menu = match context_menu.as_ref() {
3129 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3130 Some(CodeContextMenu::CodeActions(_)) => {
3131 *context_menu = None;
3132 None
3133 }
3134 None => None,
3135 };
3136 let completion_position = completion_menu.map(|menu| menu.initial_position);
3137 drop(context_menu);
3138
3139 if effects.completions
3140 && let Some(completion_position) = completion_position
3141 {
3142 let start_offset = selection_start.to_offset(buffer);
3143 let position_matches = start_offset == completion_position.to_offset(buffer);
3144 let continue_showing = if position_matches {
3145 if self.snippet_stack.is_empty() {
3146 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3147 == Some(CharKind::Word)
3148 } else {
3149 // Snippet choices can be shown even when the cursor is in whitespace.
3150 // Dismissing the menu with actions like backspace is handled by
3151 // invalidation regions.
3152 true
3153 }
3154 } else {
3155 false
3156 };
3157
3158 if continue_showing {
3159 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3160 } else {
3161 self.hide_context_menu(window, cx);
3162 }
3163 }
3164
3165 hide_hover(self, cx);
3166
3167 if old_cursor_position.to_display_point(&display_map).row()
3168 != new_cursor_position.to_display_point(&display_map).row()
3169 {
3170 self.available_code_actions.take();
3171 }
3172 self.refresh_code_actions(window, cx);
3173 self.refresh_document_highlights(cx);
3174 self.refresh_selected_text_highlights(false, window, cx);
3175 refresh_matching_bracket_highlights(self, cx);
3176 self.update_visible_edit_prediction(window, cx);
3177 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3178 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3179 self.inline_blame_popover.take();
3180 if self.git_blame_inline_enabled {
3181 self.start_inline_blame_timer(window, cx);
3182 }
3183 }
3184
3185 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3186 cx.emit(EditorEvent::SelectionsChanged { local });
3187
3188 let selections = &self.selections.disjoint_anchors_arc();
3189 if selections.len() == 1 {
3190 cx.emit(SearchEvent::ActiveMatchChanged)
3191 }
3192 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3193 let inmemory_selections = selections
3194 .iter()
3195 .map(|s| {
3196 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3197 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3198 })
3199 .collect();
3200 self.update_restoration_data(cx, |data| {
3201 data.selections = inmemory_selections;
3202 });
3203
3204 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3205 && let Some(workspace_id) =
3206 self.workspace.as_ref().and_then(|workspace| workspace.1)
3207 {
3208 let snapshot = self.buffer().read(cx).snapshot(cx);
3209 let selections = selections.clone();
3210 let background_executor = cx.background_executor().clone();
3211 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3212 self.serialize_selections = cx.background_spawn(async move {
3213 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3214 let db_selections = selections
3215 .iter()
3216 .map(|selection| {
3217 (
3218 selection.start.to_offset(&snapshot),
3219 selection.end.to_offset(&snapshot),
3220 )
3221 })
3222 .collect();
3223
3224 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3225 .await
3226 .with_context(|| {
3227 format!(
3228 "persisting editor selections for editor {editor_id}, \
3229 workspace {workspace_id:?}"
3230 )
3231 })
3232 .log_err();
3233 });
3234 }
3235 }
3236
3237 cx.notify();
3238 }
3239
3240 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3241 use text::ToOffset as _;
3242 use text::ToPoint as _;
3243
3244 if self.mode.is_minimap()
3245 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3246 {
3247 return;
3248 }
3249
3250 if !self.buffer().read(cx).is_singleton() {
3251 return;
3252 }
3253
3254 let display_snapshot = self
3255 .display_map
3256 .update(cx, |display_map, cx| display_map.snapshot(cx));
3257 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3258 return;
3259 };
3260 let inmemory_folds = display_snapshot
3261 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3262 .map(|fold| {
3263 fold.range.start.text_anchor.to_point(&snapshot)
3264 ..fold.range.end.text_anchor.to_point(&snapshot)
3265 })
3266 .collect();
3267 self.update_restoration_data(cx, |data| {
3268 data.folds = inmemory_folds;
3269 });
3270
3271 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3272 return;
3273 };
3274 let background_executor = cx.background_executor().clone();
3275 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3276 let db_folds = display_snapshot
3277 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3278 .map(|fold| {
3279 (
3280 fold.range.start.text_anchor.to_offset(&snapshot),
3281 fold.range.end.text_anchor.to_offset(&snapshot),
3282 )
3283 })
3284 .collect();
3285 self.serialize_folds = cx.background_spawn(async move {
3286 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3287 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3288 .await
3289 .with_context(|| {
3290 format!(
3291 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3292 )
3293 })
3294 .log_err();
3295 });
3296 }
3297
3298 pub fn sync_selections(
3299 &mut self,
3300 other: Entity<Editor>,
3301 cx: &mut Context<Self>,
3302 ) -> gpui::Subscription {
3303 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3304 if !other_selections.is_empty() {
3305 self.selections.change_with(cx, |selections| {
3306 selections.select_anchors(other_selections);
3307 });
3308 }
3309
3310 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3311 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3312 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3313 if other_selections.is_empty() {
3314 return;
3315 }
3316 this.selections.change_with(cx, |selections| {
3317 selections.select_anchors(other_selections);
3318 });
3319 }
3320 });
3321
3322 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3323 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3324 let these_selections = this.selections.disjoint_anchors().to_vec();
3325 if these_selections.is_empty() {
3326 return;
3327 }
3328 other.update(cx, |other_editor, cx| {
3329 other_editor.selections.change_with(cx, |selections| {
3330 selections.select_anchors(these_selections);
3331 })
3332 });
3333 }
3334 });
3335
3336 Subscription::join(other_subscription, this_subscription)
3337 }
3338
3339 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3340 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3341 /// effects of selection change occur at the end of the transaction.
3342 pub fn change_selections<R>(
3343 &mut self,
3344 effects: SelectionEffects,
3345 window: &mut Window,
3346 cx: &mut Context<Self>,
3347 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3348 ) -> R {
3349 if let Some(state) = &mut self.deferred_selection_effects_state {
3350 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3351 state.effects.completions = effects.completions;
3352 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3353 let (changed, result) = self.selections.change_with(cx, change);
3354 state.changed |= changed;
3355 return result;
3356 }
3357 let mut state = DeferredSelectionEffectsState {
3358 changed: false,
3359 effects,
3360 old_cursor_position: self.selections.newest_anchor().head(),
3361 history_entry: SelectionHistoryEntry {
3362 selections: self.selections.disjoint_anchors_arc(),
3363 select_next_state: self.select_next_state.clone(),
3364 select_prev_state: self.select_prev_state.clone(),
3365 add_selections_state: self.add_selections_state.clone(),
3366 },
3367 };
3368 let (changed, result) = self.selections.change_with(cx, change);
3369 state.changed = state.changed || changed;
3370 if self.defer_selection_effects {
3371 self.deferred_selection_effects_state = Some(state);
3372 } else {
3373 self.apply_selection_effects(state, window, cx);
3374 }
3375 result
3376 }
3377
3378 /// Defers the effects of selection change, so that the effects of multiple calls to
3379 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3380 /// to selection history and the state of popovers based on selection position aren't
3381 /// erroneously updated.
3382 pub fn with_selection_effects_deferred<R>(
3383 &mut self,
3384 window: &mut Window,
3385 cx: &mut Context<Self>,
3386 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3387 ) -> R {
3388 let already_deferred = self.defer_selection_effects;
3389 self.defer_selection_effects = true;
3390 let result = update(self, window, cx);
3391 if !already_deferred {
3392 self.defer_selection_effects = false;
3393 if let Some(state) = self.deferred_selection_effects_state.take() {
3394 self.apply_selection_effects(state, window, cx);
3395 }
3396 }
3397 result
3398 }
3399
3400 fn apply_selection_effects(
3401 &mut self,
3402 state: DeferredSelectionEffectsState,
3403 window: &mut Window,
3404 cx: &mut Context<Self>,
3405 ) {
3406 if state.changed {
3407 self.selection_history.push(state.history_entry);
3408
3409 if let Some(autoscroll) = state.effects.scroll {
3410 self.request_autoscroll(autoscroll, cx);
3411 }
3412
3413 let old_cursor_position = &state.old_cursor_position;
3414
3415 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3416
3417 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3418 self.show_signature_help(&ShowSignatureHelp, window, cx);
3419 }
3420 }
3421 }
3422
3423 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3424 where
3425 I: IntoIterator<Item = (Range<S>, T)>,
3426 S: ToOffset,
3427 T: Into<Arc<str>>,
3428 {
3429 if self.read_only(cx) {
3430 return;
3431 }
3432
3433 self.buffer
3434 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3435 }
3436
3437 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3438 where
3439 I: IntoIterator<Item = (Range<S>, T)>,
3440 S: ToOffset,
3441 T: Into<Arc<str>>,
3442 {
3443 if self.read_only(cx) {
3444 return;
3445 }
3446
3447 self.buffer.update(cx, |buffer, cx| {
3448 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3449 });
3450 }
3451
3452 pub fn edit_with_block_indent<I, S, T>(
3453 &mut self,
3454 edits: I,
3455 original_indent_columns: Vec<Option<u32>>,
3456 cx: &mut Context<Self>,
3457 ) where
3458 I: IntoIterator<Item = (Range<S>, T)>,
3459 S: ToOffset,
3460 T: Into<Arc<str>>,
3461 {
3462 if self.read_only(cx) {
3463 return;
3464 }
3465
3466 self.buffer.update(cx, |buffer, cx| {
3467 buffer.edit(
3468 edits,
3469 Some(AutoindentMode::Block {
3470 original_indent_columns,
3471 }),
3472 cx,
3473 )
3474 });
3475 }
3476
3477 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3478 self.hide_context_menu(window, cx);
3479
3480 match phase {
3481 SelectPhase::Begin {
3482 position,
3483 add,
3484 click_count,
3485 } => self.begin_selection(position, add, click_count, window, cx),
3486 SelectPhase::BeginColumnar {
3487 position,
3488 goal_column,
3489 reset,
3490 mode,
3491 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3492 SelectPhase::Extend {
3493 position,
3494 click_count,
3495 } => self.extend_selection(position, click_count, window, cx),
3496 SelectPhase::Update {
3497 position,
3498 goal_column,
3499 scroll_delta,
3500 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3501 SelectPhase::End => self.end_selection(window, cx),
3502 }
3503 }
3504
3505 fn extend_selection(
3506 &mut self,
3507 position: DisplayPoint,
3508 click_count: usize,
3509 window: &mut Window,
3510 cx: &mut Context<Self>,
3511 ) {
3512 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3513 let tail = self.selections.newest::<usize>(cx).tail();
3514 self.begin_selection(position, false, click_count, window, cx);
3515
3516 let position = position.to_offset(&display_map, Bias::Left);
3517 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3518
3519 let mut pending_selection = self
3520 .selections
3521 .pending_anchor()
3522 .cloned()
3523 .expect("extend_selection not called with pending selection");
3524 if position >= tail {
3525 pending_selection.start = tail_anchor;
3526 } else {
3527 pending_selection.end = tail_anchor;
3528 pending_selection.reversed = true;
3529 }
3530
3531 let mut pending_mode = self.selections.pending_mode().unwrap();
3532 match &mut pending_mode {
3533 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3534 _ => {}
3535 }
3536
3537 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3538 SelectionEffects::scroll(Autoscroll::fit())
3539 } else {
3540 SelectionEffects::no_scroll()
3541 };
3542
3543 self.change_selections(effects, window, cx, |s| {
3544 s.set_pending(pending_selection.clone(), pending_mode)
3545 });
3546 }
3547
3548 fn begin_selection(
3549 &mut self,
3550 position: DisplayPoint,
3551 add: bool,
3552 click_count: usize,
3553 window: &mut Window,
3554 cx: &mut Context<Self>,
3555 ) {
3556 if !self.focus_handle.is_focused(window) {
3557 self.last_focused_descendant = None;
3558 window.focus(&self.focus_handle);
3559 }
3560
3561 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3562 let buffer = display_map.buffer_snapshot();
3563 let position = display_map.clip_point(position, Bias::Left);
3564
3565 let start;
3566 let end;
3567 let mode;
3568 let mut auto_scroll;
3569 match click_count {
3570 1 => {
3571 start = buffer.anchor_before(position.to_point(&display_map));
3572 end = start;
3573 mode = SelectMode::Character;
3574 auto_scroll = true;
3575 }
3576 2 => {
3577 let position = display_map
3578 .clip_point(position, Bias::Left)
3579 .to_offset(&display_map, Bias::Left);
3580 let (range, _) = buffer.surrounding_word(position, None);
3581 start = buffer.anchor_before(range.start);
3582 end = buffer.anchor_before(range.end);
3583 mode = SelectMode::Word(start..end);
3584 auto_scroll = true;
3585 }
3586 3 => {
3587 let position = display_map
3588 .clip_point(position, Bias::Left)
3589 .to_point(&display_map);
3590 let line_start = display_map.prev_line_boundary(position).0;
3591 let next_line_start = buffer.clip_point(
3592 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3593 Bias::Left,
3594 );
3595 start = buffer.anchor_before(line_start);
3596 end = buffer.anchor_before(next_line_start);
3597 mode = SelectMode::Line(start..end);
3598 auto_scroll = true;
3599 }
3600 _ => {
3601 start = buffer.anchor_before(0);
3602 end = buffer.anchor_before(buffer.len());
3603 mode = SelectMode::All;
3604 auto_scroll = false;
3605 }
3606 }
3607 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3608
3609 let point_to_delete: Option<usize> = {
3610 let selected_points: Vec<Selection<Point>> =
3611 self.selections.disjoint_in_range(start..end, cx);
3612
3613 if !add || click_count > 1 {
3614 None
3615 } else if !selected_points.is_empty() {
3616 Some(selected_points[0].id)
3617 } else {
3618 let clicked_point_already_selected =
3619 self.selections.disjoint_anchors().iter().find(|selection| {
3620 selection.start.to_point(buffer) == start.to_point(buffer)
3621 || selection.end.to_point(buffer) == end.to_point(buffer)
3622 });
3623
3624 clicked_point_already_selected.map(|selection| selection.id)
3625 }
3626 };
3627
3628 let selections_count = self.selections.count();
3629 let effects = if auto_scroll {
3630 SelectionEffects::default()
3631 } else {
3632 SelectionEffects::no_scroll()
3633 };
3634
3635 self.change_selections(effects, window, cx, |s| {
3636 if let Some(point_to_delete) = point_to_delete {
3637 s.delete(point_to_delete);
3638
3639 if selections_count == 1 {
3640 s.set_pending_anchor_range(start..end, mode);
3641 }
3642 } else {
3643 if !add {
3644 s.clear_disjoint();
3645 }
3646
3647 s.set_pending_anchor_range(start..end, mode);
3648 }
3649 });
3650 }
3651
3652 fn begin_columnar_selection(
3653 &mut self,
3654 position: DisplayPoint,
3655 goal_column: u32,
3656 reset: bool,
3657 mode: ColumnarMode,
3658 window: &mut Window,
3659 cx: &mut Context<Self>,
3660 ) {
3661 if !self.focus_handle.is_focused(window) {
3662 self.last_focused_descendant = None;
3663 window.focus(&self.focus_handle);
3664 }
3665
3666 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3667
3668 if reset {
3669 let pointer_position = display_map
3670 .buffer_snapshot()
3671 .anchor_before(position.to_point(&display_map));
3672
3673 self.change_selections(
3674 SelectionEffects::scroll(Autoscroll::newest()),
3675 window,
3676 cx,
3677 |s| {
3678 s.clear_disjoint();
3679 s.set_pending_anchor_range(
3680 pointer_position..pointer_position,
3681 SelectMode::Character,
3682 );
3683 },
3684 );
3685 };
3686
3687 let tail = self.selections.newest::<Point>(cx).tail();
3688 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3689 self.columnar_selection_state = match mode {
3690 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3691 selection_tail: selection_anchor,
3692 display_point: if reset {
3693 if position.column() != goal_column {
3694 Some(DisplayPoint::new(position.row(), goal_column))
3695 } else {
3696 None
3697 }
3698 } else {
3699 None
3700 },
3701 }),
3702 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3703 selection_tail: selection_anchor,
3704 }),
3705 };
3706
3707 if !reset {
3708 self.select_columns(position, goal_column, &display_map, window, cx);
3709 }
3710 }
3711
3712 fn update_selection(
3713 &mut self,
3714 position: DisplayPoint,
3715 goal_column: u32,
3716 scroll_delta: gpui::Point<f32>,
3717 window: &mut Window,
3718 cx: &mut Context<Self>,
3719 ) {
3720 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3721
3722 if self.columnar_selection_state.is_some() {
3723 self.select_columns(position, goal_column, &display_map, window, cx);
3724 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3725 let buffer = display_map.buffer_snapshot();
3726 let head;
3727 let tail;
3728 let mode = self.selections.pending_mode().unwrap();
3729 match &mode {
3730 SelectMode::Character => {
3731 head = position.to_point(&display_map);
3732 tail = pending.tail().to_point(buffer);
3733 }
3734 SelectMode::Word(original_range) => {
3735 let offset = display_map
3736 .clip_point(position, Bias::Left)
3737 .to_offset(&display_map, Bias::Left);
3738 let original_range = original_range.to_offset(buffer);
3739
3740 let head_offset = if buffer.is_inside_word(offset, None)
3741 || original_range.contains(&offset)
3742 {
3743 let (word_range, _) = buffer.surrounding_word(offset, None);
3744 if word_range.start < original_range.start {
3745 word_range.start
3746 } else {
3747 word_range.end
3748 }
3749 } else {
3750 offset
3751 };
3752
3753 head = head_offset.to_point(buffer);
3754 if head_offset <= original_range.start {
3755 tail = original_range.end.to_point(buffer);
3756 } else {
3757 tail = original_range.start.to_point(buffer);
3758 }
3759 }
3760 SelectMode::Line(original_range) => {
3761 let original_range = original_range.to_point(display_map.buffer_snapshot());
3762
3763 let position = display_map
3764 .clip_point(position, Bias::Left)
3765 .to_point(&display_map);
3766 let line_start = display_map.prev_line_boundary(position).0;
3767 let next_line_start = buffer.clip_point(
3768 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3769 Bias::Left,
3770 );
3771
3772 if line_start < original_range.start {
3773 head = line_start
3774 } else {
3775 head = next_line_start
3776 }
3777
3778 if head <= original_range.start {
3779 tail = original_range.end;
3780 } else {
3781 tail = original_range.start;
3782 }
3783 }
3784 SelectMode::All => {
3785 return;
3786 }
3787 };
3788
3789 if head < tail {
3790 pending.start = buffer.anchor_before(head);
3791 pending.end = buffer.anchor_before(tail);
3792 pending.reversed = true;
3793 } else {
3794 pending.start = buffer.anchor_before(tail);
3795 pending.end = buffer.anchor_before(head);
3796 pending.reversed = false;
3797 }
3798
3799 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3800 s.set_pending(pending.clone(), mode);
3801 });
3802 } else {
3803 log::error!("update_selection dispatched with no pending selection");
3804 return;
3805 }
3806
3807 self.apply_scroll_delta(scroll_delta, window, cx);
3808 cx.notify();
3809 }
3810
3811 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3812 self.columnar_selection_state.take();
3813 if self.selections.pending_anchor().is_some() {
3814 let selections = self.selections.all::<usize>(cx);
3815 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3816 s.select(selections);
3817 s.clear_pending();
3818 });
3819 }
3820 }
3821
3822 fn select_columns(
3823 &mut self,
3824 head: DisplayPoint,
3825 goal_column: u32,
3826 display_map: &DisplaySnapshot,
3827 window: &mut Window,
3828 cx: &mut Context<Self>,
3829 ) {
3830 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3831 return;
3832 };
3833
3834 let tail = match columnar_state {
3835 ColumnarSelectionState::FromMouse {
3836 selection_tail,
3837 display_point,
3838 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3839 ColumnarSelectionState::FromSelection { selection_tail } => {
3840 selection_tail.to_display_point(display_map)
3841 }
3842 };
3843
3844 let start_row = cmp::min(tail.row(), head.row());
3845 let end_row = cmp::max(tail.row(), head.row());
3846 let start_column = cmp::min(tail.column(), goal_column);
3847 let end_column = cmp::max(tail.column(), goal_column);
3848 let reversed = start_column < tail.column();
3849
3850 let selection_ranges = (start_row.0..=end_row.0)
3851 .map(DisplayRow)
3852 .filter_map(|row| {
3853 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3854 || start_column <= display_map.line_len(row))
3855 && !display_map.is_block_line(row)
3856 {
3857 let start = display_map
3858 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3859 .to_point(display_map);
3860 let end = display_map
3861 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3862 .to_point(display_map);
3863 if reversed {
3864 Some(end..start)
3865 } else {
3866 Some(start..end)
3867 }
3868 } else {
3869 None
3870 }
3871 })
3872 .collect::<Vec<_>>();
3873
3874 let ranges = match columnar_state {
3875 ColumnarSelectionState::FromMouse { .. } => {
3876 let mut non_empty_ranges = selection_ranges
3877 .iter()
3878 .filter(|selection_range| selection_range.start != selection_range.end)
3879 .peekable();
3880 if non_empty_ranges.peek().is_some() {
3881 non_empty_ranges.cloned().collect()
3882 } else {
3883 selection_ranges
3884 }
3885 }
3886 _ => selection_ranges,
3887 };
3888
3889 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3890 s.select_ranges(ranges);
3891 });
3892 cx.notify();
3893 }
3894
3895 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3896 self.selections
3897 .all_adjusted(cx)
3898 .iter()
3899 .any(|selection| !selection.is_empty())
3900 }
3901
3902 pub fn has_pending_nonempty_selection(&self) -> bool {
3903 let pending_nonempty_selection = match self.selections.pending_anchor() {
3904 Some(Selection { start, end, .. }) => start != end,
3905 None => false,
3906 };
3907
3908 pending_nonempty_selection
3909 || (self.columnar_selection_state.is_some()
3910 && self.selections.disjoint_anchors().len() > 1)
3911 }
3912
3913 pub fn has_pending_selection(&self) -> bool {
3914 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3915 }
3916
3917 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3918 self.selection_mark_mode = false;
3919 self.selection_drag_state = SelectionDragState::None;
3920
3921 if self.clear_expanded_diff_hunks(cx) {
3922 cx.notify();
3923 return;
3924 }
3925 if self.dismiss_menus_and_popups(true, window, cx) {
3926 return;
3927 }
3928
3929 if self.mode.is_full()
3930 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3931 {
3932 return;
3933 }
3934
3935 cx.propagate();
3936 }
3937
3938 pub fn dismiss_menus_and_popups(
3939 &mut self,
3940 is_user_requested: bool,
3941 window: &mut Window,
3942 cx: &mut Context<Self>,
3943 ) -> bool {
3944 if self.take_rename(false, window, cx).is_some() {
3945 return true;
3946 }
3947
3948 if hide_hover(self, cx) {
3949 return true;
3950 }
3951
3952 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3953 return true;
3954 }
3955
3956 if self.hide_context_menu(window, cx).is_some() {
3957 return true;
3958 }
3959
3960 if self.mouse_context_menu.take().is_some() {
3961 return true;
3962 }
3963
3964 if is_user_requested && self.discard_edit_prediction(true, cx) {
3965 return true;
3966 }
3967
3968 if self.snippet_stack.pop().is_some() {
3969 return true;
3970 }
3971
3972 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3973 self.dismiss_diagnostics(cx);
3974 return true;
3975 }
3976
3977 false
3978 }
3979
3980 fn linked_editing_ranges_for(
3981 &self,
3982 selection: Range<text::Anchor>,
3983 cx: &App,
3984 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3985 if self.linked_edit_ranges.is_empty() {
3986 return None;
3987 }
3988 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3989 selection.end.buffer_id.and_then(|end_buffer_id| {
3990 if selection.start.buffer_id != Some(end_buffer_id) {
3991 return None;
3992 }
3993 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3994 let snapshot = buffer.read(cx).snapshot();
3995 self.linked_edit_ranges
3996 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3997 .map(|ranges| (ranges, snapshot, buffer))
3998 })?;
3999 use text::ToOffset as TO;
4000 // find offset from the start of current range to current cursor position
4001 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4002
4003 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4004 let start_difference = start_offset - start_byte_offset;
4005 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4006 let end_difference = end_offset - start_byte_offset;
4007 // Current range has associated linked ranges.
4008 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4009 for range in linked_ranges.iter() {
4010 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4011 let end_offset = start_offset + end_difference;
4012 let start_offset = start_offset + start_difference;
4013 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4014 continue;
4015 }
4016 if self.selections.disjoint_anchor_ranges().any(|s| {
4017 if s.start.buffer_id != selection.start.buffer_id
4018 || s.end.buffer_id != selection.end.buffer_id
4019 {
4020 return false;
4021 }
4022 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4023 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4024 }) {
4025 continue;
4026 }
4027 let start = buffer_snapshot.anchor_after(start_offset);
4028 let end = buffer_snapshot.anchor_after(end_offset);
4029 linked_edits
4030 .entry(buffer.clone())
4031 .or_default()
4032 .push(start..end);
4033 }
4034 Some(linked_edits)
4035 }
4036
4037 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4038 let text: Arc<str> = text.into();
4039
4040 if self.read_only(cx) {
4041 return;
4042 }
4043
4044 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4045
4046 let selections = self.selections.all_adjusted(cx);
4047 let mut bracket_inserted = false;
4048 let mut edits = Vec::new();
4049 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4050 let mut new_selections = Vec::with_capacity(selections.len());
4051 let mut new_autoclose_regions = Vec::new();
4052 let snapshot = self.buffer.read(cx).read(cx);
4053 let mut clear_linked_edit_ranges = false;
4054
4055 for (selection, autoclose_region) in
4056 self.selections_with_autoclose_regions(selections, &snapshot)
4057 {
4058 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4059 // Determine if the inserted text matches the opening or closing
4060 // bracket of any of this language's bracket pairs.
4061 let mut bracket_pair = None;
4062 let mut is_bracket_pair_start = false;
4063 let mut is_bracket_pair_end = false;
4064 if !text.is_empty() {
4065 let mut bracket_pair_matching_end = None;
4066 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4067 // and they are removing the character that triggered IME popup.
4068 for (pair, enabled) in scope.brackets() {
4069 if !pair.close && !pair.surround {
4070 continue;
4071 }
4072
4073 if enabled && pair.start.ends_with(text.as_ref()) {
4074 let prefix_len = pair.start.len() - text.len();
4075 let preceding_text_matches_prefix = prefix_len == 0
4076 || (selection.start.column >= (prefix_len as u32)
4077 && snapshot.contains_str_at(
4078 Point::new(
4079 selection.start.row,
4080 selection.start.column - (prefix_len as u32),
4081 ),
4082 &pair.start[..prefix_len],
4083 ));
4084 if preceding_text_matches_prefix {
4085 bracket_pair = Some(pair.clone());
4086 is_bracket_pair_start = true;
4087 break;
4088 }
4089 }
4090 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4091 {
4092 // take first bracket pair matching end, but don't break in case a later bracket
4093 // pair matches start
4094 bracket_pair_matching_end = Some(pair.clone());
4095 }
4096 }
4097 if let Some(end) = bracket_pair_matching_end
4098 && bracket_pair.is_none()
4099 {
4100 bracket_pair = Some(end);
4101 is_bracket_pair_end = true;
4102 }
4103 }
4104
4105 if let Some(bracket_pair) = bracket_pair {
4106 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4107 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4108 let auto_surround =
4109 self.use_auto_surround && snapshot_settings.use_auto_surround;
4110 if selection.is_empty() {
4111 if is_bracket_pair_start {
4112 // If the inserted text is a suffix of an opening bracket and the
4113 // selection is preceded by the rest of the opening bracket, then
4114 // insert the closing bracket.
4115 let following_text_allows_autoclose = snapshot
4116 .chars_at(selection.start)
4117 .next()
4118 .is_none_or(|c| scope.should_autoclose_before(c));
4119
4120 let preceding_text_allows_autoclose = selection.start.column == 0
4121 || snapshot
4122 .reversed_chars_at(selection.start)
4123 .next()
4124 .is_none_or(|c| {
4125 bracket_pair.start != bracket_pair.end
4126 || !snapshot
4127 .char_classifier_at(selection.start)
4128 .is_word(c)
4129 });
4130
4131 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4132 && bracket_pair.start.len() == 1
4133 {
4134 let target = bracket_pair.start.chars().next().unwrap();
4135 let current_line_count = snapshot
4136 .reversed_chars_at(selection.start)
4137 .take_while(|&c| c != '\n')
4138 .filter(|&c| c == target)
4139 .count();
4140 current_line_count % 2 == 1
4141 } else {
4142 false
4143 };
4144
4145 if autoclose
4146 && bracket_pair.close
4147 && following_text_allows_autoclose
4148 && preceding_text_allows_autoclose
4149 && !is_closing_quote
4150 {
4151 let anchor = snapshot.anchor_before(selection.end);
4152 new_selections.push((selection.map(|_| anchor), text.len()));
4153 new_autoclose_regions.push((
4154 anchor,
4155 text.len(),
4156 selection.id,
4157 bracket_pair.clone(),
4158 ));
4159 edits.push((
4160 selection.range(),
4161 format!("{}{}", text, bracket_pair.end).into(),
4162 ));
4163 bracket_inserted = true;
4164 continue;
4165 }
4166 }
4167
4168 if let Some(region) = autoclose_region {
4169 // If the selection is followed by an auto-inserted closing bracket,
4170 // then don't insert that closing bracket again; just move the selection
4171 // past the closing bracket.
4172 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4173 && text.as_ref() == region.pair.end.as_str()
4174 && snapshot.contains_str_at(region.range.end, text.as_ref());
4175 if should_skip {
4176 let anchor = snapshot.anchor_after(selection.end);
4177 new_selections
4178 .push((selection.map(|_| anchor), region.pair.end.len()));
4179 continue;
4180 }
4181 }
4182
4183 let always_treat_brackets_as_autoclosed = snapshot
4184 .language_settings_at(selection.start, cx)
4185 .always_treat_brackets_as_autoclosed;
4186 if always_treat_brackets_as_autoclosed
4187 && is_bracket_pair_end
4188 && snapshot.contains_str_at(selection.end, text.as_ref())
4189 {
4190 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4191 // and the inserted text is a closing bracket and the selection is followed
4192 // by the closing bracket then move the selection past the closing bracket.
4193 let anchor = snapshot.anchor_after(selection.end);
4194 new_selections.push((selection.map(|_| anchor), text.len()));
4195 continue;
4196 }
4197 }
4198 // If an opening bracket is 1 character long and is typed while
4199 // text is selected, then surround that text with the bracket pair.
4200 else if auto_surround
4201 && bracket_pair.surround
4202 && is_bracket_pair_start
4203 && bracket_pair.start.chars().count() == 1
4204 {
4205 edits.push((selection.start..selection.start, text.clone()));
4206 edits.push((
4207 selection.end..selection.end,
4208 bracket_pair.end.as_str().into(),
4209 ));
4210 bracket_inserted = true;
4211 new_selections.push((
4212 Selection {
4213 id: selection.id,
4214 start: snapshot.anchor_after(selection.start),
4215 end: snapshot.anchor_before(selection.end),
4216 reversed: selection.reversed,
4217 goal: selection.goal,
4218 },
4219 0,
4220 ));
4221 continue;
4222 }
4223 }
4224 }
4225
4226 if self.auto_replace_emoji_shortcode
4227 && selection.is_empty()
4228 && text.as_ref().ends_with(':')
4229 && let Some(possible_emoji_short_code) =
4230 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4231 && !possible_emoji_short_code.is_empty()
4232 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4233 {
4234 let emoji_shortcode_start = Point::new(
4235 selection.start.row,
4236 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4237 );
4238
4239 // Remove shortcode from buffer
4240 edits.push((
4241 emoji_shortcode_start..selection.start,
4242 "".to_string().into(),
4243 ));
4244 new_selections.push((
4245 Selection {
4246 id: selection.id,
4247 start: snapshot.anchor_after(emoji_shortcode_start),
4248 end: snapshot.anchor_before(selection.start),
4249 reversed: selection.reversed,
4250 goal: selection.goal,
4251 },
4252 0,
4253 ));
4254
4255 // Insert emoji
4256 let selection_start_anchor = snapshot.anchor_after(selection.start);
4257 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4258 edits.push((selection.start..selection.end, emoji.to_string().into()));
4259
4260 continue;
4261 }
4262
4263 // If not handling any auto-close operation, then just replace the selected
4264 // text with the given input and move the selection to the end of the
4265 // newly inserted text.
4266 let anchor = snapshot.anchor_after(selection.end);
4267 if !self.linked_edit_ranges.is_empty() {
4268 let start_anchor = snapshot.anchor_before(selection.start);
4269
4270 let is_word_char = text.chars().next().is_none_or(|char| {
4271 let classifier = snapshot
4272 .char_classifier_at(start_anchor.to_offset(&snapshot))
4273 .scope_context(Some(CharScopeContext::LinkedEdit));
4274 classifier.is_word(char)
4275 });
4276
4277 if is_word_char {
4278 if let Some(ranges) = self
4279 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4280 {
4281 for (buffer, edits) in ranges {
4282 linked_edits
4283 .entry(buffer.clone())
4284 .or_default()
4285 .extend(edits.into_iter().map(|range| (range, text.clone())));
4286 }
4287 }
4288 } else {
4289 clear_linked_edit_ranges = true;
4290 }
4291 }
4292
4293 new_selections.push((selection.map(|_| anchor), 0));
4294 edits.push((selection.start..selection.end, text.clone()));
4295 }
4296
4297 drop(snapshot);
4298
4299 self.transact(window, cx, |this, window, cx| {
4300 if clear_linked_edit_ranges {
4301 this.linked_edit_ranges.clear();
4302 }
4303 let initial_buffer_versions =
4304 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4305
4306 this.buffer.update(cx, |buffer, cx| {
4307 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4308 });
4309 for (buffer, edits) in linked_edits {
4310 buffer.update(cx, |buffer, cx| {
4311 let snapshot = buffer.snapshot();
4312 let edits = edits
4313 .into_iter()
4314 .map(|(range, text)| {
4315 use text::ToPoint as TP;
4316 let end_point = TP::to_point(&range.end, &snapshot);
4317 let start_point = TP::to_point(&range.start, &snapshot);
4318 (start_point..end_point, text)
4319 })
4320 .sorted_by_key(|(range, _)| range.start);
4321 buffer.edit(edits, None, cx);
4322 })
4323 }
4324 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4325 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4326 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4327 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4328 .zip(new_selection_deltas)
4329 .map(|(selection, delta)| Selection {
4330 id: selection.id,
4331 start: selection.start + delta,
4332 end: selection.end + delta,
4333 reversed: selection.reversed,
4334 goal: SelectionGoal::None,
4335 })
4336 .collect::<Vec<_>>();
4337
4338 let mut i = 0;
4339 for (position, delta, selection_id, pair) in new_autoclose_regions {
4340 let position = position.to_offset(map.buffer_snapshot()) + delta;
4341 let start = map.buffer_snapshot().anchor_before(position);
4342 let end = map.buffer_snapshot().anchor_after(position);
4343 while let Some(existing_state) = this.autoclose_regions.get(i) {
4344 match existing_state
4345 .range
4346 .start
4347 .cmp(&start, map.buffer_snapshot())
4348 {
4349 Ordering::Less => i += 1,
4350 Ordering::Greater => break,
4351 Ordering::Equal => {
4352 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4353 Ordering::Less => i += 1,
4354 Ordering::Equal => break,
4355 Ordering::Greater => break,
4356 }
4357 }
4358 }
4359 }
4360 this.autoclose_regions.insert(
4361 i,
4362 AutocloseRegion {
4363 selection_id,
4364 range: start..end,
4365 pair,
4366 },
4367 );
4368 }
4369
4370 let had_active_edit_prediction = this.has_active_edit_prediction();
4371 this.change_selections(
4372 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4373 window,
4374 cx,
4375 |s| s.select(new_selections),
4376 );
4377
4378 if !bracket_inserted
4379 && let Some(on_type_format_task) =
4380 this.trigger_on_type_formatting(text.to_string(), window, cx)
4381 {
4382 on_type_format_task.detach_and_log_err(cx);
4383 }
4384
4385 let editor_settings = EditorSettings::get_global(cx);
4386 if bracket_inserted
4387 && (editor_settings.auto_signature_help
4388 || editor_settings.show_signature_help_after_edits)
4389 {
4390 this.show_signature_help(&ShowSignatureHelp, window, cx);
4391 }
4392
4393 let trigger_in_words =
4394 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4395 if this.hard_wrap.is_some() {
4396 let latest: Range<Point> = this.selections.newest(cx).range();
4397 if latest.is_empty()
4398 && this
4399 .buffer()
4400 .read(cx)
4401 .snapshot(cx)
4402 .line_len(MultiBufferRow(latest.start.row))
4403 == latest.start.column
4404 {
4405 this.rewrap_impl(
4406 RewrapOptions {
4407 override_language_settings: true,
4408 preserve_existing_whitespace: true,
4409 },
4410 cx,
4411 )
4412 }
4413 }
4414 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4415 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4416 this.refresh_edit_prediction(true, false, window, cx);
4417 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4418 });
4419 }
4420
4421 fn find_possible_emoji_shortcode_at_position(
4422 snapshot: &MultiBufferSnapshot,
4423 position: Point,
4424 ) -> Option<String> {
4425 let mut chars = Vec::new();
4426 let mut found_colon = false;
4427 for char in snapshot.reversed_chars_at(position).take(100) {
4428 // Found a possible emoji shortcode in the middle of the buffer
4429 if found_colon {
4430 if char.is_whitespace() {
4431 chars.reverse();
4432 return Some(chars.iter().collect());
4433 }
4434 // If the previous character is not a whitespace, we are in the middle of a word
4435 // and we only want to complete the shortcode if the word is made up of other emojis
4436 let mut containing_word = String::new();
4437 for ch in snapshot
4438 .reversed_chars_at(position)
4439 .skip(chars.len() + 1)
4440 .take(100)
4441 {
4442 if ch.is_whitespace() {
4443 break;
4444 }
4445 containing_word.push(ch);
4446 }
4447 let containing_word = containing_word.chars().rev().collect::<String>();
4448 if util::word_consists_of_emojis(containing_word.as_str()) {
4449 chars.reverse();
4450 return Some(chars.iter().collect());
4451 }
4452 }
4453
4454 if char.is_whitespace() || !char.is_ascii() {
4455 return None;
4456 }
4457 if char == ':' {
4458 found_colon = true;
4459 } else {
4460 chars.push(char);
4461 }
4462 }
4463 // Found a possible emoji shortcode at the beginning of the buffer
4464 chars.reverse();
4465 Some(chars.iter().collect())
4466 }
4467
4468 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4469 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4470 self.transact(window, cx, |this, window, cx| {
4471 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4472 let selections = this.selections.all::<usize>(cx);
4473 let multi_buffer = this.buffer.read(cx);
4474 let buffer = multi_buffer.snapshot(cx);
4475 selections
4476 .iter()
4477 .map(|selection| {
4478 let start_point = selection.start.to_point(&buffer);
4479 let mut existing_indent =
4480 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4481 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4482 let start = selection.start;
4483 let end = selection.end;
4484 let selection_is_empty = start == end;
4485 let language_scope = buffer.language_scope_at(start);
4486 let (
4487 comment_delimiter,
4488 doc_delimiter,
4489 insert_extra_newline,
4490 indent_on_newline,
4491 indent_on_extra_newline,
4492 ) = if let Some(language) = &language_scope {
4493 let mut insert_extra_newline =
4494 insert_extra_newline_brackets(&buffer, start..end, language)
4495 || insert_extra_newline_tree_sitter(&buffer, start..end);
4496
4497 // Comment extension on newline is allowed only for cursor selections
4498 let comment_delimiter = maybe!({
4499 if !selection_is_empty {
4500 return None;
4501 }
4502
4503 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4504 return None;
4505 }
4506
4507 let delimiters = language.line_comment_prefixes();
4508 let max_len_of_delimiter =
4509 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4510 let (snapshot, range) =
4511 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4512
4513 let num_of_whitespaces = snapshot
4514 .chars_for_range(range.clone())
4515 .take_while(|c| c.is_whitespace())
4516 .count();
4517 let comment_candidate = snapshot
4518 .chars_for_range(range.clone())
4519 .skip(num_of_whitespaces)
4520 .take(max_len_of_delimiter)
4521 .collect::<String>();
4522 let (delimiter, trimmed_len) = delimiters
4523 .iter()
4524 .filter_map(|delimiter| {
4525 let prefix = delimiter.trim_end();
4526 if comment_candidate.starts_with(prefix) {
4527 Some((delimiter, prefix.len()))
4528 } else {
4529 None
4530 }
4531 })
4532 .max_by_key(|(_, len)| *len)?;
4533
4534 if let Some(BlockCommentConfig {
4535 start: block_start, ..
4536 }) = language.block_comment()
4537 {
4538 let block_start_trimmed = block_start.trim_end();
4539 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4540 let line_content = snapshot
4541 .chars_for_range(range)
4542 .skip(num_of_whitespaces)
4543 .take(block_start_trimmed.len())
4544 .collect::<String>();
4545
4546 if line_content.starts_with(block_start_trimmed) {
4547 return None;
4548 }
4549 }
4550 }
4551
4552 let cursor_is_placed_after_comment_marker =
4553 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4554 if cursor_is_placed_after_comment_marker {
4555 Some(delimiter.clone())
4556 } else {
4557 None
4558 }
4559 });
4560
4561 let mut indent_on_newline = IndentSize::spaces(0);
4562 let mut indent_on_extra_newline = IndentSize::spaces(0);
4563
4564 let doc_delimiter = maybe!({
4565 if !selection_is_empty {
4566 return None;
4567 }
4568
4569 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4570 return None;
4571 }
4572
4573 let BlockCommentConfig {
4574 start: start_tag,
4575 end: end_tag,
4576 prefix: delimiter,
4577 tab_size: len,
4578 } = language.documentation_comment()?;
4579 let is_within_block_comment = buffer
4580 .language_scope_at(start_point)
4581 .is_some_and(|scope| scope.override_name() == Some("comment"));
4582 if !is_within_block_comment {
4583 return None;
4584 }
4585
4586 let (snapshot, range) =
4587 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4588
4589 let num_of_whitespaces = snapshot
4590 .chars_for_range(range.clone())
4591 .take_while(|c| c.is_whitespace())
4592 .count();
4593
4594 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
4595 let column = start_point.column;
4596 let cursor_is_after_start_tag = {
4597 let start_tag_len = start_tag.len();
4598 let start_tag_line = snapshot
4599 .chars_for_range(range.clone())
4600 .skip(num_of_whitespaces)
4601 .take(start_tag_len)
4602 .collect::<String>();
4603 if start_tag_line.starts_with(start_tag.as_ref()) {
4604 num_of_whitespaces + start_tag_len <= column as usize
4605 } else {
4606 false
4607 }
4608 };
4609
4610 let cursor_is_after_delimiter = {
4611 let delimiter_trim = delimiter.trim_end();
4612 let delimiter_line = snapshot
4613 .chars_for_range(range.clone())
4614 .skip(num_of_whitespaces)
4615 .take(delimiter_trim.len())
4616 .collect::<String>();
4617 if delimiter_line.starts_with(delimiter_trim) {
4618 num_of_whitespaces + delimiter_trim.len() <= column as usize
4619 } else {
4620 false
4621 }
4622 };
4623
4624 let cursor_is_before_end_tag_if_exists = {
4625 let mut char_position = 0u32;
4626 let mut end_tag_offset = None;
4627
4628 'outer: for chunk in snapshot.text_for_range(range) {
4629 if let Some(byte_pos) = chunk.find(&**end_tag) {
4630 let chars_before_match =
4631 chunk[..byte_pos].chars().count() as u32;
4632 end_tag_offset =
4633 Some(char_position + chars_before_match);
4634 break 'outer;
4635 }
4636 char_position += chunk.chars().count() as u32;
4637 }
4638
4639 if let Some(end_tag_offset) = end_tag_offset {
4640 let cursor_is_before_end_tag = column <= end_tag_offset;
4641 if cursor_is_after_start_tag {
4642 if cursor_is_before_end_tag {
4643 insert_extra_newline = true;
4644 }
4645 let cursor_is_at_start_of_end_tag =
4646 column == end_tag_offset;
4647 if cursor_is_at_start_of_end_tag {
4648 indent_on_extra_newline.len = *len;
4649 }
4650 }
4651 cursor_is_before_end_tag
4652 } else {
4653 true
4654 }
4655 };
4656
4657 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4658 && cursor_is_before_end_tag_if_exists
4659 {
4660 if cursor_is_after_start_tag {
4661 indent_on_newline.len = *len;
4662 }
4663 Some(delimiter.clone())
4664 } else {
4665 None
4666 }
4667 });
4668
4669 (
4670 comment_delimiter,
4671 doc_delimiter,
4672 insert_extra_newline,
4673 indent_on_newline,
4674 indent_on_extra_newline,
4675 )
4676 } else {
4677 (
4678 None,
4679 None,
4680 false,
4681 IndentSize::default(),
4682 IndentSize::default(),
4683 )
4684 };
4685
4686 let prevent_auto_indent = doc_delimiter.is_some();
4687 let delimiter = comment_delimiter.or(doc_delimiter);
4688
4689 let capacity_for_delimiter =
4690 delimiter.as_deref().map(str::len).unwrap_or_default();
4691 let mut new_text = String::with_capacity(
4692 1 + capacity_for_delimiter
4693 + existing_indent.len as usize
4694 + indent_on_newline.len as usize
4695 + indent_on_extra_newline.len as usize,
4696 );
4697 new_text.push('\n');
4698 new_text.extend(existing_indent.chars());
4699 new_text.extend(indent_on_newline.chars());
4700
4701 if let Some(delimiter) = &delimiter {
4702 new_text.push_str(delimiter);
4703 }
4704
4705 if insert_extra_newline {
4706 new_text.push('\n');
4707 new_text.extend(existing_indent.chars());
4708 new_text.extend(indent_on_extra_newline.chars());
4709 }
4710
4711 let anchor = buffer.anchor_after(end);
4712 let new_selection = selection.map(|_| anchor);
4713 (
4714 ((start..end, new_text), prevent_auto_indent),
4715 (insert_extra_newline, new_selection),
4716 )
4717 })
4718 .unzip()
4719 };
4720
4721 let mut auto_indent_edits = Vec::new();
4722 let mut edits = Vec::new();
4723 for (edit, prevent_auto_indent) in edits_with_flags {
4724 if prevent_auto_indent {
4725 edits.push(edit);
4726 } else {
4727 auto_indent_edits.push(edit);
4728 }
4729 }
4730 if !edits.is_empty() {
4731 this.edit(edits, cx);
4732 }
4733 if !auto_indent_edits.is_empty() {
4734 this.edit_with_autoindent(auto_indent_edits, cx);
4735 }
4736
4737 let buffer = this.buffer.read(cx).snapshot(cx);
4738 let new_selections = selection_info
4739 .into_iter()
4740 .map(|(extra_newline_inserted, new_selection)| {
4741 let mut cursor = new_selection.end.to_point(&buffer);
4742 if extra_newline_inserted {
4743 cursor.row -= 1;
4744 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4745 }
4746 new_selection.map(|_| cursor)
4747 })
4748 .collect();
4749
4750 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4751 this.refresh_edit_prediction(true, false, window, cx);
4752 });
4753 }
4754
4755 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4756 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4757
4758 let buffer = self.buffer.read(cx);
4759 let snapshot = buffer.snapshot(cx);
4760
4761 let mut edits = Vec::new();
4762 let mut rows = Vec::new();
4763
4764 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4765 let cursor = selection.head();
4766 let row = cursor.row;
4767
4768 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4769
4770 let newline = "\n".to_string();
4771 edits.push((start_of_line..start_of_line, newline));
4772
4773 rows.push(row + rows_inserted as u32);
4774 }
4775
4776 self.transact(window, cx, |editor, window, cx| {
4777 editor.edit(edits, cx);
4778
4779 editor.change_selections(Default::default(), window, cx, |s| {
4780 let mut index = 0;
4781 s.move_cursors_with(|map, _, _| {
4782 let row = rows[index];
4783 index += 1;
4784
4785 let point = Point::new(row, 0);
4786 let boundary = map.next_line_boundary(point).1;
4787 let clipped = map.clip_point(boundary, Bias::Left);
4788
4789 (clipped, SelectionGoal::None)
4790 });
4791 });
4792
4793 let mut indent_edits = Vec::new();
4794 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4795 for row in rows {
4796 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4797 for (row, indent) in indents {
4798 if indent.len == 0 {
4799 continue;
4800 }
4801
4802 let text = match indent.kind {
4803 IndentKind::Space => " ".repeat(indent.len as usize),
4804 IndentKind::Tab => "\t".repeat(indent.len as usize),
4805 };
4806 let point = Point::new(row.0, 0);
4807 indent_edits.push((point..point, text));
4808 }
4809 }
4810 editor.edit(indent_edits, cx);
4811 });
4812 }
4813
4814 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4816
4817 let buffer = self.buffer.read(cx);
4818 let snapshot = buffer.snapshot(cx);
4819
4820 let mut edits = Vec::new();
4821 let mut rows = Vec::new();
4822 let mut rows_inserted = 0;
4823
4824 for selection in self.selections.all_adjusted(cx) {
4825 let cursor = selection.head();
4826 let row = cursor.row;
4827
4828 let point = Point::new(row + 1, 0);
4829 let start_of_line = snapshot.clip_point(point, Bias::Left);
4830
4831 let newline = "\n".to_string();
4832 edits.push((start_of_line..start_of_line, newline));
4833
4834 rows_inserted += 1;
4835 rows.push(row + rows_inserted);
4836 }
4837
4838 self.transact(window, cx, |editor, window, cx| {
4839 editor.edit(edits, cx);
4840
4841 editor.change_selections(Default::default(), window, cx, |s| {
4842 let mut index = 0;
4843 s.move_cursors_with(|map, _, _| {
4844 let row = rows[index];
4845 index += 1;
4846
4847 let point = Point::new(row, 0);
4848 let boundary = map.next_line_boundary(point).1;
4849 let clipped = map.clip_point(boundary, Bias::Left);
4850
4851 (clipped, SelectionGoal::None)
4852 });
4853 });
4854
4855 let mut indent_edits = Vec::new();
4856 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4857 for row in rows {
4858 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4859 for (row, indent) in indents {
4860 if indent.len == 0 {
4861 continue;
4862 }
4863
4864 let text = match indent.kind {
4865 IndentKind::Space => " ".repeat(indent.len as usize),
4866 IndentKind::Tab => "\t".repeat(indent.len as usize),
4867 };
4868 let point = Point::new(row.0, 0);
4869 indent_edits.push((point..point, text));
4870 }
4871 }
4872 editor.edit(indent_edits, cx);
4873 });
4874 }
4875
4876 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4877 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4878 original_indent_columns: Vec::new(),
4879 });
4880 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4881 }
4882
4883 fn insert_with_autoindent_mode(
4884 &mut self,
4885 text: &str,
4886 autoindent_mode: Option<AutoindentMode>,
4887 window: &mut Window,
4888 cx: &mut Context<Self>,
4889 ) {
4890 if self.read_only(cx) {
4891 return;
4892 }
4893
4894 let text: Arc<str> = text.into();
4895 self.transact(window, cx, |this, window, cx| {
4896 let old_selections = this.selections.all_adjusted(cx);
4897 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4898 let anchors = {
4899 let snapshot = buffer.read(cx);
4900 old_selections
4901 .iter()
4902 .map(|s| {
4903 let anchor = snapshot.anchor_after(s.head());
4904 s.map(|_| anchor)
4905 })
4906 .collect::<Vec<_>>()
4907 };
4908 buffer.edit(
4909 old_selections
4910 .iter()
4911 .map(|s| (s.start..s.end, text.clone())),
4912 autoindent_mode,
4913 cx,
4914 );
4915 anchors
4916 });
4917
4918 this.change_selections(Default::default(), window, cx, |s| {
4919 s.select_anchors(selection_anchors);
4920 });
4921
4922 cx.notify();
4923 });
4924 }
4925
4926 fn trigger_completion_on_input(
4927 &mut self,
4928 text: &str,
4929 trigger_in_words: bool,
4930 window: &mut Window,
4931 cx: &mut Context<Self>,
4932 ) {
4933 let completions_source = self
4934 .context_menu
4935 .borrow()
4936 .as_ref()
4937 .and_then(|menu| match menu {
4938 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4939 CodeContextMenu::CodeActions(_) => None,
4940 });
4941
4942 match completions_source {
4943 Some(CompletionsMenuSource::Words { .. }) => {
4944 self.open_or_update_completions_menu(
4945 Some(CompletionsMenuSource::Words {
4946 ignore_threshold: false,
4947 }),
4948 None,
4949 window,
4950 cx,
4951 );
4952 }
4953 Some(CompletionsMenuSource::Normal)
4954 | Some(CompletionsMenuSource::SnippetChoices)
4955 | None
4956 if self.is_completion_trigger(
4957 text,
4958 trigger_in_words,
4959 completions_source.is_some(),
4960 cx,
4961 ) =>
4962 {
4963 self.show_completions(
4964 &ShowCompletions {
4965 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4966 },
4967 window,
4968 cx,
4969 )
4970 }
4971 _ => {
4972 self.hide_context_menu(window, cx);
4973 }
4974 }
4975 }
4976
4977 fn is_completion_trigger(
4978 &self,
4979 text: &str,
4980 trigger_in_words: bool,
4981 menu_is_open: bool,
4982 cx: &mut Context<Self>,
4983 ) -> bool {
4984 let position = self.selections.newest_anchor().head();
4985 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4986 return false;
4987 };
4988
4989 if let Some(completion_provider) = &self.completion_provider {
4990 completion_provider.is_completion_trigger(
4991 &buffer,
4992 position.text_anchor,
4993 text,
4994 trigger_in_words,
4995 menu_is_open,
4996 cx,
4997 )
4998 } else {
4999 false
5000 }
5001 }
5002
5003 /// If any empty selections is touching the start of its innermost containing autoclose
5004 /// region, expand it to select the brackets.
5005 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5006 let selections = self.selections.all::<usize>(cx);
5007 let buffer = self.buffer.read(cx).read(cx);
5008 let new_selections = self
5009 .selections_with_autoclose_regions(selections, &buffer)
5010 .map(|(mut selection, region)| {
5011 if !selection.is_empty() {
5012 return selection;
5013 }
5014
5015 if let Some(region) = region {
5016 let mut range = region.range.to_offset(&buffer);
5017 if selection.start == range.start && range.start >= region.pair.start.len() {
5018 range.start -= region.pair.start.len();
5019 if buffer.contains_str_at(range.start, ®ion.pair.start)
5020 && buffer.contains_str_at(range.end, ®ion.pair.end)
5021 {
5022 range.end += region.pair.end.len();
5023 selection.start = range.start;
5024 selection.end = range.end;
5025
5026 return selection;
5027 }
5028 }
5029 }
5030
5031 let always_treat_brackets_as_autoclosed = buffer
5032 .language_settings_at(selection.start, cx)
5033 .always_treat_brackets_as_autoclosed;
5034
5035 if !always_treat_brackets_as_autoclosed {
5036 return selection;
5037 }
5038
5039 if let Some(scope) = buffer.language_scope_at(selection.start) {
5040 for (pair, enabled) in scope.brackets() {
5041 if !enabled || !pair.close {
5042 continue;
5043 }
5044
5045 if buffer.contains_str_at(selection.start, &pair.end) {
5046 let pair_start_len = pair.start.len();
5047 if buffer.contains_str_at(
5048 selection.start.saturating_sub(pair_start_len),
5049 &pair.start,
5050 ) {
5051 selection.start -= pair_start_len;
5052 selection.end += pair.end.len();
5053
5054 return selection;
5055 }
5056 }
5057 }
5058 }
5059
5060 selection
5061 })
5062 .collect();
5063
5064 drop(buffer);
5065 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5066 selections.select(new_selections)
5067 });
5068 }
5069
5070 /// Iterate the given selections, and for each one, find the smallest surrounding
5071 /// autoclose region. This uses the ordering of the selections and the autoclose
5072 /// regions to avoid repeated comparisons.
5073 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5074 &'a self,
5075 selections: impl IntoIterator<Item = Selection<D>>,
5076 buffer: &'a MultiBufferSnapshot,
5077 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5078 let mut i = 0;
5079 let mut regions = self.autoclose_regions.as_slice();
5080 selections.into_iter().map(move |selection| {
5081 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5082
5083 let mut enclosing = None;
5084 while let Some(pair_state) = regions.get(i) {
5085 if pair_state.range.end.to_offset(buffer) < range.start {
5086 regions = ®ions[i + 1..];
5087 i = 0;
5088 } else if pair_state.range.start.to_offset(buffer) > range.end {
5089 break;
5090 } else {
5091 if pair_state.selection_id == selection.id {
5092 enclosing = Some(pair_state);
5093 }
5094 i += 1;
5095 }
5096 }
5097
5098 (selection, enclosing)
5099 })
5100 }
5101
5102 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5103 fn invalidate_autoclose_regions(
5104 &mut self,
5105 mut selections: &[Selection<Anchor>],
5106 buffer: &MultiBufferSnapshot,
5107 ) {
5108 self.autoclose_regions.retain(|state| {
5109 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5110 return false;
5111 }
5112
5113 let mut i = 0;
5114 while let Some(selection) = selections.get(i) {
5115 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5116 selections = &selections[1..];
5117 continue;
5118 }
5119 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5120 break;
5121 }
5122 if selection.id == state.selection_id {
5123 return true;
5124 } else {
5125 i += 1;
5126 }
5127 }
5128 false
5129 });
5130 }
5131
5132 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5133 let offset = position.to_offset(buffer);
5134 let (word_range, kind) =
5135 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5136 if offset > word_range.start && kind == Some(CharKind::Word) {
5137 Some(
5138 buffer
5139 .text_for_range(word_range.start..offset)
5140 .collect::<String>(),
5141 )
5142 } else {
5143 None
5144 }
5145 }
5146
5147 pub fn toggle_inline_values(
5148 &mut self,
5149 _: &ToggleInlineValues,
5150 _: &mut Window,
5151 cx: &mut Context<Self>,
5152 ) {
5153 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5154
5155 self.refresh_inline_values(cx);
5156 }
5157
5158 pub fn toggle_inlay_hints(
5159 &mut self,
5160 _: &ToggleInlayHints,
5161 _: &mut Window,
5162 cx: &mut Context<Self>,
5163 ) {
5164 self.refresh_inlay_hints(
5165 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5166 cx,
5167 );
5168 }
5169
5170 pub fn inlay_hints_enabled(&self) -> bool {
5171 self.inlay_hint_cache.enabled
5172 }
5173
5174 pub fn inline_values_enabled(&self) -> bool {
5175 self.inline_value_cache.enabled
5176 }
5177
5178 #[cfg(any(test, feature = "test-support"))]
5179 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5180 self.display_map
5181 .read(cx)
5182 .current_inlays()
5183 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5184 .cloned()
5185 .collect()
5186 }
5187
5188 #[cfg(any(test, feature = "test-support"))]
5189 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5190 self.display_map
5191 .read(cx)
5192 .current_inlays()
5193 .cloned()
5194 .collect()
5195 }
5196
5197 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5198 if self.semantics_provider.is_none() || !self.mode.is_full() {
5199 return;
5200 }
5201
5202 let reason_description = reason.description();
5203 let ignore_debounce = matches!(
5204 reason,
5205 InlayHintRefreshReason::SettingsChange(_)
5206 | InlayHintRefreshReason::Toggle(_)
5207 | InlayHintRefreshReason::ExcerptsRemoved(_)
5208 | InlayHintRefreshReason::ModifiersChanged(_)
5209 );
5210 let (invalidate_cache, required_languages) = match reason {
5211 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5212 match self.inlay_hint_cache.modifiers_override(enabled) {
5213 Some(enabled) => {
5214 if enabled {
5215 (InvalidationStrategy::RefreshRequested, None)
5216 } else {
5217 self.splice_inlays(
5218 &self
5219 .visible_inlay_hints(cx)
5220 .iter()
5221 .map(|inlay| inlay.id)
5222 .collect::<Vec<InlayId>>(),
5223 Vec::new(),
5224 cx,
5225 );
5226 return;
5227 }
5228 }
5229 None => return,
5230 }
5231 }
5232 InlayHintRefreshReason::Toggle(enabled) => {
5233 if self.inlay_hint_cache.toggle(enabled) {
5234 if enabled {
5235 (InvalidationStrategy::RefreshRequested, None)
5236 } else {
5237 self.splice_inlays(
5238 &self
5239 .visible_inlay_hints(cx)
5240 .iter()
5241 .map(|inlay| inlay.id)
5242 .collect::<Vec<InlayId>>(),
5243 Vec::new(),
5244 cx,
5245 );
5246 return;
5247 }
5248 } else {
5249 return;
5250 }
5251 }
5252 InlayHintRefreshReason::SettingsChange(new_settings) => {
5253 match self.inlay_hint_cache.update_settings(
5254 &self.buffer,
5255 new_settings,
5256 self.visible_inlay_hints(cx),
5257 cx,
5258 ) {
5259 ControlFlow::Break(Some(InlaySplice {
5260 to_remove,
5261 to_insert,
5262 })) => {
5263 self.splice_inlays(&to_remove, to_insert, cx);
5264 return;
5265 }
5266 ControlFlow::Break(None) => return,
5267 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5268 }
5269 }
5270 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5271 if let Some(InlaySplice {
5272 to_remove,
5273 to_insert,
5274 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5275 {
5276 self.splice_inlays(&to_remove, to_insert, cx);
5277 }
5278 self.display_map.update(cx, |display_map, _| {
5279 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5280 });
5281 return;
5282 }
5283 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5284 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5285 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5286 }
5287 InlayHintRefreshReason::RefreshRequested => {
5288 (InvalidationStrategy::RefreshRequested, None)
5289 }
5290 };
5291
5292 if let Some(InlaySplice {
5293 to_remove,
5294 to_insert,
5295 }) = self.inlay_hint_cache.spawn_hint_refresh(
5296 reason_description,
5297 self.visible_excerpts(required_languages.as_ref(), cx),
5298 invalidate_cache,
5299 ignore_debounce,
5300 cx,
5301 ) {
5302 self.splice_inlays(&to_remove, to_insert, cx);
5303 }
5304 }
5305
5306 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5307 self.display_map
5308 .read(cx)
5309 .current_inlays()
5310 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5311 .cloned()
5312 .collect()
5313 }
5314
5315 pub fn visible_excerpts(
5316 &self,
5317 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5318 cx: &mut Context<Editor>,
5319 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5320 let Some(project) = self.project() else {
5321 return HashMap::default();
5322 };
5323 let project = project.read(cx);
5324 let multi_buffer = self.buffer().read(cx);
5325 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5326 let multi_buffer_visible_start = self
5327 .scroll_manager
5328 .anchor()
5329 .anchor
5330 .to_point(&multi_buffer_snapshot);
5331 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5332 multi_buffer_visible_start
5333 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5334 Bias::Left,
5335 );
5336 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5337 multi_buffer_snapshot
5338 .range_to_buffer_ranges(multi_buffer_visible_range)
5339 .into_iter()
5340 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5341 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5342 let buffer_file = project::File::from_dyn(buffer.file())?;
5343 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5344 let worktree_entry = buffer_worktree
5345 .read(cx)
5346 .entry_for_id(buffer_file.project_entry_id()?)?;
5347 if worktree_entry.is_ignored {
5348 return None;
5349 }
5350
5351 let language = buffer.language()?;
5352 if let Some(restrict_to_languages) = restrict_to_languages
5353 && !restrict_to_languages.contains(language)
5354 {
5355 return None;
5356 }
5357 Some((
5358 excerpt_id,
5359 (
5360 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5361 buffer.version().clone(),
5362 excerpt_visible_range,
5363 ),
5364 ))
5365 })
5366 .collect()
5367 }
5368
5369 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5370 TextLayoutDetails {
5371 text_system: window.text_system().clone(),
5372 editor_style: self.style.clone().unwrap(),
5373 rem_size: window.rem_size(),
5374 scroll_anchor: self.scroll_manager.anchor(),
5375 visible_rows: self.visible_line_count(),
5376 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5377 }
5378 }
5379
5380 pub fn splice_inlays(
5381 &self,
5382 to_remove: &[InlayId],
5383 to_insert: Vec<Inlay>,
5384 cx: &mut Context<Self>,
5385 ) {
5386 self.display_map.update(cx, |display_map, cx| {
5387 display_map.splice_inlays(to_remove, to_insert, cx)
5388 });
5389 cx.notify();
5390 }
5391
5392 fn trigger_on_type_formatting(
5393 &self,
5394 input: String,
5395 window: &mut Window,
5396 cx: &mut Context<Self>,
5397 ) -> Option<Task<Result<()>>> {
5398 if input.len() != 1 {
5399 return None;
5400 }
5401
5402 let project = self.project()?;
5403 let position = self.selections.newest_anchor().head();
5404 let (buffer, buffer_position) = self
5405 .buffer
5406 .read(cx)
5407 .text_anchor_for_position(position, cx)?;
5408
5409 let settings = language_settings::language_settings(
5410 buffer
5411 .read(cx)
5412 .language_at(buffer_position)
5413 .map(|l| l.name()),
5414 buffer.read(cx).file(),
5415 cx,
5416 );
5417 if !settings.use_on_type_format {
5418 return None;
5419 }
5420
5421 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5422 // hence we do LSP request & edit on host side only — add formats to host's history.
5423 let push_to_lsp_host_history = true;
5424 // If this is not the host, append its history with new edits.
5425 let push_to_client_history = project.read(cx).is_via_collab();
5426
5427 let on_type_formatting = project.update(cx, |project, cx| {
5428 project.on_type_format(
5429 buffer.clone(),
5430 buffer_position,
5431 input,
5432 push_to_lsp_host_history,
5433 cx,
5434 )
5435 });
5436 Some(cx.spawn_in(window, async move |editor, cx| {
5437 if let Some(transaction) = on_type_formatting.await? {
5438 if push_to_client_history {
5439 buffer
5440 .update(cx, |buffer, _| {
5441 buffer.push_transaction(transaction, Instant::now());
5442 buffer.finalize_last_transaction();
5443 })
5444 .ok();
5445 }
5446 editor.update(cx, |editor, cx| {
5447 editor.refresh_document_highlights(cx);
5448 })?;
5449 }
5450 Ok(())
5451 }))
5452 }
5453
5454 pub fn show_word_completions(
5455 &mut self,
5456 _: &ShowWordCompletions,
5457 window: &mut Window,
5458 cx: &mut Context<Self>,
5459 ) {
5460 self.open_or_update_completions_menu(
5461 Some(CompletionsMenuSource::Words {
5462 ignore_threshold: true,
5463 }),
5464 None,
5465 window,
5466 cx,
5467 );
5468 }
5469
5470 pub fn show_completions(
5471 &mut self,
5472 options: &ShowCompletions,
5473 window: &mut Window,
5474 cx: &mut Context<Self>,
5475 ) {
5476 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5477 }
5478
5479 fn open_or_update_completions_menu(
5480 &mut self,
5481 requested_source: Option<CompletionsMenuSource>,
5482 trigger: Option<&str>,
5483 window: &mut Window,
5484 cx: &mut Context<Self>,
5485 ) {
5486 if self.pending_rename.is_some() {
5487 return;
5488 }
5489
5490 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5491
5492 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5493 // inserted and selected. To handle that case, the start of the selection is used so that
5494 // the menu starts with all choices.
5495 let position = self
5496 .selections
5497 .newest_anchor()
5498 .start
5499 .bias_right(&multibuffer_snapshot);
5500 if position.diff_base_anchor.is_some() {
5501 return;
5502 }
5503 let buffer_position = multibuffer_snapshot.anchor_before(position);
5504 let Some(buffer) = buffer_position
5505 .buffer_id
5506 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5507 else {
5508 return;
5509 };
5510 let buffer_snapshot = buffer.read(cx).snapshot();
5511
5512 let query: Option<Arc<String>> =
5513 Self::completion_query(&multibuffer_snapshot, buffer_position)
5514 .map(|query| query.into());
5515
5516 drop(multibuffer_snapshot);
5517
5518 // Hide the current completions menu when query is empty. Without this, cached
5519 // completions from before the trigger char may be reused (#32774).
5520 if query.is_none() {
5521 let menu_is_open = matches!(
5522 self.context_menu.borrow().as_ref(),
5523 Some(CodeContextMenu::Completions(_))
5524 );
5525 if menu_is_open {
5526 self.hide_context_menu(window, cx);
5527 }
5528 }
5529
5530 let mut ignore_word_threshold = false;
5531 let provider = match requested_source {
5532 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5533 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5534 ignore_word_threshold = ignore_threshold;
5535 None
5536 }
5537 Some(CompletionsMenuSource::SnippetChoices) => {
5538 log::error!("bug: SnippetChoices requested_source is not handled");
5539 None
5540 }
5541 };
5542
5543 let sort_completions = provider
5544 .as_ref()
5545 .is_some_and(|provider| provider.sort_completions());
5546
5547 let filter_completions = provider
5548 .as_ref()
5549 .is_none_or(|provider| provider.filter_completions());
5550
5551 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5552 if filter_completions {
5553 menu.filter(query.clone(), provider.clone(), window, cx);
5554 }
5555 // When `is_incomplete` is false, no need to re-query completions when the current query
5556 // is a suffix of the initial query.
5557 if !menu.is_incomplete {
5558 // If the new query is a suffix of the old query (typing more characters) and
5559 // the previous result was complete, the existing completions can be filtered.
5560 //
5561 // Note that this is always true for snippet completions.
5562 let query_matches = match (&menu.initial_query, &query) {
5563 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5564 (None, _) => true,
5565 _ => false,
5566 };
5567 if query_matches {
5568 let position_matches = if menu.initial_position == position {
5569 true
5570 } else {
5571 let snapshot = self.buffer.read(cx).read(cx);
5572 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5573 };
5574 if position_matches {
5575 return;
5576 }
5577 }
5578 }
5579 };
5580
5581 let trigger_kind = match trigger {
5582 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5583 CompletionTriggerKind::TRIGGER_CHARACTER
5584 }
5585 _ => CompletionTriggerKind::INVOKED,
5586 };
5587 let completion_context = CompletionContext {
5588 trigger_character: trigger.and_then(|trigger| {
5589 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5590 Some(String::from(trigger))
5591 } else {
5592 None
5593 }
5594 }),
5595 trigger_kind,
5596 };
5597
5598 let Anchor {
5599 excerpt_id: buffer_excerpt_id,
5600 text_anchor: buffer_position,
5601 ..
5602 } = buffer_position;
5603
5604 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5605 buffer_snapshot.surrounding_word(buffer_position, None)
5606 {
5607 let word_to_exclude = buffer_snapshot
5608 .text_for_range(word_range.clone())
5609 .collect::<String>();
5610 (
5611 buffer_snapshot.anchor_before(word_range.start)
5612 ..buffer_snapshot.anchor_after(buffer_position),
5613 Some(word_to_exclude),
5614 )
5615 } else {
5616 (buffer_position..buffer_position, None)
5617 };
5618
5619 let language = buffer_snapshot
5620 .language_at(buffer_position)
5621 .map(|language| language.name());
5622
5623 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5624 .completions
5625 .clone();
5626
5627 let show_completion_documentation = buffer_snapshot
5628 .settings_at(buffer_position, cx)
5629 .show_completion_documentation;
5630
5631 // The document can be large, so stay in reasonable bounds when searching for words,
5632 // otherwise completion pop-up might be slow to appear.
5633 const WORD_LOOKUP_ROWS: u32 = 5_000;
5634 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5635 let min_word_search = buffer_snapshot.clip_point(
5636 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5637 Bias::Left,
5638 );
5639 let max_word_search = buffer_snapshot.clip_point(
5640 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5641 Bias::Right,
5642 );
5643 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5644 ..buffer_snapshot.point_to_offset(max_word_search);
5645
5646 let skip_digits = query
5647 .as_ref()
5648 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5649
5650 let omit_word_completions = !self.word_completions_enabled
5651 || (!ignore_word_threshold
5652 && match &query {
5653 Some(query) => query.chars().count() < completion_settings.words_min_length,
5654 None => completion_settings.words_min_length != 0,
5655 });
5656
5657 let (mut words, provider_responses) = match &provider {
5658 Some(provider) => {
5659 let provider_responses = provider.completions(
5660 buffer_excerpt_id,
5661 &buffer,
5662 buffer_position,
5663 completion_context,
5664 window,
5665 cx,
5666 );
5667
5668 let words = match (omit_word_completions, completion_settings.words) {
5669 (true, _) | (_, WordsCompletionMode::Disabled) => {
5670 Task::ready(BTreeMap::default())
5671 }
5672 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5673 .background_spawn(async move {
5674 buffer_snapshot.words_in_range(WordsQuery {
5675 fuzzy_contents: None,
5676 range: word_search_range,
5677 skip_digits,
5678 })
5679 }),
5680 };
5681
5682 (words, provider_responses)
5683 }
5684 None => {
5685 let words = if omit_word_completions {
5686 Task::ready(BTreeMap::default())
5687 } else {
5688 cx.background_spawn(async move {
5689 buffer_snapshot.words_in_range(WordsQuery {
5690 fuzzy_contents: None,
5691 range: word_search_range,
5692 skip_digits,
5693 })
5694 })
5695 };
5696 (words, Task::ready(Ok(Vec::new())))
5697 }
5698 };
5699
5700 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5701
5702 let id = post_inc(&mut self.next_completion_id);
5703 let task = cx.spawn_in(window, async move |editor, cx| {
5704 let Ok(()) = editor.update(cx, |this, _| {
5705 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5706 }) else {
5707 return;
5708 };
5709
5710 // TODO: Ideally completions from different sources would be selectively re-queried, so
5711 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5712 let mut completions = Vec::new();
5713 let mut is_incomplete = false;
5714 let mut display_options: Option<CompletionDisplayOptions> = None;
5715 if let Some(provider_responses) = provider_responses.await.log_err()
5716 && !provider_responses.is_empty()
5717 {
5718 for response in provider_responses {
5719 completions.extend(response.completions);
5720 is_incomplete = is_incomplete || response.is_incomplete;
5721 match display_options.as_mut() {
5722 None => {
5723 display_options = Some(response.display_options);
5724 }
5725 Some(options) => options.merge(&response.display_options),
5726 }
5727 }
5728 if completion_settings.words == WordsCompletionMode::Fallback {
5729 words = Task::ready(BTreeMap::default());
5730 }
5731 }
5732 let display_options = display_options.unwrap_or_default();
5733
5734 let mut words = words.await;
5735 if let Some(word_to_exclude) = &word_to_exclude {
5736 words.remove(word_to_exclude);
5737 }
5738 for lsp_completion in &completions {
5739 words.remove(&lsp_completion.new_text);
5740 }
5741 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5742 replace_range: word_replace_range.clone(),
5743 new_text: word.clone(),
5744 label: CodeLabel::plain(word, None),
5745 icon_path: None,
5746 documentation: None,
5747 source: CompletionSource::BufferWord {
5748 word_range,
5749 resolved: false,
5750 },
5751 insert_text_mode: Some(InsertTextMode::AS_IS),
5752 confirm: None,
5753 }));
5754
5755 let menu = if completions.is_empty() {
5756 None
5757 } else {
5758 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5759 let languages = editor
5760 .workspace
5761 .as_ref()
5762 .and_then(|(workspace, _)| workspace.upgrade())
5763 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5764 let menu = CompletionsMenu::new(
5765 id,
5766 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5767 sort_completions,
5768 show_completion_documentation,
5769 position,
5770 query.clone(),
5771 is_incomplete,
5772 buffer.clone(),
5773 completions.into(),
5774 display_options,
5775 snippet_sort_order,
5776 languages,
5777 language,
5778 cx,
5779 );
5780
5781 let query = if filter_completions { query } else { None };
5782 let matches_task = if let Some(query) = query {
5783 menu.do_async_filtering(query, cx)
5784 } else {
5785 Task::ready(menu.unfiltered_matches())
5786 };
5787 (menu, matches_task)
5788 }) else {
5789 return;
5790 };
5791
5792 let matches = matches_task.await;
5793
5794 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5795 // Newer menu already set, so exit.
5796 if let Some(CodeContextMenu::Completions(prev_menu)) =
5797 editor.context_menu.borrow().as_ref()
5798 && prev_menu.id > id
5799 {
5800 return;
5801 };
5802
5803 // Only valid to take prev_menu because it the new menu is immediately set
5804 // below, or the menu is hidden.
5805 if let Some(CodeContextMenu::Completions(prev_menu)) =
5806 editor.context_menu.borrow_mut().take()
5807 {
5808 let position_matches =
5809 if prev_menu.initial_position == menu.initial_position {
5810 true
5811 } else {
5812 let snapshot = editor.buffer.read(cx).read(cx);
5813 prev_menu.initial_position.to_offset(&snapshot)
5814 == menu.initial_position.to_offset(&snapshot)
5815 };
5816 if position_matches {
5817 // Preserve markdown cache before `set_filter_results` because it will
5818 // try to populate the documentation cache.
5819 menu.preserve_markdown_cache(prev_menu);
5820 }
5821 };
5822
5823 menu.set_filter_results(matches, provider, window, cx);
5824 }) else {
5825 return;
5826 };
5827
5828 menu.visible().then_some(menu)
5829 };
5830
5831 editor
5832 .update_in(cx, |editor, window, cx| {
5833 if editor.focus_handle.is_focused(window)
5834 && let Some(menu) = menu
5835 {
5836 *editor.context_menu.borrow_mut() =
5837 Some(CodeContextMenu::Completions(menu));
5838
5839 crate::hover_popover::hide_hover(editor, cx);
5840 if editor.show_edit_predictions_in_menu() {
5841 editor.update_visible_edit_prediction(window, cx);
5842 } else {
5843 editor.discard_edit_prediction(false, cx);
5844 }
5845
5846 cx.notify();
5847 return;
5848 }
5849
5850 if editor.completion_tasks.len() <= 1 {
5851 // If there are no more completion tasks and the last menu was empty, we should hide it.
5852 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5853 // If it was already hidden and we don't show edit predictions in the menu,
5854 // we should also show the edit prediction when available.
5855 if was_hidden && editor.show_edit_predictions_in_menu() {
5856 editor.update_visible_edit_prediction(window, cx);
5857 }
5858 }
5859 })
5860 .ok();
5861 });
5862
5863 self.completion_tasks.push((id, task));
5864 }
5865
5866 #[cfg(feature = "test-support")]
5867 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5868 let menu = self.context_menu.borrow();
5869 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5870 let completions = menu.completions.borrow();
5871 Some(completions.to_vec())
5872 } else {
5873 None
5874 }
5875 }
5876
5877 pub fn with_completions_menu_matching_id<R>(
5878 &self,
5879 id: CompletionId,
5880 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5881 ) -> R {
5882 let mut context_menu = self.context_menu.borrow_mut();
5883 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5884 return f(None);
5885 };
5886 if completions_menu.id != id {
5887 return f(None);
5888 }
5889 f(Some(completions_menu))
5890 }
5891
5892 pub fn confirm_completion(
5893 &mut self,
5894 action: &ConfirmCompletion,
5895 window: &mut Window,
5896 cx: &mut Context<Self>,
5897 ) -> Option<Task<Result<()>>> {
5898 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5899 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5900 }
5901
5902 pub fn confirm_completion_insert(
5903 &mut self,
5904 _: &ConfirmCompletionInsert,
5905 window: &mut Window,
5906 cx: &mut Context<Self>,
5907 ) -> Option<Task<Result<()>>> {
5908 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5909 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5910 }
5911
5912 pub fn confirm_completion_replace(
5913 &mut self,
5914 _: &ConfirmCompletionReplace,
5915 window: &mut Window,
5916 cx: &mut Context<Self>,
5917 ) -> Option<Task<Result<()>>> {
5918 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5919 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5920 }
5921
5922 pub fn compose_completion(
5923 &mut self,
5924 action: &ComposeCompletion,
5925 window: &mut Window,
5926 cx: &mut Context<Self>,
5927 ) -> Option<Task<Result<()>>> {
5928 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5929 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5930 }
5931
5932 fn do_completion(
5933 &mut self,
5934 item_ix: Option<usize>,
5935 intent: CompletionIntent,
5936 window: &mut Window,
5937 cx: &mut Context<Editor>,
5938 ) -> Option<Task<Result<()>>> {
5939 use language::ToOffset as _;
5940
5941 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5942 else {
5943 return None;
5944 };
5945
5946 let candidate_id = {
5947 let entries = completions_menu.entries.borrow();
5948 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5949 if self.show_edit_predictions_in_menu() {
5950 self.discard_edit_prediction(true, cx);
5951 }
5952 mat.candidate_id
5953 };
5954
5955 let completion = completions_menu
5956 .completions
5957 .borrow()
5958 .get(candidate_id)?
5959 .clone();
5960 cx.stop_propagation();
5961
5962 let buffer_handle = completions_menu.buffer.clone();
5963
5964 let CompletionEdit {
5965 new_text,
5966 snippet,
5967 replace_range,
5968 } = process_completion_for_edit(
5969 &completion,
5970 intent,
5971 &buffer_handle,
5972 &completions_menu.initial_position.text_anchor,
5973 cx,
5974 );
5975
5976 let buffer = buffer_handle.read(cx);
5977 let snapshot = self.buffer.read(cx).snapshot(cx);
5978 let newest_anchor = self.selections.newest_anchor();
5979 let replace_range_multibuffer = {
5980 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5981 let multibuffer_anchor = snapshot
5982 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5983 .unwrap()
5984 ..snapshot
5985 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5986 .unwrap();
5987 multibuffer_anchor.start.to_offset(&snapshot)
5988 ..multibuffer_anchor.end.to_offset(&snapshot)
5989 };
5990 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5991 return None;
5992 }
5993
5994 let old_text = buffer
5995 .text_for_range(replace_range.clone())
5996 .collect::<String>();
5997 let lookbehind = newest_anchor
5998 .start
5999 .text_anchor
6000 .to_offset(buffer)
6001 .saturating_sub(replace_range.start);
6002 let lookahead = replace_range
6003 .end
6004 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6005 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6006 let suffix = &old_text[lookbehind.min(old_text.len())..];
6007
6008 let selections = self.selections.all::<usize>(cx);
6009 let mut ranges = Vec::new();
6010 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6011
6012 for selection in &selections {
6013 let range = if selection.id == newest_anchor.id {
6014 replace_range_multibuffer.clone()
6015 } else {
6016 let mut range = selection.range();
6017
6018 // if prefix is present, don't duplicate it
6019 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6020 range.start = range.start.saturating_sub(lookbehind);
6021
6022 // if suffix is also present, mimic the newest cursor and replace it
6023 if selection.id != newest_anchor.id
6024 && snapshot.contains_str_at(range.end, suffix)
6025 {
6026 range.end += lookahead;
6027 }
6028 }
6029 range
6030 };
6031
6032 ranges.push(range.clone());
6033
6034 if !self.linked_edit_ranges.is_empty() {
6035 let start_anchor = snapshot.anchor_before(range.start);
6036 let end_anchor = snapshot.anchor_after(range.end);
6037 if let Some(ranges) = self
6038 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6039 {
6040 for (buffer, edits) in ranges {
6041 linked_edits
6042 .entry(buffer.clone())
6043 .or_default()
6044 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6045 }
6046 }
6047 }
6048 }
6049
6050 let common_prefix_len = old_text
6051 .chars()
6052 .zip(new_text.chars())
6053 .take_while(|(a, b)| a == b)
6054 .map(|(a, _)| a.len_utf8())
6055 .sum::<usize>();
6056
6057 cx.emit(EditorEvent::InputHandled {
6058 utf16_range_to_replace: None,
6059 text: new_text[common_prefix_len..].into(),
6060 });
6061
6062 self.transact(window, cx, |editor, window, cx| {
6063 if let Some(mut snippet) = snippet {
6064 snippet.text = new_text.to_string();
6065 editor
6066 .insert_snippet(&ranges, snippet, window, cx)
6067 .log_err();
6068 } else {
6069 editor.buffer.update(cx, |multi_buffer, cx| {
6070 let auto_indent = match completion.insert_text_mode {
6071 Some(InsertTextMode::AS_IS) => None,
6072 _ => editor.autoindent_mode.clone(),
6073 };
6074 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6075 multi_buffer.edit(edits, auto_indent, cx);
6076 });
6077 }
6078 for (buffer, edits) in linked_edits {
6079 buffer.update(cx, |buffer, cx| {
6080 let snapshot = buffer.snapshot();
6081 let edits = edits
6082 .into_iter()
6083 .map(|(range, text)| {
6084 use text::ToPoint as TP;
6085 let end_point = TP::to_point(&range.end, &snapshot);
6086 let start_point = TP::to_point(&range.start, &snapshot);
6087 (start_point..end_point, text)
6088 })
6089 .sorted_by_key(|(range, _)| range.start);
6090 buffer.edit(edits, None, cx);
6091 })
6092 }
6093
6094 editor.refresh_edit_prediction(true, false, window, cx);
6095 });
6096 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6097
6098 let show_new_completions_on_confirm = completion
6099 .confirm
6100 .as_ref()
6101 .is_some_and(|confirm| confirm(intent, window, cx));
6102 if show_new_completions_on_confirm {
6103 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6104 }
6105
6106 let provider = self.completion_provider.as_ref()?;
6107 drop(completion);
6108 let apply_edits = provider.apply_additional_edits_for_completion(
6109 buffer_handle,
6110 completions_menu.completions.clone(),
6111 candidate_id,
6112 true,
6113 cx,
6114 );
6115
6116 let editor_settings = EditorSettings::get_global(cx);
6117 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6118 // After the code completion is finished, users often want to know what signatures are needed.
6119 // so we should automatically call signature_help
6120 self.show_signature_help(&ShowSignatureHelp, window, cx);
6121 }
6122
6123 Some(cx.foreground_executor().spawn(async move {
6124 apply_edits.await?;
6125 Ok(())
6126 }))
6127 }
6128
6129 pub fn toggle_code_actions(
6130 &mut self,
6131 action: &ToggleCodeActions,
6132 window: &mut Window,
6133 cx: &mut Context<Self>,
6134 ) {
6135 let quick_launch = action.quick_launch;
6136 let mut context_menu = self.context_menu.borrow_mut();
6137 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6138 if code_actions.deployed_from == action.deployed_from {
6139 // Toggle if we're selecting the same one
6140 *context_menu = None;
6141 cx.notify();
6142 return;
6143 } else {
6144 // Otherwise, clear it and start a new one
6145 *context_menu = None;
6146 cx.notify();
6147 }
6148 }
6149 drop(context_menu);
6150 let snapshot = self.snapshot(window, cx);
6151 let deployed_from = action.deployed_from.clone();
6152 let action = action.clone();
6153 self.completion_tasks.clear();
6154 self.discard_edit_prediction(false, cx);
6155
6156 let multibuffer_point = match &action.deployed_from {
6157 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6158 DisplayPoint::new(*row, 0).to_point(&snapshot)
6159 }
6160 _ => self.selections.newest::<Point>(cx).head(),
6161 };
6162 let Some((buffer, buffer_row)) = snapshot
6163 .buffer_snapshot()
6164 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6165 .and_then(|(buffer_snapshot, range)| {
6166 self.buffer()
6167 .read(cx)
6168 .buffer(buffer_snapshot.remote_id())
6169 .map(|buffer| (buffer, range.start.row))
6170 })
6171 else {
6172 return;
6173 };
6174 let buffer_id = buffer.read(cx).remote_id();
6175 let tasks = self
6176 .tasks
6177 .get(&(buffer_id, buffer_row))
6178 .map(|t| Arc::new(t.to_owned()));
6179
6180 if !self.focus_handle.is_focused(window) {
6181 return;
6182 }
6183 let project = self.project.clone();
6184
6185 let code_actions_task = match deployed_from {
6186 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6187 _ => self.code_actions(buffer_row, window, cx),
6188 };
6189
6190 let runnable_task = match deployed_from {
6191 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6192 _ => {
6193 let mut task_context_task = Task::ready(None);
6194 if let Some(tasks) = &tasks
6195 && let Some(project) = project
6196 {
6197 task_context_task =
6198 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6199 }
6200
6201 cx.spawn_in(window, {
6202 let buffer = buffer.clone();
6203 async move |editor, cx| {
6204 let task_context = task_context_task.await;
6205
6206 let resolved_tasks =
6207 tasks
6208 .zip(task_context.clone())
6209 .map(|(tasks, task_context)| ResolvedTasks {
6210 templates: tasks.resolve(&task_context).collect(),
6211 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6212 multibuffer_point.row,
6213 tasks.column,
6214 )),
6215 });
6216 let debug_scenarios = editor
6217 .update(cx, |editor, cx| {
6218 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6219 })?
6220 .await;
6221 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6222 }
6223 })
6224 }
6225 };
6226
6227 cx.spawn_in(window, async move |editor, cx| {
6228 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6229 let code_actions = code_actions_task.await;
6230 let spawn_straight_away = quick_launch
6231 && resolved_tasks
6232 .as_ref()
6233 .is_some_and(|tasks| tasks.templates.len() == 1)
6234 && code_actions
6235 .as_ref()
6236 .is_none_or(|actions| actions.is_empty())
6237 && debug_scenarios.is_empty();
6238
6239 editor.update_in(cx, |editor, window, cx| {
6240 crate::hover_popover::hide_hover(editor, cx);
6241 let actions = CodeActionContents::new(
6242 resolved_tasks,
6243 code_actions,
6244 debug_scenarios,
6245 task_context.unwrap_or_default(),
6246 );
6247
6248 // Don't show the menu if there are no actions available
6249 if actions.is_empty() {
6250 cx.notify();
6251 return Task::ready(Ok(()));
6252 }
6253
6254 *editor.context_menu.borrow_mut() =
6255 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6256 buffer,
6257 actions,
6258 selected_item: Default::default(),
6259 scroll_handle: UniformListScrollHandle::default(),
6260 deployed_from,
6261 }));
6262 cx.notify();
6263 if spawn_straight_away
6264 && let Some(task) = editor.confirm_code_action(
6265 &ConfirmCodeAction { item_ix: Some(0) },
6266 window,
6267 cx,
6268 )
6269 {
6270 return task;
6271 }
6272
6273 Task::ready(Ok(()))
6274 })
6275 })
6276 .detach_and_log_err(cx);
6277 }
6278
6279 fn debug_scenarios(
6280 &mut self,
6281 resolved_tasks: &Option<ResolvedTasks>,
6282 buffer: &Entity<Buffer>,
6283 cx: &mut App,
6284 ) -> Task<Vec<task::DebugScenario>> {
6285 maybe!({
6286 let project = self.project()?;
6287 let dap_store = project.read(cx).dap_store();
6288 let mut scenarios = vec![];
6289 let resolved_tasks = resolved_tasks.as_ref()?;
6290 let buffer = buffer.read(cx);
6291 let language = buffer.language()?;
6292 let file = buffer.file();
6293 let debug_adapter = language_settings(language.name().into(), file, cx)
6294 .debuggers
6295 .first()
6296 .map(SharedString::from)
6297 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6298
6299 dap_store.update(cx, |dap_store, cx| {
6300 for (_, task) in &resolved_tasks.templates {
6301 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6302 task.original_task().clone(),
6303 debug_adapter.clone().into(),
6304 task.display_label().to_owned().into(),
6305 cx,
6306 );
6307 scenarios.push(maybe_scenario);
6308 }
6309 });
6310 Some(cx.background_spawn(async move {
6311 futures::future::join_all(scenarios)
6312 .await
6313 .into_iter()
6314 .flatten()
6315 .collect::<Vec<_>>()
6316 }))
6317 })
6318 .unwrap_or_else(|| Task::ready(vec![]))
6319 }
6320
6321 fn code_actions(
6322 &mut self,
6323 buffer_row: u32,
6324 window: &mut Window,
6325 cx: &mut Context<Self>,
6326 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6327 let mut task = self.code_actions_task.take();
6328 cx.spawn_in(window, async move |editor, cx| {
6329 while let Some(prev_task) = task {
6330 prev_task.await.log_err();
6331 task = editor
6332 .update(cx, |this, _| this.code_actions_task.take())
6333 .ok()?;
6334 }
6335
6336 editor
6337 .update(cx, |editor, cx| {
6338 editor
6339 .available_code_actions
6340 .clone()
6341 .and_then(|(location, code_actions)| {
6342 let snapshot = location.buffer.read(cx).snapshot();
6343 let point_range = location.range.to_point(&snapshot);
6344 let point_range = point_range.start.row..=point_range.end.row;
6345 if point_range.contains(&buffer_row) {
6346 Some(code_actions)
6347 } else {
6348 None
6349 }
6350 })
6351 })
6352 .ok()
6353 .flatten()
6354 })
6355 }
6356
6357 pub fn confirm_code_action(
6358 &mut self,
6359 action: &ConfirmCodeAction,
6360 window: &mut Window,
6361 cx: &mut Context<Self>,
6362 ) -> Option<Task<Result<()>>> {
6363 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6364
6365 let actions_menu =
6366 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6367 menu
6368 } else {
6369 return None;
6370 };
6371
6372 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6373 let action = actions_menu.actions.get(action_ix)?;
6374 let title = action.label();
6375 let buffer = actions_menu.buffer;
6376 let workspace = self.workspace()?;
6377
6378 match action {
6379 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6380 workspace.update(cx, |workspace, cx| {
6381 workspace.schedule_resolved_task(
6382 task_source_kind,
6383 resolved_task,
6384 false,
6385 window,
6386 cx,
6387 );
6388
6389 Some(Task::ready(Ok(())))
6390 })
6391 }
6392 CodeActionsItem::CodeAction {
6393 excerpt_id,
6394 action,
6395 provider,
6396 } => {
6397 let apply_code_action =
6398 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6399 let workspace = workspace.downgrade();
6400 Some(cx.spawn_in(window, async move |editor, cx| {
6401 let project_transaction = apply_code_action.await?;
6402 Self::open_project_transaction(
6403 &editor,
6404 workspace,
6405 project_transaction,
6406 title,
6407 cx,
6408 )
6409 .await
6410 }))
6411 }
6412 CodeActionsItem::DebugScenario(scenario) => {
6413 let context = actions_menu.actions.context;
6414
6415 workspace.update(cx, |workspace, cx| {
6416 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6417 workspace.start_debug_session(
6418 scenario,
6419 context,
6420 Some(buffer),
6421 None,
6422 window,
6423 cx,
6424 );
6425 });
6426 Some(Task::ready(Ok(())))
6427 }
6428 }
6429 }
6430
6431 pub async fn open_project_transaction(
6432 editor: &WeakEntity<Editor>,
6433 workspace: WeakEntity<Workspace>,
6434 transaction: ProjectTransaction,
6435 title: String,
6436 cx: &mut AsyncWindowContext,
6437 ) -> Result<()> {
6438 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6439 cx.update(|_, cx| {
6440 entries.sort_unstable_by_key(|(buffer, _)| {
6441 buffer.read(cx).file().map(|f| f.path().clone())
6442 });
6443 })?;
6444 if entries.is_empty() {
6445 return Ok(());
6446 }
6447
6448 // If the project transaction's edits are all contained within this editor, then
6449 // avoid opening a new editor to display them.
6450
6451 if let [(buffer, transaction)] = &*entries {
6452 let excerpt = editor.update(cx, |editor, cx| {
6453 editor
6454 .buffer()
6455 .read(cx)
6456 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6457 })?;
6458 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6459 && excerpted_buffer == *buffer
6460 {
6461 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6462 let excerpt_range = excerpt_range.to_offset(buffer);
6463 buffer
6464 .edited_ranges_for_transaction::<usize>(transaction)
6465 .all(|range| {
6466 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6467 })
6468 })?;
6469
6470 if all_edits_within_excerpt {
6471 return Ok(());
6472 }
6473 }
6474 }
6475
6476 let mut ranges_to_highlight = Vec::new();
6477 let excerpt_buffer = cx.new(|cx| {
6478 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6479 for (buffer_handle, transaction) in &entries {
6480 let edited_ranges = buffer_handle
6481 .read(cx)
6482 .edited_ranges_for_transaction::<Point>(transaction)
6483 .collect::<Vec<_>>();
6484 let (ranges, _) = multibuffer.set_excerpts_for_path(
6485 PathKey::for_buffer(buffer_handle, cx),
6486 buffer_handle.clone(),
6487 edited_ranges,
6488 multibuffer_context_lines(cx),
6489 cx,
6490 );
6491
6492 ranges_to_highlight.extend(ranges);
6493 }
6494 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6495 multibuffer
6496 })?;
6497
6498 workspace.update_in(cx, |workspace, window, cx| {
6499 let project = workspace.project().clone();
6500 let editor =
6501 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6502 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6503 editor.update(cx, |editor, cx| {
6504 editor.highlight_background::<Self>(
6505 &ranges_to_highlight,
6506 |theme| theme.colors().editor_highlighted_line_background,
6507 cx,
6508 );
6509 });
6510 })?;
6511
6512 Ok(())
6513 }
6514
6515 pub fn clear_code_action_providers(&mut self) {
6516 self.code_action_providers.clear();
6517 self.available_code_actions.take();
6518 }
6519
6520 pub fn add_code_action_provider(
6521 &mut self,
6522 provider: Rc<dyn CodeActionProvider>,
6523 window: &mut Window,
6524 cx: &mut Context<Self>,
6525 ) {
6526 if self
6527 .code_action_providers
6528 .iter()
6529 .any(|existing_provider| existing_provider.id() == provider.id())
6530 {
6531 return;
6532 }
6533
6534 self.code_action_providers.push(provider);
6535 self.refresh_code_actions(window, cx);
6536 }
6537
6538 pub fn remove_code_action_provider(
6539 &mut self,
6540 id: Arc<str>,
6541 window: &mut Window,
6542 cx: &mut Context<Self>,
6543 ) {
6544 self.code_action_providers
6545 .retain(|provider| provider.id() != id);
6546 self.refresh_code_actions(window, cx);
6547 }
6548
6549 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6550 !self.code_action_providers.is_empty()
6551 && EditorSettings::get_global(cx).toolbar.code_actions
6552 }
6553
6554 pub fn has_available_code_actions(&self) -> bool {
6555 self.available_code_actions
6556 .as_ref()
6557 .is_some_and(|(_, actions)| !actions.is_empty())
6558 }
6559
6560 fn render_inline_code_actions(
6561 &self,
6562 icon_size: ui::IconSize,
6563 display_row: DisplayRow,
6564 is_active: bool,
6565 cx: &mut Context<Self>,
6566 ) -> AnyElement {
6567 let show_tooltip = !self.context_menu_visible();
6568 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6569 .icon_size(icon_size)
6570 .shape(ui::IconButtonShape::Square)
6571 .icon_color(ui::Color::Hidden)
6572 .toggle_state(is_active)
6573 .when(show_tooltip, |this| {
6574 this.tooltip({
6575 let focus_handle = self.focus_handle.clone();
6576 move |window, cx| {
6577 Tooltip::for_action_in(
6578 "Toggle Code Actions",
6579 &ToggleCodeActions {
6580 deployed_from: None,
6581 quick_launch: false,
6582 },
6583 &focus_handle,
6584 window,
6585 cx,
6586 )
6587 }
6588 })
6589 })
6590 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6591 window.focus(&editor.focus_handle(cx));
6592 editor.toggle_code_actions(
6593 &crate::actions::ToggleCodeActions {
6594 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6595 display_row,
6596 )),
6597 quick_launch: false,
6598 },
6599 window,
6600 cx,
6601 );
6602 }))
6603 .into_any_element()
6604 }
6605
6606 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6607 &self.context_menu
6608 }
6609
6610 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6611 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6612 cx.background_executor()
6613 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6614 .await;
6615
6616 let (start_buffer, start, _, end, newest_selection) = this
6617 .update(cx, |this, cx| {
6618 let newest_selection = this.selections.newest_anchor().clone();
6619 if newest_selection.head().diff_base_anchor.is_some() {
6620 return None;
6621 }
6622 let newest_selection_adjusted = this.selections.newest_adjusted(cx);
6623 let buffer = this.buffer.read(cx);
6624
6625 let (start_buffer, start) =
6626 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6627 let (end_buffer, end) =
6628 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6629
6630 Some((start_buffer, start, end_buffer, end, newest_selection))
6631 })?
6632 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6633 .context(
6634 "Expected selection to lie in a single buffer when refreshing code actions",
6635 )?;
6636 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6637 let providers = this.code_action_providers.clone();
6638 let tasks = this
6639 .code_action_providers
6640 .iter()
6641 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6642 .collect::<Vec<_>>();
6643 (providers, tasks)
6644 })?;
6645
6646 let mut actions = Vec::new();
6647 for (provider, provider_actions) in
6648 providers.into_iter().zip(future::join_all(tasks).await)
6649 {
6650 if let Some(provider_actions) = provider_actions.log_err() {
6651 actions.extend(provider_actions.into_iter().map(|action| {
6652 AvailableCodeAction {
6653 excerpt_id: newest_selection.start.excerpt_id,
6654 action,
6655 provider: provider.clone(),
6656 }
6657 }));
6658 }
6659 }
6660
6661 this.update(cx, |this, cx| {
6662 this.available_code_actions = if actions.is_empty() {
6663 None
6664 } else {
6665 Some((
6666 Location {
6667 buffer: start_buffer,
6668 range: start..end,
6669 },
6670 actions.into(),
6671 ))
6672 };
6673 cx.notify();
6674 })
6675 }));
6676 }
6677
6678 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6679 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6680 self.show_git_blame_inline = false;
6681
6682 self.show_git_blame_inline_delay_task =
6683 Some(cx.spawn_in(window, async move |this, cx| {
6684 cx.background_executor().timer(delay).await;
6685
6686 this.update(cx, |this, cx| {
6687 this.show_git_blame_inline = true;
6688 cx.notify();
6689 })
6690 .log_err();
6691 }));
6692 }
6693 }
6694
6695 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6696 let snapshot = self.snapshot(window, cx);
6697 let cursor = self.selections.newest::<Point>(cx).head();
6698 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6699 else {
6700 return;
6701 };
6702
6703 let Some(blame) = self.blame.as_ref() else {
6704 return;
6705 };
6706
6707 let row_info = RowInfo {
6708 buffer_id: Some(buffer.remote_id()),
6709 buffer_row: Some(point.row),
6710 ..Default::default()
6711 };
6712 let Some((buffer, blame_entry)) = blame
6713 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6714 .flatten()
6715 else {
6716 return;
6717 };
6718
6719 let anchor = self.selections.newest_anchor().head();
6720 let position = self.to_pixel_point(anchor, &snapshot, window);
6721 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6722 self.show_blame_popover(
6723 buffer,
6724 &blame_entry,
6725 position + last_bounds.origin,
6726 true,
6727 cx,
6728 );
6729 };
6730 }
6731
6732 fn show_blame_popover(
6733 &mut self,
6734 buffer: BufferId,
6735 blame_entry: &BlameEntry,
6736 position: gpui::Point<Pixels>,
6737 ignore_timeout: bool,
6738 cx: &mut Context<Self>,
6739 ) {
6740 if let Some(state) = &mut self.inline_blame_popover {
6741 state.hide_task.take();
6742 } else {
6743 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6744 let blame_entry = blame_entry.clone();
6745 let show_task = cx.spawn(async move |editor, cx| {
6746 if !ignore_timeout {
6747 cx.background_executor()
6748 .timer(std::time::Duration::from_millis(blame_popover_delay))
6749 .await;
6750 }
6751 editor
6752 .update(cx, |editor, cx| {
6753 editor.inline_blame_popover_show_task.take();
6754 let Some(blame) = editor.blame.as_ref() else {
6755 return;
6756 };
6757 let blame = blame.read(cx);
6758 let details = blame.details_for_entry(buffer, &blame_entry);
6759 let markdown = cx.new(|cx| {
6760 Markdown::new(
6761 details
6762 .as_ref()
6763 .map(|message| message.message.clone())
6764 .unwrap_or_default(),
6765 None,
6766 None,
6767 cx,
6768 )
6769 });
6770 editor.inline_blame_popover = Some(InlineBlamePopover {
6771 position,
6772 hide_task: None,
6773 popover_bounds: None,
6774 popover_state: InlineBlamePopoverState {
6775 scroll_handle: ScrollHandle::new(),
6776 commit_message: details,
6777 markdown,
6778 },
6779 keyboard_grace: ignore_timeout,
6780 });
6781 cx.notify();
6782 })
6783 .ok();
6784 });
6785 self.inline_blame_popover_show_task = Some(show_task);
6786 }
6787 }
6788
6789 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6790 self.inline_blame_popover_show_task.take();
6791 if let Some(state) = &mut self.inline_blame_popover {
6792 let hide_task = cx.spawn(async move |editor, cx| {
6793 cx.background_executor()
6794 .timer(std::time::Duration::from_millis(100))
6795 .await;
6796 editor
6797 .update(cx, |editor, cx| {
6798 editor.inline_blame_popover.take();
6799 cx.notify();
6800 })
6801 .ok();
6802 });
6803 state.hide_task = Some(hide_task);
6804 }
6805 }
6806
6807 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6808 if self.pending_rename.is_some() {
6809 return None;
6810 }
6811
6812 let provider = self.semantics_provider.clone()?;
6813 let buffer = self.buffer.read(cx);
6814 let newest_selection = self.selections.newest_anchor().clone();
6815 let cursor_position = newest_selection.head();
6816 let (cursor_buffer, cursor_buffer_position) =
6817 buffer.text_anchor_for_position(cursor_position, cx)?;
6818 let (tail_buffer, tail_buffer_position) =
6819 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6820 if cursor_buffer != tail_buffer {
6821 return None;
6822 }
6823
6824 let snapshot = cursor_buffer.read(cx).snapshot();
6825 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6826 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6827 if start_word_range != end_word_range {
6828 self.document_highlights_task.take();
6829 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6830 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6831 return None;
6832 }
6833
6834 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6835 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6836 cx.background_executor()
6837 .timer(Duration::from_millis(debounce))
6838 .await;
6839
6840 let highlights = if let Some(highlights) = cx
6841 .update(|cx| {
6842 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6843 })
6844 .ok()
6845 .flatten()
6846 {
6847 highlights.await.log_err()
6848 } else {
6849 None
6850 };
6851
6852 if let Some(highlights) = highlights {
6853 this.update(cx, |this, cx| {
6854 if this.pending_rename.is_some() {
6855 return;
6856 }
6857
6858 let buffer = this.buffer.read(cx);
6859 if buffer
6860 .text_anchor_for_position(cursor_position, cx)
6861 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6862 {
6863 return;
6864 }
6865
6866 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6867 let mut write_ranges = Vec::new();
6868 let mut read_ranges = Vec::new();
6869 for highlight in highlights {
6870 let buffer_id = cursor_buffer.read(cx).remote_id();
6871 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6872 {
6873 let start = highlight
6874 .range
6875 .start
6876 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6877 let end = highlight
6878 .range
6879 .end
6880 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6881 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6882 continue;
6883 }
6884
6885 let range = Anchor::range_in_buffer(excerpt_id, buffer_id, start..end);
6886 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6887 write_ranges.push(range);
6888 } else {
6889 read_ranges.push(range);
6890 }
6891 }
6892 }
6893
6894 this.highlight_background::<DocumentHighlightRead>(
6895 &read_ranges,
6896 |theme| theme.colors().editor_document_highlight_read_background,
6897 cx,
6898 );
6899 this.highlight_background::<DocumentHighlightWrite>(
6900 &write_ranges,
6901 |theme| theme.colors().editor_document_highlight_write_background,
6902 cx,
6903 );
6904 cx.notify();
6905 })
6906 .log_err();
6907 }
6908 }));
6909 None
6910 }
6911
6912 fn prepare_highlight_query_from_selection(
6913 &mut self,
6914 cx: &mut Context<Editor>,
6915 ) -> Option<(String, Range<Anchor>)> {
6916 if matches!(self.mode, EditorMode::SingleLine) {
6917 return None;
6918 }
6919 if !EditorSettings::get_global(cx).selection_highlight {
6920 return None;
6921 }
6922 if self.selections.count() != 1 || self.selections.line_mode() {
6923 return None;
6924 }
6925 let selection = self.selections.newest_anchor();
6926 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6927 let selection_point_range = selection.start.to_point(&multi_buffer_snapshot)
6928 ..selection.end.to_point(&multi_buffer_snapshot);
6929 // If the selection spans multiple rows OR it is empty
6930 if selection_point_range.start.row != selection_point_range.end.row
6931 || selection_point_range.start.column == selection_point_range.end.column
6932 {
6933 return None;
6934 }
6935
6936 let query = multi_buffer_snapshot
6937 .text_for_range(selection.range())
6938 .collect::<String>();
6939 if query.trim().is_empty() {
6940 return None;
6941 }
6942 Some((query, selection.range()))
6943 }
6944
6945 fn update_selection_occurrence_highlights(
6946 &mut self,
6947 query_text: String,
6948 query_range: Range<Anchor>,
6949 multi_buffer_range_to_query: Range<Point>,
6950 use_debounce: bool,
6951 window: &mut Window,
6952 cx: &mut Context<Editor>,
6953 ) -> Task<()> {
6954 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6955 cx.spawn_in(window, async move |editor, cx| {
6956 if use_debounce {
6957 cx.background_executor()
6958 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6959 .await;
6960 }
6961 let match_task = cx.background_spawn(async move {
6962 let buffer_ranges = multi_buffer_snapshot
6963 .range_to_buffer_ranges(multi_buffer_range_to_query)
6964 .into_iter()
6965 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6966 let mut match_ranges = Vec::new();
6967 let Ok(regex) = project::search::SearchQuery::text(
6968 query_text.clone(),
6969 false,
6970 false,
6971 false,
6972 Default::default(),
6973 Default::default(),
6974 false,
6975 None,
6976 ) else {
6977 return Vec::default();
6978 };
6979 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6980 match_ranges.extend(
6981 regex
6982 .search(buffer_snapshot, Some(search_range.clone()))
6983 .await
6984 .into_iter()
6985 .filter_map(|match_range| {
6986 let match_start = buffer_snapshot
6987 .anchor_after(search_range.start + match_range.start);
6988 let match_end = buffer_snapshot
6989 .anchor_before(search_range.start + match_range.end);
6990 let match_anchor_range = Anchor::range_in_buffer(
6991 excerpt_id,
6992 buffer_snapshot.remote_id(),
6993 match_start..match_end,
6994 );
6995 (match_anchor_range != query_range).then_some(match_anchor_range)
6996 }),
6997 );
6998 }
6999 match_ranges
7000 });
7001 let match_ranges = match_task.await;
7002 editor
7003 .update_in(cx, |editor, _, cx| {
7004 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7005 if !match_ranges.is_empty() {
7006 editor.highlight_background::<SelectedTextHighlight>(
7007 &match_ranges,
7008 |theme| theme.colors().editor_document_highlight_bracket_background,
7009 cx,
7010 )
7011 }
7012 })
7013 .log_err();
7014 })
7015 }
7016
7017 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7018 struct NewlineFold;
7019 let type_id = std::any::TypeId::of::<NewlineFold>();
7020 if !self.mode.is_single_line() {
7021 return;
7022 }
7023 let snapshot = self.snapshot(window, cx);
7024 if snapshot.buffer_snapshot().max_point().row == 0 {
7025 return;
7026 }
7027 let task = cx.background_spawn(async move {
7028 let new_newlines = snapshot
7029 .buffer_chars_at(0)
7030 .filter_map(|(c, i)| {
7031 if c == '\n' {
7032 Some(
7033 snapshot.buffer_snapshot().anchor_after(i)
7034 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7035 )
7036 } else {
7037 None
7038 }
7039 })
7040 .collect::<Vec<_>>();
7041 let existing_newlines = snapshot
7042 .folds_in_range(0..snapshot.buffer_snapshot().len())
7043 .filter_map(|fold| {
7044 if fold.placeholder.type_tag == Some(type_id) {
7045 Some(fold.range.start..fold.range.end)
7046 } else {
7047 None
7048 }
7049 })
7050 .collect::<Vec<_>>();
7051
7052 (new_newlines, existing_newlines)
7053 });
7054 self.folding_newlines = cx.spawn(async move |this, cx| {
7055 let (new_newlines, existing_newlines) = task.await;
7056 if new_newlines == existing_newlines {
7057 return;
7058 }
7059 let placeholder = FoldPlaceholder {
7060 render: Arc::new(move |_, _, cx| {
7061 div()
7062 .bg(cx.theme().status().hint_background)
7063 .border_b_1()
7064 .size_full()
7065 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7066 .border_color(cx.theme().status().hint)
7067 .child("\\n")
7068 .into_any()
7069 }),
7070 constrain_width: false,
7071 merge_adjacent: false,
7072 type_tag: Some(type_id),
7073 };
7074 let creases = new_newlines
7075 .into_iter()
7076 .map(|range| Crease::simple(range, placeholder.clone()))
7077 .collect();
7078 this.update(cx, |this, cx| {
7079 this.display_map.update(cx, |display_map, cx| {
7080 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7081 display_map.fold(creases, cx);
7082 });
7083 })
7084 .ok();
7085 });
7086 }
7087
7088 fn refresh_selected_text_highlights(
7089 &mut self,
7090 on_buffer_edit: bool,
7091 window: &mut Window,
7092 cx: &mut Context<Editor>,
7093 ) {
7094 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7095 else {
7096 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7097 self.quick_selection_highlight_task.take();
7098 self.debounced_selection_highlight_task.take();
7099 return;
7100 };
7101 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7102 if on_buffer_edit
7103 || self
7104 .quick_selection_highlight_task
7105 .as_ref()
7106 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7107 {
7108 let multi_buffer_visible_start = self
7109 .scroll_manager
7110 .anchor()
7111 .anchor
7112 .to_point(&multi_buffer_snapshot);
7113 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7114 multi_buffer_visible_start
7115 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7116 Bias::Left,
7117 );
7118 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7119 self.quick_selection_highlight_task = Some((
7120 query_range.clone(),
7121 self.update_selection_occurrence_highlights(
7122 query_text.clone(),
7123 query_range.clone(),
7124 multi_buffer_visible_range,
7125 false,
7126 window,
7127 cx,
7128 ),
7129 ));
7130 }
7131 if on_buffer_edit
7132 || self
7133 .debounced_selection_highlight_task
7134 .as_ref()
7135 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7136 {
7137 let multi_buffer_start = multi_buffer_snapshot
7138 .anchor_before(0)
7139 .to_point(&multi_buffer_snapshot);
7140 let multi_buffer_end = multi_buffer_snapshot
7141 .anchor_after(multi_buffer_snapshot.len())
7142 .to_point(&multi_buffer_snapshot);
7143 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7144 self.debounced_selection_highlight_task = Some((
7145 query_range.clone(),
7146 self.update_selection_occurrence_highlights(
7147 query_text,
7148 query_range,
7149 multi_buffer_full_range,
7150 true,
7151 window,
7152 cx,
7153 ),
7154 ));
7155 }
7156 }
7157
7158 pub fn refresh_edit_prediction(
7159 &mut self,
7160 debounce: bool,
7161 user_requested: bool,
7162 window: &mut Window,
7163 cx: &mut Context<Self>,
7164 ) -> Option<()> {
7165 if DisableAiSettings::get_global(cx).disable_ai {
7166 return None;
7167 }
7168
7169 let provider = self.edit_prediction_provider()?;
7170 let cursor = self.selections.newest_anchor().head();
7171 let (buffer, cursor_buffer_position) =
7172 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7173
7174 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7175 self.discard_edit_prediction(false, cx);
7176 return None;
7177 }
7178
7179 self.update_visible_edit_prediction(window, cx);
7180
7181 if !user_requested
7182 && (!self.should_show_edit_predictions()
7183 || !self.is_focused(window)
7184 || buffer.read(cx).is_empty())
7185 {
7186 self.discard_edit_prediction(false, cx);
7187 return None;
7188 }
7189
7190 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7191 Some(())
7192 }
7193
7194 fn show_edit_predictions_in_menu(&self) -> bool {
7195 match self.edit_prediction_settings {
7196 EditPredictionSettings::Disabled => false,
7197 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7198 }
7199 }
7200
7201 pub fn edit_predictions_enabled(&self) -> bool {
7202 match self.edit_prediction_settings {
7203 EditPredictionSettings::Disabled => false,
7204 EditPredictionSettings::Enabled { .. } => true,
7205 }
7206 }
7207
7208 fn edit_prediction_requires_modifier(&self) -> bool {
7209 match self.edit_prediction_settings {
7210 EditPredictionSettings::Disabled => false,
7211 EditPredictionSettings::Enabled {
7212 preview_requires_modifier,
7213 ..
7214 } => preview_requires_modifier,
7215 }
7216 }
7217
7218 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7219 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7220 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7221 self.discard_edit_prediction(false, cx);
7222 } else {
7223 let selection = self.selections.newest_anchor();
7224 let cursor = selection.head();
7225
7226 if let Some((buffer, cursor_buffer_position)) =
7227 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7228 {
7229 self.edit_prediction_settings =
7230 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7231 }
7232 }
7233 }
7234
7235 fn edit_prediction_settings_at_position(
7236 &self,
7237 buffer: &Entity<Buffer>,
7238 buffer_position: language::Anchor,
7239 cx: &App,
7240 ) -> EditPredictionSettings {
7241 if !self.mode.is_full()
7242 || !self.show_edit_predictions_override.unwrap_or(true)
7243 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7244 {
7245 return EditPredictionSettings::Disabled;
7246 }
7247
7248 let buffer = buffer.read(cx);
7249
7250 let file = buffer.file();
7251
7252 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7253 return EditPredictionSettings::Disabled;
7254 };
7255
7256 let by_provider = matches!(
7257 self.menu_edit_predictions_policy,
7258 MenuEditPredictionsPolicy::ByProvider
7259 );
7260
7261 let show_in_menu = by_provider
7262 && self
7263 .edit_prediction_provider
7264 .as_ref()
7265 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7266
7267 let preview_requires_modifier =
7268 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7269
7270 EditPredictionSettings::Enabled {
7271 show_in_menu,
7272 preview_requires_modifier,
7273 }
7274 }
7275
7276 fn should_show_edit_predictions(&self) -> bool {
7277 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7278 }
7279
7280 pub fn edit_prediction_preview_is_active(&self) -> bool {
7281 matches!(
7282 self.edit_prediction_preview,
7283 EditPredictionPreview::Active { .. }
7284 )
7285 }
7286
7287 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7288 let cursor = self.selections.newest_anchor().head();
7289 if let Some((buffer, cursor_position)) =
7290 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7291 {
7292 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7293 } else {
7294 false
7295 }
7296 }
7297
7298 pub fn supports_minimap(&self, cx: &App) -> bool {
7299 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7300 }
7301
7302 fn edit_predictions_enabled_in_buffer(
7303 &self,
7304 buffer: &Entity<Buffer>,
7305 buffer_position: language::Anchor,
7306 cx: &App,
7307 ) -> bool {
7308 maybe!({
7309 if self.read_only(cx) {
7310 return Some(false);
7311 }
7312 let provider = self.edit_prediction_provider()?;
7313 if !provider.is_enabled(buffer, buffer_position, cx) {
7314 return Some(false);
7315 }
7316 let buffer = buffer.read(cx);
7317 let Some(file) = buffer.file() else {
7318 return Some(true);
7319 };
7320 let settings = all_language_settings(Some(file), cx);
7321 Some(settings.edit_predictions_enabled_for_file(file, cx))
7322 })
7323 .unwrap_or(false)
7324 }
7325
7326 fn cycle_edit_prediction(
7327 &mut self,
7328 direction: Direction,
7329 window: &mut Window,
7330 cx: &mut Context<Self>,
7331 ) -> Option<()> {
7332 let provider = self.edit_prediction_provider()?;
7333 let cursor = self.selections.newest_anchor().head();
7334 let (buffer, cursor_buffer_position) =
7335 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7336 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7337 return None;
7338 }
7339
7340 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7341 self.update_visible_edit_prediction(window, cx);
7342
7343 Some(())
7344 }
7345
7346 pub fn show_edit_prediction(
7347 &mut self,
7348 _: &ShowEditPrediction,
7349 window: &mut Window,
7350 cx: &mut Context<Self>,
7351 ) {
7352 if !self.has_active_edit_prediction() {
7353 self.refresh_edit_prediction(false, true, window, cx);
7354 return;
7355 }
7356
7357 self.update_visible_edit_prediction(window, cx);
7358 }
7359
7360 pub fn display_cursor_names(
7361 &mut self,
7362 _: &DisplayCursorNames,
7363 window: &mut Window,
7364 cx: &mut Context<Self>,
7365 ) {
7366 self.show_cursor_names(window, cx);
7367 }
7368
7369 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7370 self.show_cursor_names = true;
7371 cx.notify();
7372 cx.spawn_in(window, async move |this, cx| {
7373 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7374 this.update(cx, |this, cx| {
7375 this.show_cursor_names = false;
7376 cx.notify()
7377 })
7378 .ok()
7379 })
7380 .detach();
7381 }
7382
7383 pub fn next_edit_prediction(
7384 &mut self,
7385 _: &NextEditPrediction,
7386 window: &mut Window,
7387 cx: &mut Context<Self>,
7388 ) {
7389 if self.has_active_edit_prediction() {
7390 self.cycle_edit_prediction(Direction::Next, window, cx);
7391 } else {
7392 let is_copilot_disabled = self
7393 .refresh_edit_prediction(false, true, window, cx)
7394 .is_none();
7395 if is_copilot_disabled {
7396 cx.propagate();
7397 }
7398 }
7399 }
7400
7401 pub fn previous_edit_prediction(
7402 &mut self,
7403 _: &PreviousEditPrediction,
7404 window: &mut Window,
7405 cx: &mut Context<Self>,
7406 ) {
7407 if self.has_active_edit_prediction() {
7408 self.cycle_edit_prediction(Direction::Prev, window, cx);
7409 } else {
7410 let is_copilot_disabled = self
7411 .refresh_edit_prediction(false, true, window, cx)
7412 .is_none();
7413 if is_copilot_disabled {
7414 cx.propagate();
7415 }
7416 }
7417 }
7418
7419 pub fn accept_edit_prediction(
7420 &mut self,
7421 _: &AcceptEditPrediction,
7422 window: &mut Window,
7423 cx: &mut Context<Self>,
7424 ) {
7425 if self.show_edit_predictions_in_menu() {
7426 self.hide_context_menu(window, cx);
7427 }
7428
7429 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7430 return;
7431 };
7432
7433 match &active_edit_prediction.completion {
7434 EditPrediction::MoveWithin { target, .. } => {
7435 let target = *target;
7436
7437 if let Some(position_map) = &self.last_position_map {
7438 if position_map
7439 .visible_row_range
7440 .contains(&target.to_display_point(&position_map.snapshot).row())
7441 || !self.edit_prediction_requires_modifier()
7442 {
7443 self.unfold_ranges(&[target..target], true, false, cx);
7444 // Note that this is also done in vim's handler of the Tab action.
7445 self.change_selections(
7446 SelectionEffects::scroll(Autoscroll::newest()),
7447 window,
7448 cx,
7449 |selections| {
7450 selections.select_anchor_ranges([target..target]);
7451 },
7452 );
7453 self.clear_row_highlights::<EditPredictionPreview>();
7454
7455 self.edit_prediction_preview
7456 .set_previous_scroll_position(None);
7457 } else {
7458 self.edit_prediction_preview
7459 .set_previous_scroll_position(Some(
7460 position_map.snapshot.scroll_anchor,
7461 ));
7462
7463 self.highlight_rows::<EditPredictionPreview>(
7464 target..target,
7465 cx.theme().colors().editor_highlighted_line_background,
7466 RowHighlightOptions {
7467 autoscroll: true,
7468 ..Default::default()
7469 },
7470 cx,
7471 );
7472 self.request_autoscroll(Autoscroll::fit(), cx);
7473 }
7474 }
7475 }
7476 EditPrediction::MoveOutside { snapshot, target } => {
7477 if let Some(workspace) = self.workspace() {
7478 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7479 .detach_and_log_err(cx);
7480 }
7481 }
7482 EditPrediction::Edit { edits, .. } => {
7483 self.report_edit_prediction_event(
7484 active_edit_prediction.completion_id.clone(),
7485 true,
7486 cx,
7487 );
7488
7489 if let Some(provider) = self.edit_prediction_provider() {
7490 provider.accept(cx);
7491 }
7492
7493 // Store the transaction ID and selections before applying the edit
7494 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7495
7496 let snapshot = self.buffer.read(cx).snapshot(cx);
7497 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7498
7499 self.buffer.update(cx, |buffer, cx| {
7500 buffer.edit(edits.iter().cloned(), None, cx)
7501 });
7502
7503 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7504 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7505 });
7506
7507 let selections = self.selections.disjoint_anchors_arc();
7508 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7509 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7510 if has_new_transaction {
7511 self.selection_history
7512 .insert_transaction(transaction_id_now, selections);
7513 }
7514 }
7515
7516 self.update_visible_edit_prediction(window, cx);
7517 if self.active_edit_prediction.is_none() {
7518 self.refresh_edit_prediction(true, true, window, cx);
7519 }
7520
7521 cx.notify();
7522 }
7523 }
7524
7525 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7526 }
7527
7528 pub fn accept_partial_edit_prediction(
7529 &mut self,
7530 _: &AcceptPartialEditPrediction,
7531 window: &mut Window,
7532 cx: &mut Context<Self>,
7533 ) {
7534 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7535 return;
7536 };
7537 if self.selections.count() != 1 {
7538 return;
7539 }
7540
7541 match &active_edit_prediction.completion {
7542 EditPrediction::MoveWithin { target, .. } => {
7543 let target = *target;
7544 self.change_selections(
7545 SelectionEffects::scroll(Autoscroll::newest()),
7546 window,
7547 cx,
7548 |selections| {
7549 selections.select_anchor_ranges([target..target]);
7550 },
7551 );
7552 }
7553 EditPrediction::MoveOutside { snapshot, target } => {
7554 if let Some(workspace) = self.workspace() {
7555 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7556 .detach_and_log_err(cx);
7557 }
7558 }
7559 EditPrediction::Edit { edits, .. } => {
7560 self.report_edit_prediction_event(
7561 active_edit_prediction.completion_id.clone(),
7562 true,
7563 cx,
7564 );
7565
7566 // Find an insertion that starts at the cursor position.
7567 let snapshot = self.buffer.read(cx).snapshot(cx);
7568 let cursor_offset = self.selections.newest::<usize>(cx).head();
7569 let insertion = edits.iter().find_map(|(range, text)| {
7570 let range = range.to_offset(&snapshot);
7571 if range.is_empty() && range.start == cursor_offset {
7572 Some(text)
7573 } else {
7574 None
7575 }
7576 });
7577
7578 if let Some(text) = insertion {
7579 let mut partial_completion = text
7580 .chars()
7581 .by_ref()
7582 .take_while(|c| c.is_alphabetic())
7583 .collect::<String>();
7584 if partial_completion.is_empty() {
7585 partial_completion = text
7586 .chars()
7587 .by_ref()
7588 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7589 .collect::<String>();
7590 }
7591
7592 cx.emit(EditorEvent::InputHandled {
7593 utf16_range_to_replace: None,
7594 text: partial_completion.clone().into(),
7595 });
7596
7597 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7598
7599 self.refresh_edit_prediction(true, true, window, cx);
7600 cx.notify();
7601 } else {
7602 self.accept_edit_prediction(&Default::default(), window, cx);
7603 }
7604 }
7605 }
7606 }
7607
7608 fn discard_edit_prediction(
7609 &mut self,
7610 should_report_edit_prediction_event: bool,
7611 cx: &mut Context<Self>,
7612 ) -> bool {
7613 if should_report_edit_prediction_event {
7614 let completion_id = self
7615 .active_edit_prediction
7616 .as_ref()
7617 .and_then(|active_completion| active_completion.completion_id.clone());
7618
7619 self.report_edit_prediction_event(completion_id, false, cx);
7620 }
7621
7622 if let Some(provider) = self.edit_prediction_provider() {
7623 provider.discard(cx);
7624 }
7625
7626 self.take_active_edit_prediction(cx)
7627 }
7628
7629 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7630 let Some(provider) = self.edit_prediction_provider() else {
7631 return;
7632 };
7633
7634 let Some((_, buffer, _)) = self
7635 .buffer
7636 .read(cx)
7637 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7638 else {
7639 return;
7640 };
7641
7642 let extension = buffer
7643 .read(cx)
7644 .file()
7645 .and_then(|file| Some(file.path().extension()?.to_string()));
7646
7647 let event_type = match accepted {
7648 true => "Edit Prediction Accepted",
7649 false => "Edit Prediction Discarded",
7650 };
7651 telemetry::event!(
7652 event_type,
7653 provider = provider.name(),
7654 prediction_id = id,
7655 suggestion_accepted = accepted,
7656 file_extension = extension,
7657 );
7658 }
7659
7660 fn open_editor_at_anchor(
7661 snapshot: &language::BufferSnapshot,
7662 target: language::Anchor,
7663 workspace: &Entity<Workspace>,
7664 window: &mut Window,
7665 cx: &mut App,
7666 ) -> Task<Result<()>> {
7667 workspace.update(cx, |workspace, cx| {
7668 let path = snapshot.file().map(|file| file.full_path(cx));
7669 let Some(path) =
7670 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7671 else {
7672 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7673 };
7674 let target = text::ToPoint::to_point(&target, snapshot);
7675 let item = workspace.open_path(path, None, true, window, cx);
7676 window.spawn(cx, async move |cx| {
7677 let Some(editor) = item.await?.downcast::<Editor>() else {
7678 return Ok(());
7679 };
7680 editor
7681 .update_in(cx, |editor, window, cx| {
7682 editor.go_to_singleton_buffer_point(target, window, cx);
7683 })
7684 .ok();
7685 anyhow::Ok(())
7686 })
7687 })
7688 }
7689
7690 pub fn has_active_edit_prediction(&self) -> bool {
7691 self.active_edit_prediction.is_some()
7692 }
7693
7694 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7695 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7696 return false;
7697 };
7698
7699 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7700 self.clear_highlights::<EditPredictionHighlight>(cx);
7701 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7702 true
7703 }
7704
7705 /// Returns true when we're displaying the edit prediction popover below the cursor
7706 /// like we are not previewing and the LSP autocomplete menu is visible
7707 /// or we are in `when_holding_modifier` mode.
7708 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7709 if self.edit_prediction_preview_is_active()
7710 || !self.show_edit_predictions_in_menu()
7711 || !self.edit_predictions_enabled()
7712 {
7713 return false;
7714 }
7715
7716 if self.has_visible_completions_menu() {
7717 return true;
7718 }
7719
7720 has_completion && self.edit_prediction_requires_modifier()
7721 }
7722
7723 fn handle_modifiers_changed(
7724 &mut self,
7725 modifiers: Modifiers,
7726 position_map: &PositionMap,
7727 window: &mut Window,
7728 cx: &mut Context<Self>,
7729 ) {
7730 if self.show_edit_predictions_in_menu() {
7731 self.update_edit_prediction_preview(&modifiers, window, cx);
7732 }
7733
7734 self.update_selection_mode(&modifiers, position_map, window, cx);
7735
7736 let mouse_position = window.mouse_position();
7737 if !position_map.text_hitbox.is_hovered(window) {
7738 return;
7739 }
7740
7741 self.update_hovered_link(
7742 position_map.point_for_position(mouse_position),
7743 &position_map.snapshot,
7744 modifiers,
7745 window,
7746 cx,
7747 )
7748 }
7749
7750 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7751 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7752 if invert {
7753 match multi_cursor_setting {
7754 MultiCursorModifier::Alt => modifiers.alt,
7755 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7756 }
7757 } else {
7758 match multi_cursor_setting {
7759 MultiCursorModifier::Alt => modifiers.secondary(),
7760 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7761 }
7762 }
7763 }
7764
7765 fn columnar_selection_mode(
7766 modifiers: &Modifiers,
7767 cx: &mut Context<Self>,
7768 ) -> Option<ColumnarMode> {
7769 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7770 if Self::multi_cursor_modifier(false, modifiers, cx) {
7771 Some(ColumnarMode::FromMouse)
7772 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7773 Some(ColumnarMode::FromSelection)
7774 } else {
7775 None
7776 }
7777 } else {
7778 None
7779 }
7780 }
7781
7782 fn update_selection_mode(
7783 &mut self,
7784 modifiers: &Modifiers,
7785 position_map: &PositionMap,
7786 window: &mut Window,
7787 cx: &mut Context<Self>,
7788 ) {
7789 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7790 return;
7791 };
7792 if self.selections.pending_anchor().is_none() {
7793 return;
7794 }
7795
7796 let mouse_position = window.mouse_position();
7797 let point_for_position = position_map.point_for_position(mouse_position);
7798 let position = point_for_position.previous_valid;
7799
7800 self.select(
7801 SelectPhase::BeginColumnar {
7802 position,
7803 reset: false,
7804 mode,
7805 goal_column: point_for_position.exact_unclipped.column(),
7806 },
7807 window,
7808 cx,
7809 );
7810 }
7811
7812 fn update_edit_prediction_preview(
7813 &mut self,
7814 modifiers: &Modifiers,
7815 window: &mut Window,
7816 cx: &mut Context<Self>,
7817 ) {
7818 let mut modifiers_held = false;
7819 if let Some(accept_keystroke) = self
7820 .accept_edit_prediction_keybind(false, window, cx)
7821 .keystroke()
7822 {
7823 modifiers_held = modifiers_held
7824 || (accept_keystroke.modifiers() == modifiers
7825 && accept_keystroke.modifiers().modified());
7826 };
7827 if let Some(accept_partial_keystroke) = self
7828 .accept_edit_prediction_keybind(true, window, cx)
7829 .keystroke()
7830 {
7831 modifiers_held = modifiers_held
7832 || (accept_partial_keystroke.modifiers() == modifiers
7833 && accept_partial_keystroke.modifiers().modified());
7834 }
7835
7836 if modifiers_held {
7837 if matches!(
7838 self.edit_prediction_preview,
7839 EditPredictionPreview::Inactive { .. }
7840 ) {
7841 self.edit_prediction_preview = EditPredictionPreview::Active {
7842 previous_scroll_position: None,
7843 since: Instant::now(),
7844 };
7845
7846 self.update_visible_edit_prediction(window, cx);
7847 cx.notify();
7848 }
7849 } else if let EditPredictionPreview::Active {
7850 previous_scroll_position,
7851 since,
7852 } = self.edit_prediction_preview
7853 {
7854 if let (Some(previous_scroll_position), Some(position_map)) =
7855 (previous_scroll_position, self.last_position_map.as_ref())
7856 {
7857 self.set_scroll_position(
7858 previous_scroll_position
7859 .scroll_position(&position_map.snapshot.display_snapshot),
7860 window,
7861 cx,
7862 );
7863 }
7864
7865 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7866 released_too_fast: since.elapsed() < Duration::from_millis(200),
7867 };
7868 self.clear_row_highlights::<EditPredictionPreview>();
7869 self.update_visible_edit_prediction(window, cx);
7870 cx.notify();
7871 }
7872 }
7873
7874 fn update_visible_edit_prediction(
7875 &mut self,
7876 _window: &mut Window,
7877 cx: &mut Context<Self>,
7878 ) -> Option<()> {
7879 if DisableAiSettings::get_global(cx).disable_ai {
7880 return None;
7881 }
7882
7883 if self.ime_transaction.is_some() {
7884 self.discard_edit_prediction(false, cx);
7885 return None;
7886 }
7887
7888 let selection = self.selections.newest_anchor();
7889 let cursor = selection.head();
7890 let multibuffer = self.buffer.read(cx).snapshot(cx);
7891 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7892 let excerpt_id = cursor.excerpt_id;
7893
7894 let show_in_menu = self.show_edit_predictions_in_menu();
7895 let completions_menu_has_precedence = !show_in_menu
7896 && (self.context_menu.borrow().is_some()
7897 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7898
7899 if completions_menu_has_precedence
7900 || !offset_selection.is_empty()
7901 || self
7902 .active_edit_prediction
7903 .as_ref()
7904 .is_some_and(|completion| {
7905 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7906 return false;
7907 };
7908 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7909 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7910 !invalidation_range.contains(&offset_selection.head())
7911 })
7912 {
7913 self.discard_edit_prediction(false, cx);
7914 return None;
7915 }
7916
7917 self.take_active_edit_prediction(cx);
7918 let Some(provider) = self.edit_prediction_provider() else {
7919 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7920 return None;
7921 };
7922
7923 let (buffer, cursor_buffer_position) =
7924 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7925
7926 self.edit_prediction_settings =
7927 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7928
7929 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7930
7931 if self.edit_prediction_indent_conflict {
7932 let cursor_point = cursor.to_point(&multibuffer);
7933
7934 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7935
7936 if let Some((_, indent)) = indents.iter().next()
7937 && indent.len == cursor_point.column
7938 {
7939 self.edit_prediction_indent_conflict = false;
7940 }
7941 }
7942
7943 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7944
7945 let (completion_id, edits, edit_preview) = match edit_prediction {
7946 edit_prediction::EditPrediction::Local {
7947 id,
7948 edits,
7949 edit_preview,
7950 } => (id, edits, edit_preview),
7951 edit_prediction::EditPrediction::Jump {
7952 id,
7953 snapshot,
7954 target,
7955 } => {
7956 self.stale_edit_prediction_in_menu = None;
7957 self.active_edit_prediction = Some(EditPredictionState {
7958 inlay_ids: vec![],
7959 completion: EditPrediction::MoveOutside { snapshot, target },
7960 completion_id: id,
7961 invalidation_range: None,
7962 });
7963 cx.notify();
7964 return Some(());
7965 }
7966 };
7967
7968 let edits = edits
7969 .into_iter()
7970 .flat_map(|(range, new_text)| {
7971 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7972 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7973 Some((start..end, new_text))
7974 })
7975 .collect::<Vec<_>>();
7976 if edits.is_empty() {
7977 return None;
7978 }
7979
7980 let first_edit_start = edits.first().unwrap().0.start;
7981 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7982 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7983
7984 let last_edit_end = edits.last().unwrap().0.end;
7985 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7986 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7987
7988 let cursor_row = cursor.to_point(&multibuffer).row;
7989
7990 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7991
7992 let mut inlay_ids = Vec::new();
7993 let invalidation_row_range;
7994 let move_invalidation_row_range = if cursor_row < edit_start_row {
7995 Some(cursor_row..edit_end_row)
7996 } else if cursor_row > edit_end_row {
7997 Some(edit_start_row..cursor_row)
7998 } else {
7999 None
8000 };
8001 let supports_jump = self
8002 .edit_prediction_provider
8003 .as_ref()
8004 .map(|provider| provider.provider.supports_jump_to_edit())
8005 .unwrap_or(true);
8006
8007 let is_move = supports_jump
8008 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8009 let completion = if is_move {
8010 invalidation_row_range =
8011 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8012 let target = first_edit_start;
8013 EditPrediction::MoveWithin { target, snapshot }
8014 } else {
8015 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8016 && !self.edit_predictions_hidden_for_vim_mode;
8017
8018 if show_completions_in_buffer {
8019 if edits
8020 .iter()
8021 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8022 {
8023 let mut inlays = Vec::new();
8024 for (range, new_text) in &edits {
8025 let inlay = Inlay::edit_prediction(
8026 post_inc(&mut self.next_inlay_id),
8027 range.start,
8028 new_text.as_str(),
8029 );
8030 inlay_ids.push(inlay.id);
8031 inlays.push(inlay);
8032 }
8033
8034 self.splice_inlays(&[], inlays, cx);
8035 } else {
8036 let background_color = cx.theme().status().deleted_background;
8037 self.highlight_text::<EditPredictionHighlight>(
8038 edits.iter().map(|(range, _)| range.clone()).collect(),
8039 HighlightStyle {
8040 background_color: Some(background_color),
8041 ..Default::default()
8042 },
8043 cx,
8044 );
8045 }
8046 }
8047
8048 invalidation_row_range = edit_start_row..edit_end_row;
8049
8050 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8051 if provider.show_tab_accept_marker() {
8052 EditDisplayMode::TabAccept
8053 } else {
8054 EditDisplayMode::Inline
8055 }
8056 } else {
8057 EditDisplayMode::DiffPopover
8058 };
8059
8060 EditPrediction::Edit {
8061 edits,
8062 edit_preview,
8063 display_mode,
8064 snapshot,
8065 }
8066 };
8067
8068 let invalidation_range = multibuffer
8069 .anchor_before(Point::new(invalidation_row_range.start, 0))
8070 ..multibuffer.anchor_after(Point::new(
8071 invalidation_row_range.end,
8072 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8073 ));
8074
8075 self.stale_edit_prediction_in_menu = None;
8076 self.active_edit_prediction = Some(EditPredictionState {
8077 inlay_ids,
8078 completion,
8079 completion_id,
8080 invalidation_range: Some(invalidation_range),
8081 });
8082
8083 cx.notify();
8084
8085 Some(())
8086 }
8087
8088 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8089 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8090 }
8091
8092 fn clear_tasks(&mut self) {
8093 self.tasks.clear()
8094 }
8095
8096 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8097 if self.tasks.insert(key, value).is_some() {
8098 // This case should hopefully be rare, but just in case...
8099 log::error!(
8100 "multiple different run targets found on a single line, only the last target will be rendered"
8101 )
8102 }
8103 }
8104
8105 /// Get all display points of breakpoints that will be rendered within editor
8106 ///
8107 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8108 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8109 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8110 fn active_breakpoints(
8111 &self,
8112 range: Range<DisplayRow>,
8113 window: &mut Window,
8114 cx: &mut Context<Self>,
8115 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8116 let mut breakpoint_display_points = HashMap::default();
8117
8118 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8119 return breakpoint_display_points;
8120 };
8121
8122 let snapshot = self.snapshot(window, cx);
8123
8124 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
8125 let Some(project) = self.project() else {
8126 return breakpoint_display_points;
8127 };
8128
8129 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8130 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8131
8132 for (buffer_snapshot, range, excerpt_id) in
8133 multi_buffer_snapshot.range_to_buffer_ranges(range)
8134 {
8135 let Some(buffer) = project
8136 .read(cx)
8137 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8138 else {
8139 continue;
8140 };
8141 let breakpoints = breakpoint_store.read(cx).breakpoints(
8142 &buffer,
8143 Some(
8144 buffer_snapshot.anchor_before(range.start)
8145 ..buffer_snapshot.anchor_after(range.end),
8146 ),
8147 buffer_snapshot,
8148 cx,
8149 );
8150 for (breakpoint, state) in breakpoints {
8151 let multi_buffer_anchor =
8152 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8153 let position = multi_buffer_anchor
8154 .to_point(multi_buffer_snapshot)
8155 .to_display_point(&snapshot);
8156
8157 breakpoint_display_points.insert(
8158 position.row(),
8159 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8160 );
8161 }
8162 }
8163
8164 breakpoint_display_points
8165 }
8166
8167 fn breakpoint_context_menu(
8168 &self,
8169 anchor: Anchor,
8170 window: &mut Window,
8171 cx: &mut Context<Self>,
8172 ) -> Entity<ui::ContextMenu> {
8173 let weak_editor = cx.weak_entity();
8174 let focus_handle = self.focus_handle(cx);
8175
8176 let row = self
8177 .buffer
8178 .read(cx)
8179 .snapshot(cx)
8180 .summary_for_anchor::<Point>(&anchor)
8181 .row;
8182
8183 let breakpoint = self
8184 .breakpoint_at_row(row, window, cx)
8185 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8186
8187 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8188 "Edit Log Breakpoint"
8189 } else {
8190 "Set Log Breakpoint"
8191 };
8192
8193 let condition_breakpoint_msg = if breakpoint
8194 .as_ref()
8195 .is_some_and(|bp| bp.1.condition.is_some())
8196 {
8197 "Edit Condition Breakpoint"
8198 } else {
8199 "Set Condition Breakpoint"
8200 };
8201
8202 let hit_condition_breakpoint_msg = if breakpoint
8203 .as_ref()
8204 .is_some_and(|bp| bp.1.hit_condition.is_some())
8205 {
8206 "Edit Hit Condition Breakpoint"
8207 } else {
8208 "Set Hit Condition Breakpoint"
8209 };
8210
8211 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8212 "Unset Breakpoint"
8213 } else {
8214 "Set Breakpoint"
8215 };
8216
8217 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8218
8219 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8220 BreakpointState::Enabled => Some("Disable"),
8221 BreakpointState::Disabled => Some("Enable"),
8222 });
8223
8224 let (anchor, breakpoint) =
8225 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8226
8227 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8228 menu.on_blur_subscription(Subscription::new(|| {}))
8229 .context(focus_handle)
8230 .when(run_to_cursor, |this| {
8231 let weak_editor = weak_editor.clone();
8232 this.entry("Run to cursor", None, move |window, cx| {
8233 weak_editor
8234 .update(cx, |editor, cx| {
8235 editor.change_selections(
8236 SelectionEffects::no_scroll(),
8237 window,
8238 cx,
8239 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8240 );
8241 })
8242 .ok();
8243
8244 window.dispatch_action(Box::new(RunToCursor), cx);
8245 })
8246 .separator()
8247 })
8248 .when_some(toggle_state_msg, |this, msg| {
8249 this.entry(msg, None, {
8250 let weak_editor = weak_editor.clone();
8251 let breakpoint = breakpoint.clone();
8252 move |_window, cx| {
8253 weak_editor
8254 .update(cx, |this, cx| {
8255 this.edit_breakpoint_at_anchor(
8256 anchor,
8257 breakpoint.as_ref().clone(),
8258 BreakpointEditAction::InvertState,
8259 cx,
8260 );
8261 })
8262 .log_err();
8263 }
8264 })
8265 })
8266 .entry(set_breakpoint_msg, None, {
8267 let weak_editor = weak_editor.clone();
8268 let breakpoint = breakpoint.clone();
8269 move |_window, cx| {
8270 weak_editor
8271 .update(cx, |this, cx| {
8272 this.edit_breakpoint_at_anchor(
8273 anchor,
8274 breakpoint.as_ref().clone(),
8275 BreakpointEditAction::Toggle,
8276 cx,
8277 );
8278 })
8279 .log_err();
8280 }
8281 })
8282 .entry(log_breakpoint_msg, None, {
8283 let breakpoint = breakpoint.clone();
8284 let weak_editor = weak_editor.clone();
8285 move |window, cx| {
8286 weak_editor
8287 .update(cx, |this, cx| {
8288 this.add_edit_breakpoint_block(
8289 anchor,
8290 breakpoint.as_ref(),
8291 BreakpointPromptEditAction::Log,
8292 window,
8293 cx,
8294 );
8295 })
8296 .log_err();
8297 }
8298 })
8299 .entry(condition_breakpoint_msg, None, {
8300 let breakpoint = breakpoint.clone();
8301 let weak_editor = weak_editor.clone();
8302 move |window, cx| {
8303 weak_editor
8304 .update(cx, |this, cx| {
8305 this.add_edit_breakpoint_block(
8306 anchor,
8307 breakpoint.as_ref(),
8308 BreakpointPromptEditAction::Condition,
8309 window,
8310 cx,
8311 );
8312 })
8313 .log_err();
8314 }
8315 })
8316 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8317 weak_editor
8318 .update(cx, |this, cx| {
8319 this.add_edit_breakpoint_block(
8320 anchor,
8321 breakpoint.as_ref(),
8322 BreakpointPromptEditAction::HitCondition,
8323 window,
8324 cx,
8325 );
8326 })
8327 .log_err();
8328 })
8329 })
8330 }
8331
8332 fn render_breakpoint(
8333 &self,
8334 position: Anchor,
8335 row: DisplayRow,
8336 breakpoint: &Breakpoint,
8337 state: Option<BreakpointSessionState>,
8338 cx: &mut Context<Self>,
8339 ) -> IconButton {
8340 let is_rejected = state.is_some_and(|s| !s.verified);
8341 // Is it a breakpoint that shows up when hovering over gutter?
8342 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8343 (false, false),
8344 |PhantomBreakpointIndicator {
8345 is_active,
8346 display_row,
8347 collides_with_existing_breakpoint,
8348 }| {
8349 (
8350 is_active && display_row == row,
8351 collides_with_existing_breakpoint,
8352 )
8353 },
8354 );
8355
8356 let (color, icon) = {
8357 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8358 (false, false) => ui::IconName::DebugBreakpoint,
8359 (true, false) => ui::IconName::DebugLogBreakpoint,
8360 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8361 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8362 };
8363
8364 let color = if is_phantom {
8365 Color::Hint
8366 } else if is_rejected {
8367 Color::Disabled
8368 } else {
8369 Color::Debugger
8370 };
8371
8372 (color, icon)
8373 };
8374
8375 let breakpoint = Arc::from(breakpoint.clone());
8376
8377 let alt_as_text = gpui::Keystroke {
8378 modifiers: Modifiers::secondary_key(),
8379 ..Default::default()
8380 };
8381 let primary_action_text = if breakpoint.is_disabled() {
8382 "Enable breakpoint"
8383 } else if is_phantom && !collides_with_existing {
8384 "Set breakpoint"
8385 } else {
8386 "Unset breakpoint"
8387 };
8388 let focus_handle = self.focus_handle.clone();
8389
8390 let meta = if is_rejected {
8391 SharedString::from("No executable code is associated with this line.")
8392 } else if collides_with_existing && !breakpoint.is_disabled() {
8393 SharedString::from(format!(
8394 "{alt_as_text}-click to disable,\nright-click for more options."
8395 ))
8396 } else {
8397 SharedString::from("Right-click for more options.")
8398 };
8399 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8400 .icon_size(IconSize::XSmall)
8401 .size(ui::ButtonSize::None)
8402 .when(is_rejected, |this| {
8403 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8404 })
8405 .icon_color(color)
8406 .style(ButtonStyle::Transparent)
8407 .on_click(cx.listener({
8408 move |editor, event: &ClickEvent, window, cx| {
8409 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8410 BreakpointEditAction::InvertState
8411 } else {
8412 BreakpointEditAction::Toggle
8413 };
8414
8415 window.focus(&editor.focus_handle(cx));
8416 editor.edit_breakpoint_at_anchor(
8417 position,
8418 breakpoint.as_ref().clone(),
8419 edit_action,
8420 cx,
8421 );
8422 }
8423 }))
8424 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8425 editor.set_breakpoint_context_menu(
8426 row,
8427 Some(position),
8428 event.position(),
8429 window,
8430 cx,
8431 );
8432 }))
8433 .tooltip(move |window, cx| {
8434 Tooltip::with_meta_in(
8435 primary_action_text,
8436 Some(&ToggleBreakpoint),
8437 meta.clone(),
8438 &focus_handle,
8439 window,
8440 cx,
8441 )
8442 })
8443 }
8444
8445 fn build_tasks_context(
8446 project: &Entity<Project>,
8447 buffer: &Entity<Buffer>,
8448 buffer_row: u32,
8449 tasks: &Arc<RunnableTasks>,
8450 cx: &mut Context<Self>,
8451 ) -> Task<Option<task::TaskContext>> {
8452 let position = Point::new(buffer_row, tasks.column);
8453 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8454 let location = Location {
8455 buffer: buffer.clone(),
8456 range: range_start..range_start,
8457 };
8458 // Fill in the environmental variables from the tree-sitter captures
8459 let mut captured_task_variables = TaskVariables::default();
8460 for (capture_name, value) in tasks.extra_variables.clone() {
8461 captured_task_variables.insert(
8462 task::VariableName::Custom(capture_name.into()),
8463 value.clone(),
8464 );
8465 }
8466 project.update(cx, |project, cx| {
8467 project.task_store().update(cx, |task_store, cx| {
8468 task_store.task_context_for_location(captured_task_variables, location, cx)
8469 })
8470 })
8471 }
8472
8473 pub fn spawn_nearest_task(
8474 &mut self,
8475 action: &SpawnNearestTask,
8476 window: &mut Window,
8477 cx: &mut Context<Self>,
8478 ) {
8479 let Some((workspace, _)) = self.workspace.clone() else {
8480 return;
8481 };
8482 let Some(project) = self.project.clone() else {
8483 return;
8484 };
8485
8486 // Try to find a closest, enclosing node using tree-sitter that has a task
8487 let Some((buffer, buffer_row, tasks)) = self
8488 .find_enclosing_node_task(cx)
8489 // Or find the task that's closest in row-distance.
8490 .or_else(|| self.find_closest_task(cx))
8491 else {
8492 return;
8493 };
8494
8495 let reveal_strategy = action.reveal;
8496 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8497 cx.spawn_in(window, async move |_, cx| {
8498 let context = task_context.await?;
8499 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8500
8501 let resolved = &mut resolved_task.resolved;
8502 resolved.reveal = reveal_strategy;
8503
8504 workspace
8505 .update_in(cx, |workspace, window, cx| {
8506 workspace.schedule_resolved_task(
8507 task_source_kind,
8508 resolved_task,
8509 false,
8510 window,
8511 cx,
8512 );
8513 })
8514 .ok()
8515 })
8516 .detach();
8517 }
8518
8519 fn find_closest_task(
8520 &mut self,
8521 cx: &mut Context<Self>,
8522 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8523 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8524
8525 let ((buffer_id, row), tasks) = self
8526 .tasks
8527 .iter()
8528 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8529
8530 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8531 let tasks = Arc::new(tasks.to_owned());
8532 Some((buffer, *row, tasks))
8533 }
8534
8535 fn find_enclosing_node_task(
8536 &mut self,
8537 cx: &mut Context<Self>,
8538 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8539 let snapshot = self.buffer.read(cx).snapshot(cx);
8540 let offset = self.selections.newest::<usize>(cx).head();
8541 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8542 let buffer_id = excerpt.buffer().remote_id();
8543
8544 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8545 let mut cursor = layer.node().walk();
8546
8547 while cursor.goto_first_child_for_byte(offset).is_some() {
8548 if cursor.node().end_byte() == offset {
8549 cursor.goto_next_sibling();
8550 }
8551 }
8552
8553 // Ascend to the smallest ancestor that contains the range and has a task.
8554 loop {
8555 let node = cursor.node();
8556 let node_range = node.byte_range();
8557 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8558
8559 // Check if this node contains our offset
8560 if node_range.start <= offset && node_range.end >= offset {
8561 // If it contains offset, check for task
8562 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8563 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8564 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8565 }
8566 }
8567
8568 if !cursor.goto_parent() {
8569 break;
8570 }
8571 }
8572 None
8573 }
8574
8575 fn render_run_indicator(
8576 &self,
8577 _style: &EditorStyle,
8578 is_active: bool,
8579 row: DisplayRow,
8580 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8581 cx: &mut Context<Self>,
8582 ) -> IconButton {
8583 let color = Color::Muted;
8584 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8585
8586 IconButton::new(
8587 ("run_indicator", row.0 as usize),
8588 ui::IconName::PlayOutlined,
8589 )
8590 .shape(ui::IconButtonShape::Square)
8591 .icon_size(IconSize::XSmall)
8592 .icon_color(color)
8593 .toggle_state(is_active)
8594 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8595 let quick_launch = match e {
8596 ClickEvent::Keyboard(_) => true,
8597 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8598 };
8599
8600 window.focus(&editor.focus_handle(cx));
8601 editor.toggle_code_actions(
8602 &ToggleCodeActions {
8603 deployed_from: Some(CodeActionSource::RunMenu(row)),
8604 quick_launch,
8605 },
8606 window,
8607 cx,
8608 );
8609 }))
8610 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8611 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8612 }))
8613 }
8614
8615 pub fn context_menu_visible(&self) -> bool {
8616 !self.edit_prediction_preview_is_active()
8617 && self
8618 .context_menu
8619 .borrow()
8620 .as_ref()
8621 .is_some_and(|menu| menu.visible())
8622 }
8623
8624 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8625 self.context_menu
8626 .borrow()
8627 .as_ref()
8628 .map(|menu| menu.origin())
8629 }
8630
8631 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8632 self.context_menu_options = Some(options);
8633 }
8634
8635 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8636 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8637
8638 fn render_edit_prediction_popover(
8639 &mut self,
8640 text_bounds: &Bounds<Pixels>,
8641 content_origin: gpui::Point<Pixels>,
8642 right_margin: Pixels,
8643 editor_snapshot: &EditorSnapshot,
8644 visible_row_range: Range<DisplayRow>,
8645 scroll_top: ScrollOffset,
8646 scroll_bottom: ScrollOffset,
8647 line_layouts: &[LineWithInvisibles],
8648 line_height: Pixels,
8649 scroll_position: gpui::Point<ScrollOffset>,
8650 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8651 newest_selection_head: Option<DisplayPoint>,
8652 editor_width: Pixels,
8653 style: &EditorStyle,
8654 window: &mut Window,
8655 cx: &mut App,
8656 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8657 if self.mode().is_minimap() {
8658 return None;
8659 }
8660 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8661
8662 if self.edit_prediction_visible_in_cursor_popover(true) {
8663 return None;
8664 }
8665
8666 match &active_edit_prediction.completion {
8667 EditPrediction::MoveWithin { target, .. } => {
8668 let target_display_point = target.to_display_point(editor_snapshot);
8669
8670 if self.edit_prediction_requires_modifier() {
8671 if !self.edit_prediction_preview_is_active() {
8672 return None;
8673 }
8674
8675 self.render_edit_prediction_modifier_jump_popover(
8676 text_bounds,
8677 content_origin,
8678 visible_row_range,
8679 line_layouts,
8680 line_height,
8681 scroll_pixel_position,
8682 newest_selection_head,
8683 target_display_point,
8684 window,
8685 cx,
8686 )
8687 } else {
8688 self.render_edit_prediction_eager_jump_popover(
8689 text_bounds,
8690 content_origin,
8691 editor_snapshot,
8692 visible_row_range,
8693 scroll_top,
8694 scroll_bottom,
8695 line_height,
8696 scroll_pixel_position,
8697 target_display_point,
8698 editor_width,
8699 window,
8700 cx,
8701 )
8702 }
8703 }
8704 EditPrediction::Edit {
8705 display_mode: EditDisplayMode::Inline,
8706 ..
8707 } => None,
8708 EditPrediction::Edit {
8709 display_mode: EditDisplayMode::TabAccept,
8710 edits,
8711 ..
8712 } => {
8713 let range = &edits.first()?.0;
8714 let target_display_point = range.end.to_display_point(editor_snapshot);
8715
8716 self.render_edit_prediction_end_of_line_popover(
8717 "Accept",
8718 editor_snapshot,
8719 visible_row_range,
8720 target_display_point,
8721 line_height,
8722 scroll_pixel_position,
8723 content_origin,
8724 editor_width,
8725 window,
8726 cx,
8727 )
8728 }
8729 EditPrediction::Edit {
8730 edits,
8731 edit_preview,
8732 display_mode: EditDisplayMode::DiffPopover,
8733 snapshot,
8734 } => self.render_edit_prediction_diff_popover(
8735 text_bounds,
8736 content_origin,
8737 right_margin,
8738 editor_snapshot,
8739 visible_row_range,
8740 line_layouts,
8741 line_height,
8742 scroll_position,
8743 scroll_pixel_position,
8744 newest_selection_head,
8745 editor_width,
8746 style,
8747 edits,
8748 edit_preview,
8749 snapshot,
8750 window,
8751 cx,
8752 ),
8753 EditPrediction::MoveOutside { snapshot, .. } => {
8754 let file_name = snapshot
8755 .file()
8756 .map(|file| file.file_name(cx))
8757 .unwrap_or("untitled");
8758 let mut element = self
8759 .render_edit_prediction_line_popover(
8760 format!("Jump to {file_name}"),
8761 Some(IconName::ZedPredict),
8762 window,
8763 cx,
8764 )
8765 .into_any();
8766
8767 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8768 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8769 let origin_y = text_bounds.size.height - size.height - px(30.);
8770 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8771 element.prepaint_at(origin, window, cx);
8772
8773 Some((element, origin))
8774 }
8775 }
8776 }
8777
8778 fn render_edit_prediction_modifier_jump_popover(
8779 &mut self,
8780 text_bounds: &Bounds<Pixels>,
8781 content_origin: gpui::Point<Pixels>,
8782 visible_row_range: Range<DisplayRow>,
8783 line_layouts: &[LineWithInvisibles],
8784 line_height: Pixels,
8785 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8786 newest_selection_head: Option<DisplayPoint>,
8787 target_display_point: DisplayPoint,
8788 window: &mut Window,
8789 cx: &mut App,
8790 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8791 let scrolled_content_origin =
8792 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8793
8794 const SCROLL_PADDING_Y: Pixels = px(12.);
8795
8796 if target_display_point.row() < visible_row_range.start {
8797 return self.render_edit_prediction_scroll_popover(
8798 |_| SCROLL_PADDING_Y,
8799 IconName::ArrowUp,
8800 visible_row_range,
8801 line_layouts,
8802 newest_selection_head,
8803 scrolled_content_origin,
8804 window,
8805 cx,
8806 );
8807 } else if target_display_point.row() >= visible_row_range.end {
8808 return self.render_edit_prediction_scroll_popover(
8809 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8810 IconName::ArrowDown,
8811 visible_row_range,
8812 line_layouts,
8813 newest_selection_head,
8814 scrolled_content_origin,
8815 window,
8816 cx,
8817 );
8818 }
8819
8820 const POLE_WIDTH: Pixels = px(2.);
8821
8822 let line_layout =
8823 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8824 let target_column = target_display_point.column() as usize;
8825
8826 let target_x = line_layout.x_for_index(target_column);
8827 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8828 - scroll_pixel_position.y;
8829
8830 let flag_on_right = target_x < text_bounds.size.width / 2.;
8831
8832 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8833 border_color.l += 0.001;
8834
8835 let mut element = v_flex()
8836 .items_end()
8837 .when(flag_on_right, |el| el.items_start())
8838 .child(if flag_on_right {
8839 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8840 .rounded_bl(px(0.))
8841 .rounded_tl(px(0.))
8842 .border_l_2()
8843 .border_color(border_color)
8844 } else {
8845 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8846 .rounded_br(px(0.))
8847 .rounded_tr(px(0.))
8848 .border_r_2()
8849 .border_color(border_color)
8850 })
8851 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8852 .into_any();
8853
8854 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8855
8856 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8857 - point(
8858 if flag_on_right {
8859 POLE_WIDTH
8860 } else {
8861 size.width - POLE_WIDTH
8862 },
8863 size.height - line_height,
8864 );
8865
8866 origin.x = origin.x.max(content_origin.x);
8867
8868 element.prepaint_at(origin, window, cx);
8869
8870 Some((element, origin))
8871 }
8872
8873 fn render_edit_prediction_scroll_popover(
8874 &mut self,
8875 to_y: impl Fn(Size<Pixels>) -> Pixels,
8876 scroll_icon: IconName,
8877 visible_row_range: Range<DisplayRow>,
8878 line_layouts: &[LineWithInvisibles],
8879 newest_selection_head: Option<DisplayPoint>,
8880 scrolled_content_origin: gpui::Point<Pixels>,
8881 window: &mut Window,
8882 cx: &mut App,
8883 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8884 let mut element = self
8885 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8886 .into_any();
8887
8888 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8889
8890 let cursor = newest_selection_head?;
8891 let cursor_row_layout =
8892 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8893 let cursor_column = cursor.column() as usize;
8894
8895 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8896
8897 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8898
8899 element.prepaint_at(origin, window, cx);
8900 Some((element, origin))
8901 }
8902
8903 fn render_edit_prediction_eager_jump_popover(
8904 &mut self,
8905 text_bounds: &Bounds<Pixels>,
8906 content_origin: gpui::Point<Pixels>,
8907 editor_snapshot: &EditorSnapshot,
8908 visible_row_range: Range<DisplayRow>,
8909 scroll_top: ScrollOffset,
8910 scroll_bottom: ScrollOffset,
8911 line_height: Pixels,
8912 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8913 target_display_point: DisplayPoint,
8914 editor_width: Pixels,
8915 window: &mut Window,
8916 cx: &mut App,
8917 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8918 if target_display_point.row().as_f64() < scroll_top {
8919 let mut element = self
8920 .render_edit_prediction_line_popover(
8921 "Jump to Edit",
8922 Some(IconName::ArrowUp),
8923 window,
8924 cx,
8925 )
8926 .into_any();
8927
8928 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8929 let offset = point(
8930 (text_bounds.size.width - size.width) / 2.,
8931 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8932 );
8933
8934 let origin = text_bounds.origin + offset;
8935 element.prepaint_at(origin, window, cx);
8936 Some((element, origin))
8937 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8938 let mut element = self
8939 .render_edit_prediction_line_popover(
8940 "Jump to Edit",
8941 Some(IconName::ArrowDown),
8942 window,
8943 cx,
8944 )
8945 .into_any();
8946
8947 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8948 let offset = point(
8949 (text_bounds.size.width - size.width) / 2.,
8950 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8951 );
8952
8953 let origin = text_bounds.origin + offset;
8954 element.prepaint_at(origin, window, cx);
8955 Some((element, origin))
8956 } else {
8957 self.render_edit_prediction_end_of_line_popover(
8958 "Jump to Edit",
8959 editor_snapshot,
8960 visible_row_range,
8961 target_display_point,
8962 line_height,
8963 scroll_pixel_position,
8964 content_origin,
8965 editor_width,
8966 window,
8967 cx,
8968 )
8969 }
8970 }
8971
8972 fn render_edit_prediction_end_of_line_popover(
8973 self: &mut Editor,
8974 label: &'static str,
8975 editor_snapshot: &EditorSnapshot,
8976 visible_row_range: Range<DisplayRow>,
8977 target_display_point: DisplayPoint,
8978 line_height: Pixels,
8979 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8980 content_origin: gpui::Point<Pixels>,
8981 editor_width: Pixels,
8982 window: &mut Window,
8983 cx: &mut App,
8984 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8985 let target_line_end = DisplayPoint::new(
8986 target_display_point.row(),
8987 editor_snapshot.line_len(target_display_point.row()),
8988 );
8989
8990 let mut element = self
8991 .render_edit_prediction_line_popover(label, None, window, cx)
8992 .into_any();
8993
8994 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8995
8996 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8997
8998 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8999 let mut origin = start_point
9000 + line_origin
9001 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9002 origin.x = origin.x.max(content_origin.x);
9003
9004 let max_x = content_origin.x + editor_width - size.width;
9005
9006 if origin.x > max_x {
9007 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9008
9009 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9010 origin.y += offset;
9011 IconName::ArrowUp
9012 } else {
9013 origin.y -= offset;
9014 IconName::ArrowDown
9015 };
9016
9017 element = self
9018 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9019 .into_any();
9020
9021 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9022
9023 origin.x = content_origin.x + editor_width - size.width - px(2.);
9024 }
9025
9026 element.prepaint_at(origin, window, cx);
9027 Some((element, origin))
9028 }
9029
9030 fn render_edit_prediction_diff_popover(
9031 self: &Editor,
9032 text_bounds: &Bounds<Pixels>,
9033 content_origin: gpui::Point<Pixels>,
9034 right_margin: Pixels,
9035 editor_snapshot: &EditorSnapshot,
9036 visible_row_range: Range<DisplayRow>,
9037 line_layouts: &[LineWithInvisibles],
9038 line_height: Pixels,
9039 scroll_position: gpui::Point<ScrollOffset>,
9040 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9041 newest_selection_head: Option<DisplayPoint>,
9042 editor_width: Pixels,
9043 style: &EditorStyle,
9044 edits: &Vec<(Range<Anchor>, String)>,
9045 edit_preview: &Option<language::EditPreview>,
9046 snapshot: &language::BufferSnapshot,
9047 window: &mut Window,
9048 cx: &mut App,
9049 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9050 let edit_start = edits
9051 .first()
9052 .unwrap()
9053 .0
9054 .start
9055 .to_display_point(editor_snapshot);
9056 let edit_end = edits
9057 .last()
9058 .unwrap()
9059 .0
9060 .end
9061 .to_display_point(editor_snapshot);
9062
9063 let is_visible = visible_row_range.contains(&edit_start.row())
9064 || visible_row_range.contains(&edit_end.row());
9065 if !is_visible {
9066 return None;
9067 }
9068
9069 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9070 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9071 } else {
9072 // Fallback for providers without edit_preview
9073 crate::edit_prediction_fallback_text(edits, cx)
9074 };
9075
9076 let styled_text = highlighted_edits.to_styled_text(&style.text);
9077 let line_count = highlighted_edits.text.lines().count();
9078
9079 const BORDER_WIDTH: Pixels = px(1.);
9080
9081 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9082 let has_keybind = keybind.is_some();
9083
9084 let mut element = h_flex()
9085 .items_start()
9086 .child(
9087 h_flex()
9088 .bg(cx.theme().colors().editor_background)
9089 .border(BORDER_WIDTH)
9090 .shadow_xs()
9091 .border_color(cx.theme().colors().border)
9092 .rounded_l_lg()
9093 .when(line_count > 1, |el| el.rounded_br_lg())
9094 .pr_1()
9095 .child(styled_text),
9096 )
9097 .child(
9098 h_flex()
9099 .h(line_height + BORDER_WIDTH * 2.)
9100 .px_1p5()
9101 .gap_1()
9102 // Workaround: For some reason, there's a gap if we don't do this
9103 .ml(-BORDER_WIDTH)
9104 .shadow(vec![gpui::BoxShadow {
9105 color: gpui::black().opacity(0.05),
9106 offset: point(px(1.), px(1.)),
9107 blur_radius: px(2.),
9108 spread_radius: px(0.),
9109 }])
9110 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9111 .border(BORDER_WIDTH)
9112 .border_color(cx.theme().colors().border)
9113 .rounded_r_lg()
9114 .id("edit_prediction_diff_popover_keybind")
9115 .when(!has_keybind, |el| {
9116 let status_colors = cx.theme().status();
9117
9118 el.bg(status_colors.error_background)
9119 .border_color(status_colors.error.opacity(0.6))
9120 .child(Icon::new(IconName::Info).color(Color::Error))
9121 .cursor_default()
9122 .hoverable_tooltip(move |_window, cx| {
9123 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9124 })
9125 })
9126 .children(keybind),
9127 )
9128 .into_any();
9129
9130 let longest_row =
9131 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9132 let longest_line_width = if visible_row_range.contains(&longest_row) {
9133 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9134 } else {
9135 layout_line(
9136 longest_row,
9137 editor_snapshot,
9138 style,
9139 editor_width,
9140 |_| false,
9141 window,
9142 cx,
9143 )
9144 .width
9145 };
9146
9147 let viewport_bounds =
9148 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9149 right: -right_margin,
9150 ..Default::default()
9151 });
9152
9153 let x_after_longest = Pixels::from(
9154 ScrollPixelOffset::from(
9155 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9156 ) - scroll_pixel_position.x,
9157 );
9158
9159 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9160
9161 // Fully visible if it can be displayed within the window (allow overlapping other
9162 // panes). However, this is only allowed if the popover starts within text_bounds.
9163 let can_position_to_the_right = x_after_longest < text_bounds.right()
9164 && x_after_longest + element_bounds.width < viewport_bounds.right();
9165
9166 let mut origin = if can_position_to_the_right {
9167 point(
9168 x_after_longest,
9169 text_bounds.origin.y
9170 + Pixels::from(
9171 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9172 - scroll_pixel_position.y,
9173 ),
9174 )
9175 } else {
9176 let cursor_row = newest_selection_head.map(|head| head.row());
9177 let above_edit = edit_start
9178 .row()
9179 .0
9180 .checked_sub(line_count as u32)
9181 .map(DisplayRow);
9182 let below_edit = Some(edit_end.row() + 1);
9183 let above_cursor =
9184 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9185 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9186
9187 // Place the edit popover adjacent to the edit if there is a location
9188 // available that is onscreen and does not obscure the cursor. Otherwise,
9189 // place it adjacent to the cursor.
9190 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9191 .into_iter()
9192 .flatten()
9193 .find(|&start_row| {
9194 let end_row = start_row + line_count as u32;
9195 visible_row_range.contains(&start_row)
9196 && visible_row_range.contains(&end_row)
9197 && cursor_row
9198 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9199 })?;
9200
9201 content_origin
9202 + point(
9203 Pixels::from(-scroll_pixel_position.x),
9204 Pixels::from(
9205 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9206 ),
9207 )
9208 };
9209
9210 origin.x -= BORDER_WIDTH;
9211
9212 window.defer_draw(element, origin, 1);
9213
9214 // Do not return an element, since it will already be drawn due to defer_draw.
9215 None
9216 }
9217
9218 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9219 px(30.)
9220 }
9221
9222 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9223 if self.read_only(cx) {
9224 cx.theme().players().read_only()
9225 } else {
9226 self.style.as_ref().unwrap().local_player
9227 }
9228 }
9229
9230 fn render_edit_prediction_accept_keybind(
9231 &self,
9232 window: &mut Window,
9233 cx: &App,
9234 ) -> Option<AnyElement> {
9235 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9236 let accept_keystroke = accept_binding.keystroke()?;
9237
9238 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9239
9240 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9241 Color::Accent
9242 } else {
9243 Color::Muted
9244 };
9245
9246 h_flex()
9247 .px_0p5()
9248 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9249 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9250 .text_size(TextSize::XSmall.rems(cx))
9251 .child(h_flex().children(ui::render_modifiers(
9252 accept_keystroke.modifiers(),
9253 PlatformStyle::platform(),
9254 Some(modifiers_color),
9255 Some(IconSize::XSmall.rems().into()),
9256 true,
9257 )))
9258 .when(is_platform_style_mac, |parent| {
9259 parent.child(accept_keystroke.key().to_string())
9260 })
9261 .when(!is_platform_style_mac, |parent| {
9262 parent.child(
9263 Key::new(
9264 util::capitalize(accept_keystroke.key()),
9265 Some(Color::Default),
9266 )
9267 .size(Some(IconSize::XSmall.rems().into())),
9268 )
9269 })
9270 .into_any()
9271 .into()
9272 }
9273
9274 fn render_edit_prediction_line_popover(
9275 &self,
9276 label: impl Into<SharedString>,
9277 icon: Option<IconName>,
9278 window: &mut Window,
9279 cx: &App,
9280 ) -> Stateful<Div> {
9281 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9282
9283 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9284 let has_keybind = keybind.is_some();
9285
9286 h_flex()
9287 .id("ep-line-popover")
9288 .py_0p5()
9289 .pl_1()
9290 .pr(padding_right)
9291 .gap_1()
9292 .rounded_md()
9293 .border_1()
9294 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9295 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9296 .shadow_xs()
9297 .when(!has_keybind, |el| {
9298 let status_colors = cx.theme().status();
9299
9300 el.bg(status_colors.error_background)
9301 .border_color(status_colors.error.opacity(0.6))
9302 .pl_2()
9303 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9304 .cursor_default()
9305 .hoverable_tooltip(move |_window, cx| {
9306 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9307 })
9308 })
9309 .children(keybind)
9310 .child(
9311 Label::new(label)
9312 .size(LabelSize::Small)
9313 .when(!has_keybind, |el| {
9314 el.color(cx.theme().status().error.into()).strikethrough()
9315 }),
9316 )
9317 .when(!has_keybind, |el| {
9318 el.child(
9319 h_flex().ml_1().child(
9320 Icon::new(IconName::Info)
9321 .size(IconSize::Small)
9322 .color(cx.theme().status().error.into()),
9323 ),
9324 )
9325 })
9326 .when_some(icon, |element, icon| {
9327 element.child(
9328 div()
9329 .mt(px(1.5))
9330 .child(Icon::new(icon).size(IconSize::Small)),
9331 )
9332 })
9333 }
9334
9335 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9336 let accent_color = cx.theme().colors().text_accent;
9337 let editor_bg_color = cx.theme().colors().editor_background;
9338 editor_bg_color.blend(accent_color.opacity(0.1))
9339 }
9340
9341 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9342 let accent_color = cx.theme().colors().text_accent;
9343 let editor_bg_color = cx.theme().colors().editor_background;
9344 editor_bg_color.blend(accent_color.opacity(0.6))
9345 }
9346 fn get_prediction_provider_icon_name(
9347 provider: &Option<RegisteredEditPredictionProvider>,
9348 ) -> IconName {
9349 match provider {
9350 Some(provider) => match provider.provider.name() {
9351 "copilot" => IconName::Copilot,
9352 "supermaven" => IconName::Supermaven,
9353 _ => IconName::ZedPredict,
9354 },
9355 None => IconName::ZedPredict,
9356 }
9357 }
9358
9359 fn render_edit_prediction_cursor_popover(
9360 &self,
9361 min_width: Pixels,
9362 max_width: Pixels,
9363 cursor_point: Point,
9364 style: &EditorStyle,
9365 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9366 _window: &Window,
9367 cx: &mut Context<Editor>,
9368 ) -> Option<AnyElement> {
9369 let provider = self.edit_prediction_provider.as_ref()?;
9370 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9371
9372 let is_refreshing = provider.provider.is_refreshing(cx);
9373
9374 fn pending_completion_container(icon: IconName) -> Div {
9375 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9376 }
9377
9378 let completion = match &self.active_edit_prediction {
9379 Some(prediction) => {
9380 if !self.has_visible_completions_menu() {
9381 const RADIUS: Pixels = px(6.);
9382 const BORDER_WIDTH: Pixels = px(1.);
9383
9384 return Some(
9385 h_flex()
9386 .elevation_2(cx)
9387 .border(BORDER_WIDTH)
9388 .border_color(cx.theme().colors().border)
9389 .when(accept_keystroke.is_none(), |el| {
9390 el.border_color(cx.theme().status().error)
9391 })
9392 .rounded(RADIUS)
9393 .rounded_tl(px(0.))
9394 .overflow_hidden()
9395 .child(div().px_1p5().child(match &prediction.completion {
9396 EditPrediction::MoveWithin { target, snapshot } => {
9397 use text::ToPoint as _;
9398 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9399 {
9400 Icon::new(IconName::ZedPredictDown)
9401 } else {
9402 Icon::new(IconName::ZedPredictUp)
9403 }
9404 }
9405 EditPrediction::MoveOutside { .. } => {
9406 // TODO [zeta2] custom icon for external jump?
9407 Icon::new(provider_icon)
9408 }
9409 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9410 }))
9411 .child(
9412 h_flex()
9413 .gap_1()
9414 .py_1()
9415 .px_2()
9416 .rounded_r(RADIUS - BORDER_WIDTH)
9417 .border_l_1()
9418 .border_color(cx.theme().colors().border)
9419 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9420 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9421 el.child(
9422 Label::new("Hold")
9423 .size(LabelSize::Small)
9424 .when(accept_keystroke.is_none(), |el| {
9425 el.strikethrough()
9426 })
9427 .line_height_style(LineHeightStyle::UiLabel),
9428 )
9429 })
9430 .id("edit_prediction_cursor_popover_keybind")
9431 .when(accept_keystroke.is_none(), |el| {
9432 let status_colors = cx.theme().status();
9433
9434 el.bg(status_colors.error_background)
9435 .border_color(status_colors.error.opacity(0.6))
9436 .child(Icon::new(IconName::Info).color(Color::Error))
9437 .cursor_default()
9438 .hoverable_tooltip(move |_window, cx| {
9439 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9440 .into()
9441 })
9442 })
9443 .when_some(
9444 accept_keystroke.as_ref(),
9445 |el, accept_keystroke| {
9446 el.child(h_flex().children(ui::render_modifiers(
9447 accept_keystroke.modifiers(),
9448 PlatformStyle::platform(),
9449 Some(Color::Default),
9450 Some(IconSize::XSmall.rems().into()),
9451 false,
9452 )))
9453 },
9454 ),
9455 )
9456 .into_any(),
9457 );
9458 }
9459
9460 self.render_edit_prediction_cursor_popover_preview(
9461 prediction,
9462 cursor_point,
9463 style,
9464 cx,
9465 )?
9466 }
9467
9468 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9469 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9470 stale_completion,
9471 cursor_point,
9472 style,
9473 cx,
9474 )?,
9475
9476 None => pending_completion_container(provider_icon)
9477 .child(Label::new("...").size(LabelSize::Small)),
9478 },
9479
9480 None => pending_completion_container(provider_icon)
9481 .child(Label::new("...").size(LabelSize::Small)),
9482 };
9483
9484 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9485 completion
9486 .with_animation(
9487 "loading-completion",
9488 Animation::new(Duration::from_secs(2))
9489 .repeat()
9490 .with_easing(pulsating_between(0.4, 0.8)),
9491 |label, delta| label.opacity(delta),
9492 )
9493 .into_any_element()
9494 } else {
9495 completion.into_any_element()
9496 };
9497
9498 let has_completion = self.active_edit_prediction.is_some();
9499
9500 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9501 Some(
9502 h_flex()
9503 .min_w(min_width)
9504 .max_w(max_width)
9505 .flex_1()
9506 .elevation_2(cx)
9507 .border_color(cx.theme().colors().border)
9508 .child(
9509 div()
9510 .flex_1()
9511 .py_1()
9512 .px_2()
9513 .overflow_hidden()
9514 .child(completion),
9515 )
9516 .when_some(accept_keystroke, |el, accept_keystroke| {
9517 if !accept_keystroke.modifiers().modified() {
9518 return el;
9519 }
9520
9521 el.child(
9522 h_flex()
9523 .h_full()
9524 .border_l_1()
9525 .rounded_r_lg()
9526 .border_color(cx.theme().colors().border)
9527 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9528 .gap_1()
9529 .py_1()
9530 .px_2()
9531 .child(
9532 h_flex()
9533 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9534 .when(is_platform_style_mac, |parent| parent.gap_1())
9535 .child(h_flex().children(ui::render_modifiers(
9536 accept_keystroke.modifiers(),
9537 PlatformStyle::platform(),
9538 Some(if !has_completion {
9539 Color::Muted
9540 } else {
9541 Color::Default
9542 }),
9543 None,
9544 false,
9545 ))),
9546 )
9547 .child(Label::new("Preview").into_any_element())
9548 .opacity(if has_completion { 1.0 } else { 0.4 }),
9549 )
9550 })
9551 .into_any(),
9552 )
9553 }
9554
9555 fn render_edit_prediction_cursor_popover_preview(
9556 &self,
9557 completion: &EditPredictionState,
9558 cursor_point: Point,
9559 style: &EditorStyle,
9560 cx: &mut Context<Editor>,
9561 ) -> Option<Div> {
9562 use text::ToPoint as _;
9563
9564 fn render_relative_row_jump(
9565 prefix: impl Into<String>,
9566 current_row: u32,
9567 target_row: u32,
9568 ) -> Div {
9569 let (row_diff, arrow) = if target_row < current_row {
9570 (current_row - target_row, IconName::ArrowUp)
9571 } else {
9572 (target_row - current_row, IconName::ArrowDown)
9573 };
9574
9575 h_flex()
9576 .child(
9577 Label::new(format!("{}{}", prefix.into(), row_diff))
9578 .color(Color::Muted)
9579 .size(LabelSize::Small),
9580 )
9581 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9582 }
9583
9584 let supports_jump = self
9585 .edit_prediction_provider
9586 .as_ref()
9587 .map(|provider| provider.provider.supports_jump_to_edit())
9588 .unwrap_or(true);
9589
9590 match &completion.completion {
9591 EditPrediction::MoveWithin {
9592 target, snapshot, ..
9593 } => {
9594 if !supports_jump {
9595 return None;
9596 }
9597
9598 Some(
9599 h_flex()
9600 .px_2()
9601 .gap_2()
9602 .flex_1()
9603 .child(
9604 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9605 Icon::new(IconName::ZedPredictDown)
9606 } else {
9607 Icon::new(IconName::ZedPredictUp)
9608 },
9609 )
9610 .child(Label::new("Jump to Edit")),
9611 )
9612 }
9613 EditPrediction::MoveOutside { snapshot, .. } => {
9614 let file_name = snapshot
9615 .file()
9616 .map(|file| file.file_name(cx))
9617 .unwrap_or("untitled");
9618 Some(
9619 h_flex()
9620 .px_2()
9621 .gap_2()
9622 .flex_1()
9623 .child(Icon::new(IconName::ZedPredict))
9624 .child(Label::new(format!("Jump to {file_name}"))),
9625 )
9626 }
9627 EditPrediction::Edit {
9628 edits,
9629 edit_preview,
9630 snapshot,
9631 display_mode: _,
9632 } => {
9633 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9634
9635 let (highlighted_edits, has_more_lines) =
9636 if let Some(edit_preview) = edit_preview.as_ref() {
9637 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9638 .first_line_preview()
9639 } else {
9640 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9641 };
9642
9643 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9644 .with_default_highlights(&style.text, highlighted_edits.highlights);
9645
9646 let preview = h_flex()
9647 .gap_1()
9648 .min_w_16()
9649 .child(styled_text)
9650 .when(has_more_lines, |parent| parent.child("…"));
9651
9652 let left = if supports_jump && first_edit_row != cursor_point.row {
9653 render_relative_row_jump("", cursor_point.row, first_edit_row)
9654 .into_any_element()
9655 } else {
9656 let icon_name =
9657 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9658 Icon::new(icon_name).into_any_element()
9659 };
9660
9661 Some(
9662 h_flex()
9663 .h_full()
9664 .flex_1()
9665 .gap_2()
9666 .pr_1()
9667 .overflow_x_hidden()
9668 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9669 .child(left)
9670 .child(preview),
9671 )
9672 }
9673 }
9674 }
9675
9676 pub fn render_context_menu(
9677 &self,
9678 style: &EditorStyle,
9679 max_height_in_lines: u32,
9680 window: &mut Window,
9681 cx: &mut Context<Editor>,
9682 ) -> Option<AnyElement> {
9683 let menu = self.context_menu.borrow();
9684 let menu = menu.as_ref()?;
9685 if !menu.visible() {
9686 return None;
9687 };
9688 Some(menu.render(style, max_height_in_lines, window, cx))
9689 }
9690
9691 fn render_context_menu_aside(
9692 &mut self,
9693 max_size: Size<Pixels>,
9694 window: &mut Window,
9695 cx: &mut Context<Editor>,
9696 ) -> Option<AnyElement> {
9697 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9698 if menu.visible() {
9699 menu.render_aside(max_size, window, cx)
9700 } else {
9701 None
9702 }
9703 })
9704 }
9705
9706 fn hide_context_menu(
9707 &mut self,
9708 window: &mut Window,
9709 cx: &mut Context<Self>,
9710 ) -> Option<CodeContextMenu> {
9711 cx.notify();
9712 self.completion_tasks.clear();
9713 let context_menu = self.context_menu.borrow_mut().take();
9714 self.stale_edit_prediction_in_menu.take();
9715 self.update_visible_edit_prediction(window, cx);
9716 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9717 && let Some(completion_provider) = &self.completion_provider
9718 {
9719 completion_provider.selection_changed(None, window, cx);
9720 }
9721 context_menu
9722 }
9723
9724 fn show_snippet_choices(
9725 &mut self,
9726 choices: &Vec<String>,
9727 selection: Range<Anchor>,
9728 cx: &mut Context<Self>,
9729 ) {
9730 let Some((_, buffer, _)) = self
9731 .buffer()
9732 .read(cx)
9733 .excerpt_containing(selection.start, cx)
9734 else {
9735 return;
9736 };
9737 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9738 else {
9739 return;
9740 };
9741 if buffer != end_buffer {
9742 log::error!("expected anchor range to have matching buffer IDs");
9743 return;
9744 }
9745
9746 let id = post_inc(&mut self.next_completion_id);
9747 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9748 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9749 CompletionsMenu::new_snippet_choices(
9750 id,
9751 true,
9752 choices,
9753 selection,
9754 buffer,
9755 snippet_sort_order,
9756 ),
9757 ));
9758 }
9759
9760 pub fn insert_snippet(
9761 &mut self,
9762 insertion_ranges: &[Range<usize>],
9763 snippet: Snippet,
9764 window: &mut Window,
9765 cx: &mut Context<Self>,
9766 ) -> Result<()> {
9767 struct Tabstop<T> {
9768 is_end_tabstop: bool,
9769 ranges: Vec<Range<T>>,
9770 choices: Option<Vec<String>>,
9771 }
9772
9773 let tabstops = self.buffer.update(cx, |buffer, cx| {
9774 let snippet_text: Arc<str> = snippet.text.clone().into();
9775 let edits = insertion_ranges
9776 .iter()
9777 .cloned()
9778 .map(|range| (range, snippet_text.clone()));
9779 let autoindent_mode = AutoindentMode::Block {
9780 original_indent_columns: Vec::new(),
9781 };
9782 buffer.edit(edits, Some(autoindent_mode), cx);
9783
9784 let snapshot = &*buffer.read(cx);
9785 let snippet = &snippet;
9786 snippet
9787 .tabstops
9788 .iter()
9789 .map(|tabstop| {
9790 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9791 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9792 });
9793 let mut tabstop_ranges = tabstop
9794 .ranges
9795 .iter()
9796 .flat_map(|tabstop_range| {
9797 let mut delta = 0_isize;
9798 insertion_ranges.iter().map(move |insertion_range| {
9799 let insertion_start = insertion_range.start as isize + delta;
9800 delta +=
9801 snippet.text.len() as isize - insertion_range.len() as isize;
9802
9803 let start = ((insertion_start + tabstop_range.start) as usize)
9804 .min(snapshot.len());
9805 let end = ((insertion_start + tabstop_range.end) as usize)
9806 .min(snapshot.len());
9807 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9808 })
9809 })
9810 .collect::<Vec<_>>();
9811 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9812
9813 Tabstop {
9814 is_end_tabstop,
9815 ranges: tabstop_ranges,
9816 choices: tabstop.choices.clone(),
9817 }
9818 })
9819 .collect::<Vec<_>>()
9820 });
9821 if let Some(tabstop) = tabstops.first() {
9822 self.change_selections(Default::default(), window, cx, |s| {
9823 // Reverse order so that the first range is the newest created selection.
9824 // Completions will use it and autoscroll will prioritize it.
9825 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9826 });
9827
9828 if let Some(choices) = &tabstop.choices
9829 && let Some(selection) = tabstop.ranges.first()
9830 {
9831 self.show_snippet_choices(choices, selection.clone(), cx)
9832 }
9833
9834 // If we're already at the last tabstop and it's at the end of the snippet,
9835 // we're done, we don't need to keep the state around.
9836 if !tabstop.is_end_tabstop {
9837 let choices = tabstops
9838 .iter()
9839 .map(|tabstop| tabstop.choices.clone())
9840 .collect();
9841
9842 let ranges = tabstops
9843 .into_iter()
9844 .map(|tabstop| tabstop.ranges)
9845 .collect::<Vec<_>>();
9846
9847 self.snippet_stack.push(SnippetState {
9848 active_index: 0,
9849 ranges,
9850 choices,
9851 });
9852 }
9853
9854 // Check whether the just-entered snippet ends with an auto-closable bracket.
9855 if self.autoclose_regions.is_empty() {
9856 let snapshot = self.buffer.read(cx).snapshot(cx);
9857 let mut all_selections = self.selections.all::<Point>(cx);
9858 for selection in &mut all_selections {
9859 let selection_head = selection.head();
9860 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9861 continue;
9862 };
9863
9864 let mut bracket_pair = None;
9865 let max_lookup_length = scope
9866 .brackets()
9867 .map(|(pair, _)| {
9868 pair.start
9869 .as_str()
9870 .chars()
9871 .count()
9872 .max(pair.end.as_str().chars().count())
9873 })
9874 .max();
9875 if let Some(max_lookup_length) = max_lookup_length {
9876 let next_text = snapshot
9877 .chars_at(selection_head)
9878 .take(max_lookup_length)
9879 .collect::<String>();
9880 let prev_text = snapshot
9881 .reversed_chars_at(selection_head)
9882 .take(max_lookup_length)
9883 .collect::<String>();
9884
9885 for (pair, enabled) in scope.brackets() {
9886 if enabled
9887 && pair.close
9888 && prev_text.starts_with(pair.start.as_str())
9889 && next_text.starts_with(pair.end.as_str())
9890 {
9891 bracket_pair = Some(pair.clone());
9892 break;
9893 }
9894 }
9895 }
9896
9897 if let Some(pair) = bracket_pair {
9898 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9899 let autoclose_enabled =
9900 self.use_autoclose && snapshot_settings.use_autoclose;
9901 if autoclose_enabled {
9902 let start = snapshot.anchor_after(selection_head);
9903 let end = snapshot.anchor_after(selection_head);
9904 self.autoclose_regions.push(AutocloseRegion {
9905 selection_id: selection.id,
9906 range: start..end,
9907 pair,
9908 });
9909 }
9910 }
9911 }
9912 }
9913 }
9914 Ok(())
9915 }
9916
9917 pub fn move_to_next_snippet_tabstop(
9918 &mut self,
9919 window: &mut Window,
9920 cx: &mut Context<Self>,
9921 ) -> bool {
9922 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9923 }
9924
9925 pub fn move_to_prev_snippet_tabstop(
9926 &mut self,
9927 window: &mut Window,
9928 cx: &mut Context<Self>,
9929 ) -> bool {
9930 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9931 }
9932
9933 pub fn move_to_snippet_tabstop(
9934 &mut self,
9935 bias: Bias,
9936 window: &mut Window,
9937 cx: &mut Context<Self>,
9938 ) -> bool {
9939 if let Some(mut snippet) = self.snippet_stack.pop() {
9940 match bias {
9941 Bias::Left => {
9942 if snippet.active_index > 0 {
9943 snippet.active_index -= 1;
9944 } else {
9945 self.snippet_stack.push(snippet);
9946 return false;
9947 }
9948 }
9949 Bias::Right => {
9950 if snippet.active_index + 1 < snippet.ranges.len() {
9951 snippet.active_index += 1;
9952 } else {
9953 self.snippet_stack.push(snippet);
9954 return false;
9955 }
9956 }
9957 }
9958 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9959 self.change_selections(Default::default(), window, cx, |s| {
9960 // Reverse order so that the first range is the newest created selection.
9961 // Completions will use it and autoscroll will prioritize it.
9962 s.select_ranges(current_ranges.iter().rev().cloned())
9963 });
9964
9965 if let Some(choices) = &snippet.choices[snippet.active_index]
9966 && let Some(selection) = current_ranges.first()
9967 {
9968 self.show_snippet_choices(choices, selection.clone(), cx);
9969 }
9970
9971 // If snippet state is not at the last tabstop, push it back on the stack
9972 if snippet.active_index + 1 < snippet.ranges.len() {
9973 self.snippet_stack.push(snippet);
9974 }
9975 return true;
9976 }
9977 }
9978
9979 false
9980 }
9981
9982 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9983 self.transact(window, cx, |this, window, cx| {
9984 this.select_all(&SelectAll, window, cx);
9985 this.insert("", window, cx);
9986 });
9987 }
9988
9989 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9990 if self.read_only(cx) {
9991 return;
9992 }
9993 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9994 self.transact(window, cx, |this, window, cx| {
9995 this.select_autoclose_pair(window, cx);
9996 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9997 if !this.linked_edit_ranges.is_empty() {
9998 let selections = this.selections.all::<MultiBufferPoint>(cx);
9999 let snapshot = this.buffer.read(cx).snapshot(cx);
10000
10001 for selection in selections.iter() {
10002 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10003 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10004 if selection_start.buffer_id != selection_end.buffer_id {
10005 continue;
10006 }
10007 if let Some(ranges) =
10008 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10009 {
10010 for (buffer, entries) in ranges {
10011 linked_ranges.entry(buffer).or_default().extend(entries);
10012 }
10013 }
10014 }
10015 }
10016
10017 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
10018 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10019 for selection in &mut selections {
10020 if selection.is_empty() {
10021 let old_head = selection.head();
10022 let mut new_head =
10023 movement::left(&display_map, old_head.to_display_point(&display_map))
10024 .to_point(&display_map);
10025 if let Some((buffer, line_buffer_range)) = display_map
10026 .buffer_snapshot()
10027 .buffer_line_for_row(MultiBufferRow(old_head.row))
10028 {
10029 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10030 let indent_len = match indent_size.kind {
10031 IndentKind::Space => {
10032 buffer.settings_at(line_buffer_range.start, cx).tab_size
10033 }
10034 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10035 };
10036 if old_head.column <= indent_size.len && old_head.column > 0 {
10037 let indent_len = indent_len.get();
10038 new_head = cmp::min(
10039 new_head,
10040 MultiBufferPoint::new(
10041 old_head.row,
10042 ((old_head.column - 1) / indent_len) * indent_len,
10043 ),
10044 );
10045 }
10046 }
10047
10048 selection.set_head(new_head, SelectionGoal::None);
10049 }
10050 }
10051
10052 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10053 this.insert("", window, cx);
10054 let empty_str: Arc<str> = Arc::from("");
10055 for (buffer, edits) in linked_ranges {
10056 let snapshot = buffer.read(cx).snapshot();
10057 use text::ToPoint as TP;
10058
10059 let edits = edits
10060 .into_iter()
10061 .map(|range| {
10062 let end_point = TP::to_point(&range.end, &snapshot);
10063 let mut start_point = TP::to_point(&range.start, &snapshot);
10064
10065 if end_point == start_point {
10066 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10067 .saturating_sub(1);
10068 start_point =
10069 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10070 };
10071
10072 (start_point..end_point, empty_str.clone())
10073 })
10074 .sorted_by_key(|(range, _)| range.start)
10075 .collect::<Vec<_>>();
10076 buffer.update(cx, |this, cx| {
10077 this.edit(edits, None, cx);
10078 })
10079 }
10080 this.refresh_edit_prediction(true, false, window, cx);
10081 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
10082 });
10083 }
10084
10085 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10086 if self.read_only(cx) {
10087 return;
10088 }
10089 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10090 self.transact(window, cx, |this, window, cx| {
10091 this.change_selections(Default::default(), window, cx, |s| {
10092 s.move_with(|map, selection| {
10093 if selection.is_empty() {
10094 let cursor = movement::right(map, selection.head());
10095 selection.end = cursor;
10096 selection.reversed = true;
10097 selection.goal = SelectionGoal::None;
10098 }
10099 })
10100 });
10101 this.insert("", window, cx);
10102 this.refresh_edit_prediction(true, false, window, cx);
10103 });
10104 }
10105
10106 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10107 if self.mode.is_single_line() {
10108 cx.propagate();
10109 return;
10110 }
10111
10112 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10113 if self.move_to_prev_snippet_tabstop(window, cx) {
10114 return;
10115 }
10116 self.outdent(&Outdent, window, cx);
10117 }
10118
10119 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10120 if self.mode.is_single_line() {
10121 cx.propagate();
10122 return;
10123 }
10124
10125 if self.move_to_next_snippet_tabstop(window, cx) {
10126 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10127 return;
10128 }
10129 if self.read_only(cx) {
10130 return;
10131 }
10132 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10133 let mut selections = self.selections.all_adjusted(cx);
10134 let buffer = self.buffer.read(cx);
10135 let snapshot = buffer.snapshot(cx);
10136 let rows_iter = selections.iter().map(|s| s.head().row);
10137 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10138
10139 let has_some_cursor_in_whitespace = selections
10140 .iter()
10141 .filter(|selection| selection.is_empty())
10142 .any(|selection| {
10143 let cursor = selection.head();
10144 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10145 cursor.column < current_indent.len
10146 });
10147
10148 let mut edits = Vec::new();
10149 let mut prev_edited_row = 0;
10150 let mut row_delta = 0;
10151 for selection in &mut selections {
10152 if selection.start.row != prev_edited_row {
10153 row_delta = 0;
10154 }
10155 prev_edited_row = selection.end.row;
10156
10157 // If the selection is non-empty, then increase the indentation of the selected lines.
10158 if !selection.is_empty() {
10159 row_delta =
10160 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10161 continue;
10162 }
10163
10164 let cursor = selection.head();
10165 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10166 if let Some(suggested_indent) =
10167 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10168 {
10169 // Don't do anything if already at suggested indent
10170 // and there is any other cursor which is not
10171 if has_some_cursor_in_whitespace
10172 && cursor.column == current_indent.len
10173 && current_indent.len == suggested_indent.len
10174 {
10175 continue;
10176 }
10177
10178 // Adjust line and move cursor to suggested indent
10179 // if cursor is not at suggested indent
10180 if cursor.column < suggested_indent.len
10181 && cursor.column <= current_indent.len
10182 && current_indent.len <= suggested_indent.len
10183 {
10184 selection.start = Point::new(cursor.row, suggested_indent.len);
10185 selection.end = selection.start;
10186 if row_delta == 0 {
10187 edits.extend(Buffer::edit_for_indent_size_adjustment(
10188 cursor.row,
10189 current_indent,
10190 suggested_indent,
10191 ));
10192 row_delta = suggested_indent.len - current_indent.len;
10193 }
10194 continue;
10195 }
10196
10197 // If current indent is more than suggested indent
10198 // only move cursor to current indent and skip indent
10199 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10200 selection.start = Point::new(cursor.row, current_indent.len);
10201 selection.end = selection.start;
10202 continue;
10203 }
10204 }
10205
10206 // Otherwise, insert a hard or soft tab.
10207 let settings = buffer.language_settings_at(cursor, cx);
10208 let tab_size = if settings.hard_tabs {
10209 IndentSize::tab()
10210 } else {
10211 let tab_size = settings.tab_size.get();
10212 let indent_remainder = snapshot
10213 .text_for_range(Point::new(cursor.row, 0)..cursor)
10214 .flat_map(str::chars)
10215 .fold(row_delta % tab_size, |counter: u32, c| {
10216 if c == '\t' {
10217 0
10218 } else {
10219 (counter + 1) % tab_size
10220 }
10221 });
10222
10223 let chars_to_next_tab_stop = tab_size - indent_remainder;
10224 IndentSize::spaces(chars_to_next_tab_stop)
10225 };
10226 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10227 selection.end = selection.start;
10228 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10229 row_delta += tab_size.len;
10230 }
10231
10232 self.transact(window, cx, |this, window, cx| {
10233 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10234 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10235 this.refresh_edit_prediction(true, false, window, cx);
10236 });
10237 }
10238
10239 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10240 if self.read_only(cx) {
10241 return;
10242 }
10243 if self.mode.is_single_line() {
10244 cx.propagate();
10245 return;
10246 }
10247
10248 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10249 let mut selections = self.selections.all::<Point>(cx);
10250 let mut prev_edited_row = 0;
10251 let mut row_delta = 0;
10252 let mut edits = Vec::new();
10253 let buffer = self.buffer.read(cx);
10254 let snapshot = buffer.snapshot(cx);
10255 for selection in &mut selections {
10256 if selection.start.row != prev_edited_row {
10257 row_delta = 0;
10258 }
10259 prev_edited_row = selection.end.row;
10260
10261 row_delta =
10262 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10263 }
10264
10265 self.transact(window, cx, |this, window, cx| {
10266 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10267 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10268 });
10269 }
10270
10271 fn indent_selection(
10272 buffer: &MultiBuffer,
10273 snapshot: &MultiBufferSnapshot,
10274 selection: &mut Selection<Point>,
10275 edits: &mut Vec<(Range<Point>, String)>,
10276 delta_for_start_row: u32,
10277 cx: &App,
10278 ) -> u32 {
10279 let settings = buffer.language_settings_at(selection.start, cx);
10280 let tab_size = settings.tab_size.get();
10281 let indent_kind = if settings.hard_tabs {
10282 IndentKind::Tab
10283 } else {
10284 IndentKind::Space
10285 };
10286 let mut start_row = selection.start.row;
10287 let mut end_row = selection.end.row + 1;
10288
10289 // If a selection ends at the beginning of a line, don't indent
10290 // that last line.
10291 if selection.end.column == 0 && selection.end.row > selection.start.row {
10292 end_row -= 1;
10293 }
10294
10295 // Avoid re-indenting a row that has already been indented by a
10296 // previous selection, but still update this selection's column
10297 // to reflect that indentation.
10298 if delta_for_start_row > 0 {
10299 start_row += 1;
10300 selection.start.column += delta_for_start_row;
10301 if selection.end.row == selection.start.row {
10302 selection.end.column += delta_for_start_row;
10303 }
10304 }
10305
10306 let mut delta_for_end_row = 0;
10307 let has_multiple_rows = start_row + 1 != end_row;
10308 for row in start_row..end_row {
10309 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10310 let indent_delta = match (current_indent.kind, indent_kind) {
10311 (IndentKind::Space, IndentKind::Space) => {
10312 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10313 IndentSize::spaces(columns_to_next_tab_stop)
10314 }
10315 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10316 (_, IndentKind::Tab) => IndentSize::tab(),
10317 };
10318
10319 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10320 0
10321 } else {
10322 selection.start.column
10323 };
10324 let row_start = Point::new(row, start);
10325 edits.push((
10326 row_start..row_start,
10327 indent_delta.chars().collect::<String>(),
10328 ));
10329
10330 // Update this selection's endpoints to reflect the indentation.
10331 if row == selection.start.row {
10332 selection.start.column += indent_delta.len;
10333 }
10334 if row == selection.end.row {
10335 selection.end.column += indent_delta.len;
10336 delta_for_end_row = indent_delta.len;
10337 }
10338 }
10339
10340 if selection.start.row == selection.end.row {
10341 delta_for_start_row + delta_for_end_row
10342 } else {
10343 delta_for_end_row
10344 }
10345 }
10346
10347 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10348 if self.read_only(cx) {
10349 return;
10350 }
10351 if self.mode.is_single_line() {
10352 cx.propagate();
10353 return;
10354 }
10355
10356 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10357 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10358 let selections = self.selections.all::<Point>(cx);
10359 let mut deletion_ranges = Vec::new();
10360 let mut last_outdent = None;
10361 {
10362 let buffer = self.buffer.read(cx);
10363 let snapshot = buffer.snapshot(cx);
10364 for selection in &selections {
10365 let settings = buffer.language_settings_at(selection.start, cx);
10366 let tab_size = settings.tab_size.get();
10367 let mut rows = selection.spanned_rows(false, &display_map);
10368
10369 // Avoid re-outdenting a row that has already been outdented by a
10370 // previous selection.
10371 if let Some(last_row) = last_outdent
10372 && last_row == rows.start
10373 {
10374 rows.start = rows.start.next_row();
10375 }
10376 let has_multiple_rows = rows.len() > 1;
10377 for row in rows.iter_rows() {
10378 let indent_size = snapshot.indent_size_for_line(row);
10379 if indent_size.len > 0 {
10380 let deletion_len = match indent_size.kind {
10381 IndentKind::Space => {
10382 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10383 if columns_to_prev_tab_stop == 0 {
10384 tab_size
10385 } else {
10386 columns_to_prev_tab_stop
10387 }
10388 }
10389 IndentKind::Tab => 1,
10390 };
10391 let start = if has_multiple_rows
10392 || deletion_len > selection.start.column
10393 || indent_size.len < selection.start.column
10394 {
10395 0
10396 } else {
10397 selection.start.column - deletion_len
10398 };
10399 deletion_ranges.push(
10400 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10401 );
10402 last_outdent = Some(row);
10403 }
10404 }
10405 }
10406 }
10407
10408 self.transact(window, cx, |this, window, cx| {
10409 this.buffer.update(cx, |buffer, cx| {
10410 let empty_str: Arc<str> = Arc::default();
10411 buffer.edit(
10412 deletion_ranges
10413 .into_iter()
10414 .map(|range| (range, empty_str.clone())),
10415 None,
10416 cx,
10417 );
10418 });
10419 let selections = this.selections.all::<usize>(cx);
10420 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10421 });
10422 }
10423
10424 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10425 if self.read_only(cx) {
10426 return;
10427 }
10428 if self.mode.is_single_line() {
10429 cx.propagate();
10430 return;
10431 }
10432
10433 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10434 let selections = self
10435 .selections
10436 .all::<usize>(cx)
10437 .into_iter()
10438 .map(|s| s.range());
10439
10440 self.transact(window, cx, |this, window, cx| {
10441 this.buffer.update(cx, |buffer, cx| {
10442 buffer.autoindent_ranges(selections, cx);
10443 });
10444 let selections = this.selections.all::<usize>(cx);
10445 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10446 });
10447 }
10448
10449 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10450 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10451 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10452 let selections = self.selections.all::<Point>(cx);
10453
10454 let mut new_cursors = Vec::new();
10455 let mut edit_ranges = Vec::new();
10456 let mut selections = selections.iter().peekable();
10457 while let Some(selection) = selections.next() {
10458 let mut rows = selection.spanned_rows(false, &display_map);
10459
10460 // Accumulate contiguous regions of rows that we want to delete.
10461 while let Some(next_selection) = selections.peek() {
10462 let next_rows = next_selection.spanned_rows(false, &display_map);
10463 if next_rows.start <= rows.end {
10464 rows.end = next_rows.end;
10465 selections.next().unwrap();
10466 } else {
10467 break;
10468 }
10469 }
10470
10471 let buffer = display_map.buffer_snapshot();
10472 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10473 let edit_end = if buffer.max_point().row >= rows.end.0 {
10474 // If there's a line after the range, delete the \n from the end of the row range
10475 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer)
10476 } else {
10477 // If there isn't a line after the range, delete the \n from the line before the
10478 // start of the row range
10479 edit_start = edit_start.saturating_sub(1);
10480 buffer.len()
10481 };
10482
10483 let (cursor, goal) = movement::down_by_rows(
10484 &display_map,
10485 selection.head().to_display_point(&display_map),
10486 rows.len() as u32,
10487 selection.goal,
10488 false,
10489 &self.text_layout_details(window),
10490 );
10491
10492 new_cursors.push((
10493 selection.id,
10494 buffer.anchor_after(cursor.to_point(&display_map)),
10495 goal,
10496 ));
10497 edit_ranges.push(edit_start..edit_end);
10498 }
10499
10500 self.transact(window, cx, |this, window, cx| {
10501 let buffer = this.buffer.update(cx, |buffer, cx| {
10502 let empty_str: Arc<str> = Arc::default();
10503 buffer.edit(
10504 edit_ranges
10505 .into_iter()
10506 .map(|range| (range, empty_str.clone())),
10507 None,
10508 cx,
10509 );
10510 buffer.snapshot(cx)
10511 });
10512 let new_selections = new_cursors
10513 .into_iter()
10514 .map(|(id, cursor, goal)| {
10515 let cursor = cursor.to_point(&buffer);
10516 Selection {
10517 id,
10518 start: cursor,
10519 end: cursor,
10520 reversed: false,
10521 goal,
10522 }
10523 })
10524 .collect();
10525
10526 this.change_selections(Default::default(), window, cx, |s| {
10527 s.select(new_selections);
10528 });
10529 });
10530 }
10531
10532 pub fn join_lines_impl(
10533 &mut self,
10534 insert_whitespace: bool,
10535 window: &mut Window,
10536 cx: &mut Context<Self>,
10537 ) {
10538 if self.read_only(cx) {
10539 return;
10540 }
10541 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10542 for selection in self.selections.all::<Point>(cx) {
10543 let start = MultiBufferRow(selection.start.row);
10544 // Treat single line selections as if they include the next line. Otherwise this action
10545 // would do nothing for single line selections individual cursors.
10546 let end = if selection.start.row == selection.end.row {
10547 MultiBufferRow(selection.start.row + 1)
10548 } else {
10549 MultiBufferRow(selection.end.row)
10550 };
10551
10552 if let Some(last_row_range) = row_ranges.last_mut()
10553 && start <= last_row_range.end
10554 {
10555 last_row_range.end = end;
10556 continue;
10557 }
10558 row_ranges.push(start..end);
10559 }
10560
10561 let snapshot = self.buffer.read(cx).snapshot(cx);
10562 let mut cursor_positions = Vec::new();
10563 for row_range in &row_ranges {
10564 let anchor = snapshot.anchor_before(Point::new(
10565 row_range.end.previous_row().0,
10566 snapshot.line_len(row_range.end.previous_row()),
10567 ));
10568 cursor_positions.push(anchor..anchor);
10569 }
10570
10571 self.transact(window, cx, |this, window, cx| {
10572 for row_range in row_ranges.into_iter().rev() {
10573 for row in row_range.iter_rows().rev() {
10574 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10575 let next_line_row = row.next_row();
10576 let indent = snapshot.indent_size_for_line(next_line_row);
10577 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10578
10579 let replace =
10580 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10581 " "
10582 } else {
10583 ""
10584 };
10585
10586 this.buffer.update(cx, |buffer, cx| {
10587 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10588 });
10589 }
10590 }
10591
10592 this.change_selections(Default::default(), window, cx, |s| {
10593 s.select_anchor_ranges(cursor_positions)
10594 });
10595 });
10596 }
10597
10598 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10599 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10600 self.join_lines_impl(true, window, cx);
10601 }
10602
10603 pub fn sort_lines_case_sensitive(
10604 &mut self,
10605 _: &SortLinesCaseSensitive,
10606 window: &mut Window,
10607 cx: &mut Context<Self>,
10608 ) {
10609 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10610 }
10611
10612 pub fn sort_lines_by_length(
10613 &mut self,
10614 _: &SortLinesByLength,
10615 window: &mut Window,
10616 cx: &mut Context<Self>,
10617 ) {
10618 self.manipulate_immutable_lines(window, cx, |lines| {
10619 lines.sort_by_key(|&line| line.chars().count())
10620 })
10621 }
10622
10623 pub fn sort_lines_case_insensitive(
10624 &mut self,
10625 _: &SortLinesCaseInsensitive,
10626 window: &mut Window,
10627 cx: &mut Context<Self>,
10628 ) {
10629 self.manipulate_immutable_lines(window, cx, |lines| {
10630 lines.sort_by_key(|line| line.to_lowercase())
10631 })
10632 }
10633
10634 pub fn unique_lines_case_insensitive(
10635 &mut self,
10636 _: &UniqueLinesCaseInsensitive,
10637 window: &mut Window,
10638 cx: &mut Context<Self>,
10639 ) {
10640 self.manipulate_immutable_lines(window, cx, |lines| {
10641 let mut seen = HashSet::default();
10642 lines.retain(|line| seen.insert(line.to_lowercase()));
10643 })
10644 }
10645
10646 pub fn unique_lines_case_sensitive(
10647 &mut self,
10648 _: &UniqueLinesCaseSensitive,
10649 window: &mut Window,
10650 cx: &mut Context<Self>,
10651 ) {
10652 self.manipulate_immutable_lines(window, cx, |lines| {
10653 let mut seen = HashSet::default();
10654 lines.retain(|line| seen.insert(*line));
10655 })
10656 }
10657
10658 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10659 let snapshot = self.buffer.read(cx).snapshot(cx);
10660 for selection in self.selections.disjoint_anchors_arc().iter() {
10661 if snapshot
10662 .language_at(selection.start)
10663 .and_then(|lang| lang.config().wrap_characters.as_ref())
10664 .is_some()
10665 {
10666 return true;
10667 }
10668 }
10669 false
10670 }
10671
10672 fn wrap_selections_in_tag(
10673 &mut self,
10674 _: &WrapSelectionsInTag,
10675 window: &mut Window,
10676 cx: &mut Context<Self>,
10677 ) {
10678 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10679
10680 let snapshot = self.buffer.read(cx).snapshot(cx);
10681
10682 let mut edits = Vec::new();
10683 let mut boundaries = Vec::new();
10684
10685 for selection in self.selections.all::<Point>(cx).iter() {
10686 let Some(wrap_config) = snapshot
10687 .language_at(selection.start)
10688 .and_then(|lang| lang.config().wrap_characters.clone())
10689 else {
10690 continue;
10691 };
10692
10693 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10694 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10695
10696 let start_before = snapshot.anchor_before(selection.start);
10697 let end_after = snapshot.anchor_after(selection.end);
10698
10699 edits.push((start_before..start_before, open_tag));
10700 edits.push((end_after..end_after, close_tag));
10701
10702 boundaries.push((
10703 start_before,
10704 end_after,
10705 wrap_config.start_prefix.len(),
10706 wrap_config.end_suffix.len(),
10707 ));
10708 }
10709
10710 if edits.is_empty() {
10711 return;
10712 }
10713
10714 self.transact(window, cx, |this, window, cx| {
10715 let buffer = this.buffer.update(cx, |buffer, cx| {
10716 buffer.edit(edits, None, cx);
10717 buffer.snapshot(cx)
10718 });
10719
10720 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10721 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10722 boundaries.into_iter()
10723 {
10724 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10725 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10726 new_selections.push(open_offset..open_offset);
10727 new_selections.push(close_offset..close_offset);
10728 }
10729
10730 this.change_selections(Default::default(), window, cx, |s| {
10731 s.select_ranges(new_selections);
10732 });
10733
10734 this.request_autoscroll(Autoscroll::fit(), cx);
10735 });
10736 }
10737
10738 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10739 let Some(project) = self.project.clone() else {
10740 return;
10741 };
10742 self.reload(project, window, cx)
10743 .detach_and_notify_err(window, cx);
10744 }
10745
10746 pub fn restore_file(
10747 &mut self,
10748 _: &::git::RestoreFile,
10749 window: &mut Window,
10750 cx: &mut Context<Self>,
10751 ) {
10752 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10753 let mut buffer_ids = HashSet::default();
10754 let snapshot = self.buffer().read(cx).snapshot(cx);
10755 for selection in self.selections.all::<usize>(cx) {
10756 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10757 }
10758
10759 let buffer = self.buffer().read(cx);
10760 let ranges = buffer_ids
10761 .into_iter()
10762 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10763 .collect::<Vec<_>>();
10764
10765 self.restore_hunks_in_ranges(ranges, window, cx);
10766 }
10767
10768 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10769 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10770 let selections = self
10771 .selections
10772 .all(cx)
10773 .into_iter()
10774 .map(|s| s.range())
10775 .collect();
10776 self.restore_hunks_in_ranges(selections, window, cx);
10777 }
10778
10779 pub fn restore_hunks_in_ranges(
10780 &mut self,
10781 ranges: Vec<Range<Point>>,
10782 window: &mut Window,
10783 cx: &mut Context<Editor>,
10784 ) {
10785 let mut revert_changes = HashMap::default();
10786 let chunk_by = self
10787 .snapshot(window, cx)
10788 .hunks_for_ranges(ranges)
10789 .into_iter()
10790 .chunk_by(|hunk| hunk.buffer_id);
10791 for (buffer_id, hunks) in &chunk_by {
10792 let hunks = hunks.collect::<Vec<_>>();
10793 for hunk in &hunks {
10794 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10795 }
10796 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10797 }
10798 drop(chunk_by);
10799 if !revert_changes.is_empty() {
10800 self.transact(window, cx, |editor, window, cx| {
10801 editor.restore(revert_changes, window, cx);
10802 });
10803 }
10804 }
10805
10806 pub fn open_active_item_in_terminal(
10807 &mut self,
10808 _: &OpenInTerminal,
10809 window: &mut Window,
10810 cx: &mut Context<Self>,
10811 ) {
10812 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10813 let project_path = buffer.read(cx).project_path(cx)?;
10814 let project = self.project()?.read(cx);
10815 let entry = project.entry_for_path(&project_path, cx)?;
10816 let parent = match &entry.canonical_path {
10817 Some(canonical_path) => canonical_path.to_path_buf(),
10818 None => project.absolute_path(&project_path, cx)?,
10819 }
10820 .parent()?
10821 .to_path_buf();
10822 Some(parent)
10823 }) {
10824 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10825 }
10826 }
10827
10828 fn set_breakpoint_context_menu(
10829 &mut self,
10830 display_row: DisplayRow,
10831 position: Option<Anchor>,
10832 clicked_point: gpui::Point<Pixels>,
10833 window: &mut Window,
10834 cx: &mut Context<Self>,
10835 ) {
10836 let source = self
10837 .buffer
10838 .read(cx)
10839 .snapshot(cx)
10840 .anchor_before(Point::new(display_row.0, 0u32));
10841
10842 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10843
10844 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10845 self,
10846 source,
10847 clicked_point,
10848 context_menu,
10849 window,
10850 cx,
10851 );
10852 }
10853
10854 fn add_edit_breakpoint_block(
10855 &mut self,
10856 anchor: Anchor,
10857 breakpoint: &Breakpoint,
10858 edit_action: BreakpointPromptEditAction,
10859 window: &mut Window,
10860 cx: &mut Context<Self>,
10861 ) {
10862 let weak_editor = cx.weak_entity();
10863 let bp_prompt = cx.new(|cx| {
10864 BreakpointPromptEditor::new(
10865 weak_editor,
10866 anchor,
10867 breakpoint.clone(),
10868 edit_action,
10869 window,
10870 cx,
10871 )
10872 });
10873
10874 let height = bp_prompt.update(cx, |this, cx| {
10875 this.prompt
10876 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10877 });
10878 let cloned_prompt = bp_prompt.clone();
10879 let blocks = vec![BlockProperties {
10880 style: BlockStyle::Sticky,
10881 placement: BlockPlacement::Above(anchor),
10882 height: Some(height),
10883 render: Arc::new(move |cx| {
10884 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10885 cloned_prompt.clone().into_any_element()
10886 }),
10887 priority: 0,
10888 }];
10889
10890 let focus_handle = bp_prompt.focus_handle(cx);
10891 window.focus(&focus_handle);
10892
10893 let block_ids = self.insert_blocks(blocks, None, cx);
10894 bp_prompt.update(cx, |prompt, _| {
10895 prompt.add_block_ids(block_ids);
10896 });
10897 }
10898
10899 pub(crate) fn breakpoint_at_row(
10900 &self,
10901 row: u32,
10902 window: &mut Window,
10903 cx: &mut Context<Self>,
10904 ) -> Option<(Anchor, Breakpoint)> {
10905 let snapshot = self.snapshot(window, cx);
10906 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10907
10908 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10909 }
10910
10911 pub(crate) fn breakpoint_at_anchor(
10912 &self,
10913 breakpoint_position: Anchor,
10914 snapshot: &EditorSnapshot,
10915 cx: &mut Context<Self>,
10916 ) -> Option<(Anchor, Breakpoint)> {
10917 let buffer = self
10918 .buffer
10919 .read(cx)
10920 .buffer_for_anchor(breakpoint_position, cx)?;
10921
10922 let enclosing_excerpt = breakpoint_position.excerpt_id;
10923 let buffer_snapshot = buffer.read(cx).snapshot();
10924
10925 let row = buffer_snapshot
10926 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10927 .row;
10928
10929 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10930 let anchor_end = snapshot
10931 .buffer_snapshot()
10932 .anchor_after(Point::new(row, line_len));
10933
10934 self.breakpoint_store
10935 .as_ref()?
10936 .read_with(cx, |breakpoint_store, cx| {
10937 breakpoint_store
10938 .breakpoints(
10939 &buffer,
10940 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10941 &buffer_snapshot,
10942 cx,
10943 )
10944 .next()
10945 .and_then(|(bp, _)| {
10946 let breakpoint_row = buffer_snapshot
10947 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10948 .row;
10949
10950 if breakpoint_row == row {
10951 snapshot
10952 .buffer_snapshot()
10953 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10954 .map(|position| (position, bp.bp.clone()))
10955 } else {
10956 None
10957 }
10958 })
10959 })
10960 }
10961
10962 pub fn edit_log_breakpoint(
10963 &mut self,
10964 _: &EditLogBreakpoint,
10965 window: &mut Window,
10966 cx: &mut Context<Self>,
10967 ) {
10968 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10969 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10970 message: None,
10971 state: BreakpointState::Enabled,
10972 condition: None,
10973 hit_condition: None,
10974 });
10975
10976 self.add_edit_breakpoint_block(
10977 anchor,
10978 &breakpoint,
10979 BreakpointPromptEditAction::Log,
10980 window,
10981 cx,
10982 );
10983 }
10984 }
10985
10986 fn breakpoints_at_cursors(
10987 &self,
10988 window: &mut Window,
10989 cx: &mut Context<Self>,
10990 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10991 let snapshot = self.snapshot(window, cx);
10992 let cursors = self
10993 .selections
10994 .disjoint_anchors_arc()
10995 .iter()
10996 .map(|selection| {
10997 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10998
10999 let breakpoint_position = self
11000 .breakpoint_at_row(cursor_position.row, window, cx)
11001 .map(|bp| bp.0)
11002 .unwrap_or_else(|| {
11003 snapshot
11004 .display_snapshot
11005 .buffer_snapshot()
11006 .anchor_after(Point::new(cursor_position.row, 0))
11007 });
11008
11009 let breakpoint = self
11010 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11011 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11012
11013 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11014 })
11015 // 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.
11016 .collect::<HashMap<Anchor, _>>();
11017
11018 cursors.into_iter().collect()
11019 }
11020
11021 pub fn enable_breakpoint(
11022 &mut self,
11023 _: &crate::actions::EnableBreakpoint,
11024 window: &mut Window,
11025 cx: &mut Context<Self>,
11026 ) {
11027 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11028 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11029 continue;
11030 };
11031 self.edit_breakpoint_at_anchor(
11032 anchor,
11033 breakpoint,
11034 BreakpointEditAction::InvertState,
11035 cx,
11036 );
11037 }
11038 }
11039
11040 pub fn disable_breakpoint(
11041 &mut self,
11042 _: &crate::actions::DisableBreakpoint,
11043 window: &mut Window,
11044 cx: &mut Context<Self>,
11045 ) {
11046 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11047 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11048 continue;
11049 };
11050 self.edit_breakpoint_at_anchor(
11051 anchor,
11052 breakpoint,
11053 BreakpointEditAction::InvertState,
11054 cx,
11055 );
11056 }
11057 }
11058
11059 pub fn toggle_breakpoint(
11060 &mut self,
11061 _: &crate::actions::ToggleBreakpoint,
11062 window: &mut Window,
11063 cx: &mut Context<Self>,
11064 ) {
11065 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11066 if let Some(breakpoint) = breakpoint {
11067 self.edit_breakpoint_at_anchor(
11068 anchor,
11069 breakpoint,
11070 BreakpointEditAction::Toggle,
11071 cx,
11072 );
11073 } else {
11074 self.edit_breakpoint_at_anchor(
11075 anchor,
11076 Breakpoint::new_standard(),
11077 BreakpointEditAction::Toggle,
11078 cx,
11079 );
11080 }
11081 }
11082 }
11083
11084 pub fn edit_breakpoint_at_anchor(
11085 &mut self,
11086 breakpoint_position: Anchor,
11087 breakpoint: Breakpoint,
11088 edit_action: BreakpointEditAction,
11089 cx: &mut Context<Self>,
11090 ) {
11091 let Some(breakpoint_store) = &self.breakpoint_store else {
11092 return;
11093 };
11094
11095 let Some(buffer) = self
11096 .buffer
11097 .read(cx)
11098 .buffer_for_anchor(breakpoint_position, cx)
11099 else {
11100 return;
11101 };
11102
11103 breakpoint_store.update(cx, |breakpoint_store, cx| {
11104 breakpoint_store.toggle_breakpoint(
11105 buffer,
11106 BreakpointWithPosition {
11107 position: breakpoint_position.text_anchor,
11108 bp: breakpoint,
11109 },
11110 edit_action,
11111 cx,
11112 );
11113 });
11114
11115 cx.notify();
11116 }
11117
11118 #[cfg(any(test, feature = "test-support"))]
11119 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11120 self.breakpoint_store.clone()
11121 }
11122
11123 pub fn prepare_restore_change(
11124 &self,
11125 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11126 hunk: &MultiBufferDiffHunk,
11127 cx: &mut App,
11128 ) -> Option<()> {
11129 if hunk.is_created_file() {
11130 return None;
11131 }
11132 let buffer = self.buffer.read(cx);
11133 let diff = buffer.diff_for(hunk.buffer_id)?;
11134 let buffer = buffer.buffer(hunk.buffer_id)?;
11135 let buffer = buffer.read(cx);
11136 let original_text = diff
11137 .read(cx)
11138 .base_text()
11139 .as_rope()
11140 .slice(hunk.diff_base_byte_range.clone());
11141 let buffer_snapshot = buffer.snapshot();
11142 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11143 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11144 probe
11145 .0
11146 .start
11147 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11148 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11149 }) {
11150 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11151 Some(())
11152 } else {
11153 None
11154 }
11155 }
11156
11157 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11158 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11159 }
11160
11161 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11162 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11163 }
11164
11165 fn manipulate_lines<M>(
11166 &mut self,
11167 window: &mut Window,
11168 cx: &mut Context<Self>,
11169 mut manipulate: M,
11170 ) where
11171 M: FnMut(&str) -> LineManipulationResult,
11172 {
11173 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11174
11175 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11176 let buffer = self.buffer.read(cx).snapshot(cx);
11177
11178 let mut edits = Vec::new();
11179
11180 let selections = self.selections.all::<Point>(cx);
11181 let mut selections = selections.iter().peekable();
11182 let mut contiguous_row_selections = Vec::new();
11183 let mut new_selections = Vec::new();
11184 let mut added_lines = 0;
11185 let mut removed_lines = 0;
11186
11187 while let Some(selection) = selections.next() {
11188 let (start_row, end_row) = consume_contiguous_rows(
11189 &mut contiguous_row_selections,
11190 selection,
11191 &display_map,
11192 &mut selections,
11193 );
11194
11195 let start_point = Point::new(start_row.0, 0);
11196 let end_point = Point::new(
11197 end_row.previous_row().0,
11198 buffer.line_len(end_row.previous_row()),
11199 );
11200 let text = buffer
11201 .text_for_range(start_point..end_point)
11202 .collect::<String>();
11203
11204 let LineManipulationResult {
11205 new_text,
11206 line_count_before,
11207 line_count_after,
11208 } = manipulate(&text);
11209
11210 edits.push((start_point..end_point, new_text));
11211
11212 // Selections must change based on added and removed line count
11213 let start_row =
11214 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11215 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11216 new_selections.push(Selection {
11217 id: selection.id,
11218 start: start_row,
11219 end: end_row,
11220 goal: SelectionGoal::None,
11221 reversed: selection.reversed,
11222 });
11223
11224 if line_count_after > line_count_before {
11225 added_lines += line_count_after - line_count_before;
11226 } else if line_count_before > line_count_after {
11227 removed_lines += line_count_before - line_count_after;
11228 }
11229 }
11230
11231 self.transact(window, cx, |this, window, cx| {
11232 let buffer = this.buffer.update(cx, |buffer, cx| {
11233 buffer.edit(edits, None, cx);
11234 buffer.snapshot(cx)
11235 });
11236
11237 // Recalculate offsets on newly edited buffer
11238 let new_selections = new_selections
11239 .iter()
11240 .map(|s| {
11241 let start_point = Point::new(s.start.0, 0);
11242 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11243 Selection {
11244 id: s.id,
11245 start: buffer.point_to_offset(start_point),
11246 end: buffer.point_to_offset(end_point),
11247 goal: s.goal,
11248 reversed: s.reversed,
11249 }
11250 })
11251 .collect();
11252
11253 this.change_selections(Default::default(), window, cx, |s| {
11254 s.select(new_selections);
11255 });
11256
11257 this.request_autoscroll(Autoscroll::fit(), cx);
11258 });
11259 }
11260
11261 fn manipulate_immutable_lines<Fn>(
11262 &mut self,
11263 window: &mut Window,
11264 cx: &mut Context<Self>,
11265 mut callback: Fn,
11266 ) where
11267 Fn: FnMut(&mut Vec<&str>),
11268 {
11269 self.manipulate_lines(window, cx, |text| {
11270 let mut lines: Vec<&str> = text.split('\n').collect();
11271 let line_count_before = lines.len();
11272
11273 callback(&mut lines);
11274
11275 LineManipulationResult {
11276 new_text: lines.join("\n"),
11277 line_count_before,
11278 line_count_after: lines.len(),
11279 }
11280 });
11281 }
11282
11283 fn manipulate_mutable_lines<Fn>(
11284 &mut self,
11285 window: &mut Window,
11286 cx: &mut Context<Self>,
11287 mut callback: Fn,
11288 ) where
11289 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11290 {
11291 self.manipulate_lines(window, cx, |text| {
11292 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11293 let line_count_before = lines.len();
11294
11295 callback(&mut lines);
11296
11297 LineManipulationResult {
11298 new_text: lines.join("\n"),
11299 line_count_before,
11300 line_count_after: lines.len(),
11301 }
11302 });
11303 }
11304
11305 pub fn convert_indentation_to_spaces(
11306 &mut self,
11307 _: &ConvertIndentationToSpaces,
11308 window: &mut Window,
11309 cx: &mut Context<Self>,
11310 ) {
11311 let settings = self.buffer.read(cx).language_settings(cx);
11312 let tab_size = settings.tab_size.get() as usize;
11313
11314 self.manipulate_mutable_lines(window, cx, |lines| {
11315 // Allocates a reasonably sized scratch buffer once for the whole loop
11316 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11317 // Avoids recomputing spaces that could be inserted many times
11318 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11319 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11320 .collect();
11321
11322 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11323 let mut chars = line.as_ref().chars();
11324 let mut col = 0;
11325 let mut changed = false;
11326
11327 for ch in chars.by_ref() {
11328 match ch {
11329 ' ' => {
11330 reindented_line.push(' ');
11331 col += 1;
11332 }
11333 '\t' => {
11334 // \t are converted to spaces depending on the current column
11335 let spaces_len = tab_size - (col % tab_size);
11336 reindented_line.extend(&space_cache[spaces_len - 1]);
11337 col += spaces_len;
11338 changed = true;
11339 }
11340 _ => {
11341 // If we dont append before break, the character is consumed
11342 reindented_line.push(ch);
11343 break;
11344 }
11345 }
11346 }
11347
11348 if !changed {
11349 reindented_line.clear();
11350 continue;
11351 }
11352 // Append the rest of the line and replace old reference with new one
11353 reindented_line.extend(chars);
11354 *line = Cow::Owned(reindented_line.clone());
11355 reindented_line.clear();
11356 }
11357 });
11358 }
11359
11360 pub fn convert_indentation_to_tabs(
11361 &mut self,
11362 _: &ConvertIndentationToTabs,
11363 window: &mut Window,
11364 cx: &mut Context<Self>,
11365 ) {
11366 let settings = self.buffer.read(cx).language_settings(cx);
11367 let tab_size = settings.tab_size.get() as usize;
11368
11369 self.manipulate_mutable_lines(window, cx, |lines| {
11370 // Allocates a reasonably sized buffer once for the whole loop
11371 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11372 // Avoids recomputing spaces that could be inserted many times
11373 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11374 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11375 .collect();
11376
11377 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11378 let mut chars = line.chars();
11379 let mut spaces_count = 0;
11380 let mut first_non_indent_char = None;
11381 let mut changed = false;
11382
11383 for ch in chars.by_ref() {
11384 match ch {
11385 ' ' => {
11386 // Keep track of spaces. Append \t when we reach tab_size
11387 spaces_count += 1;
11388 changed = true;
11389 if spaces_count == tab_size {
11390 reindented_line.push('\t');
11391 spaces_count = 0;
11392 }
11393 }
11394 '\t' => {
11395 reindented_line.push('\t');
11396 spaces_count = 0;
11397 }
11398 _ => {
11399 // Dont append it yet, we might have remaining spaces
11400 first_non_indent_char = Some(ch);
11401 break;
11402 }
11403 }
11404 }
11405
11406 if !changed {
11407 reindented_line.clear();
11408 continue;
11409 }
11410 // Remaining spaces that didn't make a full tab stop
11411 if spaces_count > 0 {
11412 reindented_line.extend(&space_cache[spaces_count - 1]);
11413 }
11414 // If we consume an extra character that was not indentation, add it back
11415 if let Some(extra_char) = first_non_indent_char {
11416 reindented_line.push(extra_char);
11417 }
11418 // Append the rest of the line and replace old reference with new one
11419 reindented_line.extend(chars);
11420 *line = Cow::Owned(reindented_line.clone());
11421 reindented_line.clear();
11422 }
11423 });
11424 }
11425
11426 pub fn convert_to_upper_case(
11427 &mut self,
11428 _: &ConvertToUpperCase,
11429 window: &mut Window,
11430 cx: &mut Context<Self>,
11431 ) {
11432 self.manipulate_text(window, cx, |text| text.to_uppercase())
11433 }
11434
11435 pub fn convert_to_lower_case(
11436 &mut self,
11437 _: &ConvertToLowerCase,
11438 window: &mut Window,
11439 cx: &mut Context<Self>,
11440 ) {
11441 self.manipulate_text(window, cx, |text| text.to_lowercase())
11442 }
11443
11444 pub fn convert_to_title_case(
11445 &mut self,
11446 _: &ConvertToTitleCase,
11447 window: &mut Window,
11448 cx: &mut Context<Self>,
11449 ) {
11450 self.manipulate_text(window, cx, |text| {
11451 text.split('\n')
11452 .map(|line| line.to_case(Case::Title))
11453 .join("\n")
11454 })
11455 }
11456
11457 pub fn convert_to_snake_case(
11458 &mut self,
11459 _: &ConvertToSnakeCase,
11460 window: &mut Window,
11461 cx: &mut Context<Self>,
11462 ) {
11463 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11464 }
11465
11466 pub fn convert_to_kebab_case(
11467 &mut self,
11468 _: &ConvertToKebabCase,
11469 window: &mut Window,
11470 cx: &mut Context<Self>,
11471 ) {
11472 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11473 }
11474
11475 pub fn convert_to_upper_camel_case(
11476 &mut self,
11477 _: &ConvertToUpperCamelCase,
11478 window: &mut Window,
11479 cx: &mut Context<Self>,
11480 ) {
11481 self.manipulate_text(window, cx, |text| {
11482 text.split('\n')
11483 .map(|line| line.to_case(Case::UpperCamel))
11484 .join("\n")
11485 })
11486 }
11487
11488 pub fn convert_to_lower_camel_case(
11489 &mut self,
11490 _: &ConvertToLowerCamelCase,
11491 window: &mut Window,
11492 cx: &mut Context<Self>,
11493 ) {
11494 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11495 }
11496
11497 pub fn convert_to_opposite_case(
11498 &mut self,
11499 _: &ConvertToOppositeCase,
11500 window: &mut Window,
11501 cx: &mut Context<Self>,
11502 ) {
11503 self.manipulate_text(window, cx, |text| {
11504 text.chars()
11505 .fold(String::with_capacity(text.len()), |mut t, c| {
11506 if c.is_uppercase() {
11507 t.extend(c.to_lowercase());
11508 } else {
11509 t.extend(c.to_uppercase());
11510 }
11511 t
11512 })
11513 })
11514 }
11515
11516 pub fn convert_to_sentence_case(
11517 &mut self,
11518 _: &ConvertToSentenceCase,
11519 window: &mut Window,
11520 cx: &mut Context<Self>,
11521 ) {
11522 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11523 }
11524
11525 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11526 self.manipulate_text(window, cx, |text| {
11527 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11528 if has_upper_case_characters {
11529 text.to_lowercase()
11530 } else {
11531 text.to_uppercase()
11532 }
11533 })
11534 }
11535
11536 pub fn convert_to_rot13(
11537 &mut self,
11538 _: &ConvertToRot13,
11539 window: &mut Window,
11540 cx: &mut Context<Self>,
11541 ) {
11542 self.manipulate_text(window, cx, |text| {
11543 text.chars()
11544 .map(|c| match c {
11545 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11546 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11547 _ => c,
11548 })
11549 .collect()
11550 })
11551 }
11552
11553 pub fn convert_to_rot47(
11554 &mut self,
11555 _: &ConvertToRot47,
11556 window: &mut Window,
11557 cx: &mut Context<Self>,
11558 ) {
11559 self.manipulate_text(window, cx, |text| {
11560 text.chars()
11561 .map(|c| {
11562 let code_point = c as u32;
11563 if code_point >= 33 && code_point <= 126 {
11564 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11565 }
11566 c
11567 })
11568 .collect()
11569 })
11570 }
11571
11572 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11573 where
11574 Fn: FnMut(&str) -> String,
11575 {
11576 let buffer = self.buffer.read(cx).snapshot(cx);
11577
11578 let mut new_selections = Vec::new();
11579 let mut edits = Vec::new();
11580 let mut selection_adjustment = 0i32;
11581
11582 for selection in self.selections.all_adjusted(cx) {
11583 let selection_is_empty = selection.is_empty();
11584
11585 let (start, end) = if selection_is_empty {
11586 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11587 (word_range.start, word_range.end)
11588 } else {
11589 (
11590 buffer.point_to_offset(selection.start),
11591 buffer.point_to_offset(selection.end),
11592 )
11593 };
11594
11595 let text = buffer.text_for_range(start..end).collect::<String>();
11596 let old_length = text.len() as i32;
11597 let text = callback(&text);
11598
11599 new_selections.push(Selection {
11600 start: (start as i32 - selection_adjustment) as usize,
11601 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11602 goal: SelectionGoal::None,
11603 id: selection.id,
11604 reversed: selection.reversed,
11605 });
11606
11607 selection_adjustment += old_length - text.len() as i32;
11608
11609 edits.push((start..end, text));
11610 }
11611
11612 self.transact(window, cx, |this, window, cx| {
11613 this.buffer.update(cx, |buffer, cx| {
11614 buffer.edit(edits, None, cx);
11615 });
11616
11617 this.change_selections(Default::default(), window, cx, |s| {
11618 s.select(new_selections);
11619 });
11620
11621 this.request_autoscroll(Autoscroll::fit(), cx);
11622 });
11623 }
11624
11625 pub fn move_selection_on_drop(
11626 &mut self,
11627 selection: &Selection<Anchor>,
11628 target: DisplayPoint,
11629 is_cut: bool,
11630 window: &mut Window,
11631 cx: &mut Context<Self>,
11632 ) {
11633 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11634 let buffer = display_map.buffer_snapshot();
11635 let mut edits = Vec::new();
11636 let insert_point = display_map
11637 .clip_point(target, Bias::Left)
11638 .to_point(&display_map);
11639 let text = buffer
11640 .text_for_range(selection.start..selection.end)
11641 .collect::<String>();
11642 if is_cut {
11643 edits.push(((selection.start..selection.end), String::new()));
11644 }
11645 let insert_anchor = buffer.anchor_before(insert_point);
11646 edits.push(((insert_anchor..insert_anchor), text));
11647 let last_edit_start = insert_anchor.bias_left(buffer);
11648 let last_edit_end = insert_anchor.bias_right(buffer);
11649 self.transact(window, cx, |this, window, cx| {
11650 this.buffer.update(cx, |buffer, cx| {
11651 buffer.edit(edits, None, cx);
11652 });
11653 this.change_selections(Default::default(), window, cx, |s| {
11654 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11655 });
11656 });
11657 }
11658
11659 pub fn clear_selection_drag_state(&mut self) {
11660 self.selection_drag_state = SelectionDragState::None;
11661 }
11662
11663 pub fn duplicate(
11664 &mut self,
11665 upwards: bool,
11666 whole_lines: bool,
11667 window: &mut Window,
11668 cx: &mut Context<Self>,
11669 ) {
11670 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11671
11672 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11673 let buffer = display_map.buffer_snapshot();
11674 let selections = self.selections.all::<Point>(cx);
11675
11676 let mut edits = Vec::new();
11677 let mut selections_iter = selections.iter().peekable();
11678 while let Some(selection) = selections_iter.next() {
11679 let mut rows = selection.spanned_rows(false, &display_map);
11680 // duplicate line-wise
11681 if whole_lines || selection.start == selection.end {
11682 // Avoid duplicating the same lines twice.
11683 while let Some(next_selection) = selections_iter.peek() {
11684 let next_rows = next_selection.spanned_rows(false, &display_map);
11685 if next_rows.start < rows.end {
11686 rows.end = next_rows.end;
11687 selections_iter.next().unwrap();
11688 } else {
11689 break;
11690 }
11691 }
11692
11693 // Copy the text from the selected row region and splice it either at the start
11694 // or end of the region.
11695 let start = Point::new(rows.start.0, 0);
11696 let end = Point::new(
11697 rows.end.previous_row().0,
11698 buffer.line_len(rows.end.previous_row()),
11699 );
11700
11701 let mut text = buffer.text_for_range(start..end).collect::<String>();
11702
11703 let insert_location = if upwards {
11704 // When duplicating upward, we need to insert before the current line.
11705 // If we're on the last line and it doesn't end with a newline,
11706 // we need to add a newline before the duplicated content.
11707 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11708 && buffer.max_point().column > 0
11709 && !text.ends_with('\n');
11710
11711 if needs_leading_newline {
11712 text.insert(0, '\n');
11713 end
11714 } else {
11715 text.push('\n');
11716 Point::new(rows.end.0, 0)
11717 }
11718 } else {
11719 text.push('\n');
11720 start
11721 };
11722 edits.push((insert_location..insert_location, text));
11723 } else {
11724 // duplicate character-wise
11725 let start = selection.start;
11726 let end = selection.end;
11727 let text = buffer.text_for_range(start..end).collect::<String>();
11728 edits.push((selection.end..selection.end, text));
11729 }
11730 }
11731
11732 self.transact(window, cx, |this, _, cx| {
11733 this.buffer.update(cx, |buffer, cx| {
11734 buffer.edit(edits, None, cx);
11735 });
11736
11737 this.request_autoscroll(Autoscroll::fit(), cx);
11738 });
11739 }
11740
11741 pub fn duplicate_line_up(
11742 &mut self,
11743 _: &DuplicateLineUp,
11744 window: &mut Window,
11745 cx: &mut Context<Self>,
11746 ) {
11747 self.duplicate(true, true, window, cx);
11748 }
11749
11750 pub fn duplicate_line_down(
11751 &mut self,
11752 _: &DuplicateLineDown,
11753 window: &mut Window,
11754 cx: &mut Context<Self>,
11755 ) {
11756 self.duplicate(false, true, window, cx);
11757 }
11758
11759 pub fn duplicate_selection(
11760 &mut self,
11761 _: &DuplicateSelection,
11762 window: &mut Window,
11763 cx: &mut Context<Self>,
11764 ) {
11765 self.duplicate(false, false, window, cx);
11766 }
11767
11768 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11769 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11770 if self.mode.is_single_line() {
11771 cx.propagate();
11772 return;
11773 }
11774
11775 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11776 let buffer = self.buffer.read(cx).snapshot(cx);
11777
11778 let mut edits = Vec::new();
11779 let mut unfold_ranges = Vec::new();
11780 let mut refold_creases = Vec::new();
11781
11782 let selections = self.selections.all::<Point>(cx);
11783 let mut selections = selections.iter().peekable();
11784 let mut contiguous_row_selections = Vec::new();
11785 let mut new_selections = Vec::new();
11786
11787 while let Some(selection) = selections.next() {
11788 // Find all the selections that span a contiguous row range
11789 let (start_row, end_row) = consume_contiguous_rows(
11790 &mut contiguous_row_selections,
11791 selection,
11792 &display_map,
11793 &mut selections,
11794 );
11795
11796 // Move the text spanned by the row range to be before the line preceding the row range
11797 if start_row.0 > 0 {
11798 let range_to_move = Point::new(
11799 start_row.previous_row().0,
11800 buffer.line_len(start_row.previous_row()),
11801 )
11802 ..Point::new(
11803 end_row.previous_row().0,
11804 buffer.line_len(end_row.previous_row()),
11805 );
11806 let insertion_point = display_map
11807 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11808 .0;
11809
11810 // Don't move lines across excerpts
11811 if buffer
11812 .excerpt_containing(insertion_point..range_to_move.end)
11813 .is_some()
11814 {
11815 let text = buffer
11816 .text_for_range(range_to_move.clone())
11817 .flat_map(|s| s.chars())
11818 .skip(1)
11819 .chain(['\n'])
11820 .collect::<String>();
11821
11822 edits.push((
11823 buffer.anchor_after(range_to_move.start)
11824 ..buffer.anchor_before(range_to_move.end),
11825 String::new(),
11826 ));
11827 let insertion_anchor = buffer.anchor_after(insertion_point);
11828 edits.push((insertion_anchor..insertion_anchor, text));
11829
11830 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11831
11832 // Move selections up
11833 new_selections.extend(contiguous_row_selections.drain(..).map(
11834 |mut selection| {
11835 selection.start.row -= row_delta;
11836 selection.end.row -= row_delta;
11837 selection
11838 },
11839 ));
11840
11841 // Move folds up
11842 unfold_ranges.push(range_to_move.clone());
11843 for fold in display_map.folds_in_range(
11844 buffer.anchor_before(range_to_move.start)
11845 ..buffer.anchor_after(range_to_move.end),
11846 ) {
11847 let mut start = fold.range.start.to_point(&buffer);
11848 let mut end = fold.range.end.to_point(&buffer);
11849 start.row -= row_delta;
11850 end.row -= row_delta;
11851 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11852 }
11853 }
11854 }
11855
11856 // If we didn't move line(s), preserve the existing selections
11857 new_selections.append(&mut contiguous_row_selections);
11858 }
11859
11860 self.transact(window, cx, |this, window, cx| {
11861 this.unfold_ranges(&unfold_ranges, true, true, cx);
11862 this.buffer.update(cx, |buffer, cx| {
11863 for (range, text) in edits {
11864 buffer.edit([(range, text)], None, cx);
11865 }
11866 });
11867 this.fold_creases(refold_creases, true, window, cx);
11868 this.change_selections(Default::default(), window, cx, |s| {
11869 s.select(new_selections);
11870 })
11871 });
11872 }
11873
11874 pub fn move_line_down(
11875 &mut self,
11876 _: &MoveLineDown,
11877 window: &mut Window,
11878 cx: &mut Context<Self>,
11879 ) {
11880 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11881 if self.mode.is_single_line() {
11882 cx.propagate();
11883 return;
11884 }
11885
11886 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11887 let buffer = self.buffer.read(cx).snapshot(cx);
11888
11889 let mut edits = Vec::new();
11890 let mut unfold_ranges = Vec::new();
11891 let mut refold_creases = Vec::new();
11892
11893 let selections = self.selections.all::<Point>(cx);
11894 let mut selections = selections.iter().peekable();
11895 let mut contiguous_row_selections = Vec::new();
11896 let mut new_selections = Vec::new();
11897
11898 while let Some(selection) = selections.next() {
11899 // Find all the selections that span a contiguous row range
11900 let (start_row, end_row) = consume_contiguous_rows(
11901 &mut contiguous_row_selections,
11902 selection,
11903 &display_map,
11904 &mut selections,
11905 );
11906
11907 // Move the text spanned by the row range to be after the last line of the row range
11908 if end_row.0 <= buffer.max_point().row {
11909 let range_to_move =
11910 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11911 let insertion_point = display_map
11912 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11913 .0;
11914
11915 // Don't move lines across excerpt boundaries
11916 if buffer
11917 .excerpt_containing(range_to_move.start..insertion_point)
11918 .is_some()
11919 {
11920 let mut text = String::from("\n");
11921 text.extend(buffer.text_for_range(range_to_move.clone()));
11922 text.pop(); // Drop trailing newline
11923 edits.push((
11924 buffer.anchor_after(range_to_move.start)
11925 ..buffer.anchor_before(range_to_move.end),
11926 String::new(),
11927 ));
11928 let insertion_anchor = buffer.anchor_after(insertion_point);
11929 edits.push((insertion_anchor..insertion_anchor, text));
11930
11931 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11932
11933 // Move selections down
11934 new_selections.extend(contiguous_row_selections.drain(..).map(
11935 |mut selection| {
11936 selection.start.row += row_delta;
11937 selection.end.row += row_delta;
11938 selection
11939 },
11940 ));
11941
11942 // Move folds down
11943 unfold_ranges.push(range_to_move.clone());
11944 for fold in display_map.folds_in_range(
11945 buffer.anchor_before(range_to_move.start)
11946 ..buffer.anchor_after(range_to_move.end),
11947 ) {
11948 let mut start = fold.range.start.to_point(&buffer);
11949 let mut end = fold.range.end.to_point(&buffer);
11950 start.row += row_delta;
11951 end.row += row_delta;
11952 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11953 }
11954 }
11955 }
11956
11957 // If we didn't move line(s), preserve the existing selections
11958 new_selections.append(&mut contiguous_row_selections);
11959 }
11960
11961 self.transact(window, cx, |this, window, cx| {
11962 this.unfold_ranges(&unfold_ranges, true, true, cx);
11963 this.buffer.update(cx, |buffer, cx| {
11964 for (range, text) in edits {
11965 buffer.edit([(range, text)], None, cx);
11966 }
11967 });
11968 this.fold_creases(refold_creases, true, window, cx);
11969 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11970 });
11971 }
11972
11973 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11974 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11975 let text_layout_details = &self.text_layout_details(window);
11976 self.transact(window, cx, |this, window, cx| {
11977 let edits = this.change_selections(Default::default(), window, cx, |s| {
11978 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11979 s.move_with(|display_map, selection| {
11980 if !selection.is_empty() {
11981 return;
11982 }
11983
11984 let mut head = selection.head();
11985 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11986 if head.column() == display_map.line_len(head.row()) {
11987 transpose_offset = display_map
11988 .buffer_snapshot()
11989 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11990 }
11991
11992 if transpose_offset == 0 {
11993 return;
11994 }
11995
11996 *head.column_mut() += 1;
11997 head = display_map.clip_point(head, Bias::Right);
11998 let goal = SelectionGoal::HorizontalPosition(
11999 display_map
12000 .x_for_display_point(head, text_layout_details)
12001 .into(),
12002 );
12003 selection.collapse_to(head, goal);
12004
12005 let transpose_start = display_map
12006 .buffer_snapshot()
12007 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12008 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12009 let transpose_end = display_map
12010 .buffer_snapshot()
12011 .clip_offset(transpose_offset + 1, Bias::Right);
12012 if let Some(ch) = display_map
12013 .buffer_snapshot()
12014 .chars_at(transpose_start)
12015 .next()
12016 {
12017 edits.push((transpose_start..transpose_offset, String::new()));
12018 edits.push((transpose_end..transpose_end, ch.to_string()));
12019 }
12020 }
12021 });
12022 edits
12023 });
12024 this.buffer
12025 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12026 let selections = this.selections.all::<usize>(cx);
12027 this.change_selections(Default::default(), window, cx, |s| {
12028 s.select(selections);
12029 });
12030 });
12031 }
12032
12033 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12034 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12035 if self.mode.is_single_line() {
12036 cx.propagate();
12037 return;
12038 }
12039
12040 self.rewrap_impl(RewrapOptions::default(), cx)
12041 }
12042
12043 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12044 let buffer = self.buffer.read(cx).snapshot(cx);
12045 let selections = self.selections.all::<Point>(cx);
12046
12047 #[derive(Clone, Debug, PartialEq)]
12048 enum CommentFormat {
12049 /// single line comment, with prefix for line
12050 Line(String),
12051 /// single line within a block comment, with prefix for line
12052 BlockLine(String),
12053 /// a single line of a block comment that includes the initial delimiter
12054 BlockCommentWithStart(BlockCommentConfig),
12055 /// a single line of a block comment that includes the ending delimiter
12056 BlockCommentWithEnd(BlockCommentConfig),
12057 }
12058
12059 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12060 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12061 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12062 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12063 .peekable();
12064
12065 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12066 row
12067 } else {
12068 return Vec::new();
12069 };
12070
12071 let language_settings = buffer.language_settings_at(selection.head(), cx);
12072 let language_scope = buffer.language_scope_at(selection.head());
12073
12074 let indent_and_prefix_for_row =
12075 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12076 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12077 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12078 &language_scope
12079 {
12080 let indent_end = Point::new(row, indent.len);
12081 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12082 let line_text_after_indent = buffer
12083 .text_for_range(indent_end..line_end)
12084 .collect::<String>();
12085
12086 let is_within_comment_override = buffer
12087 .language_scope_at(indent_end)
12088 .is_some_and(|scope| scope.override_name() == Some("comment"));
12089 let comment_delimiters = if is_within_comment_override {
12090 // we are within a comment syntax node, but we don't
12091 // yet know what kind of comment: block, doc or line
12092 match (
12093 language_scope.documentation_comment(),
12094 language_scope.block_comment(),
12095 ) {
12096 (Some(config), _) | (_, Some(config))
12097 if buffer.contains_str_at(indent_end, &config.start) =>
12098 {
12099 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12100 }
12101 (Some(config), _) | (_, Some(config))
12102 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12103 {
12104 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12105 }
12106 (Some(config), _) | (_, Some(config))
12107 if buffer.contains_str_at(indent_end, &config.prefix) =>
12108 {
12109 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12110 }
12111 (_, _) => language_scope
12112 .line_comment_prefixes()
12113 .iter()
12114 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12115 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12116 }
12117 } else {
12118 // we not in an overridden comment node, but we may
12119 // be within a non-overridden line comment node
12120 language_scope
12121 .line_comment_prefixes()
12122 .iter()
12123 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12124 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12125 };
12126
12127 let rewrap_prefix = language_scope
12128 .rewrap_prefixes()
12129 .iter()
12130 .find_map(|prefix_regex| {
12131 prefix_regex.find(&line_text_after_indent).map(|mat| {
12132 if mat.start() == 0 {
12133 Some(mat.as_str().to_string())
12134 } else {
12135 None
12136 }
12137 })
12138 })
12139 .flatten();
12140 (comment_delimiters, rewrap_prefix)
12141 } else {
12142 (None, None)
12143 };
12144 (indent, comment_prefix, rewrap_prefix)
12145 };
12146
12147 let mut ranges = Vec::new();
12148 let from_empty_selection = selection.is_empty();
12149
12150 let mut current_range_start = first_row;
12151 let mut prev_row = first_row;
12152 let (
12153 mut current_range_indent,
12154 mut current_range_comment_delimiters,
12155 mut current_range_rewrap_prefix,
12156 ) = indent_and_prefix_for_row(first_row);
12157
12158 for row in non_blank_rows_iter.skip(1) {
12159 let has_paragraph_break = row > prev_row + 1;
12160
12161 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12162 indent_and_prefix_for_row(row);
12163
12164 let has_indent_change = row_indent != current_range_indent;
12165 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12166
12167 let has_boundary_change = has_comment_change
12168 || row_rewrap_prefix.is_some()
12169 || (has_indent_change && current_range_comment_delimiters.is_some());
12170
12171 if has_paragraph_break || has_boundary_change {
12172 ranges.push((
12173 language_settings.clone(),
12174 Point::new(current_range_start, 0)
12175 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12176 current_range_indent,
12177 current_range_comment_delimiters.clone(),
12178 current_range_rewrap_prefix.clone(),
12179 from_empty_selection,
12180 ));
12181 current_range_start = row;
12182 current_range_indent = row_indent;
12183 current_range_comment_delimiters = row_comment_delimiters;
12184 current_range_rewrap_prefix = row_rewrap_prefix;
12185 }
12186 prev_row = row;
12187 }
12188
12189 ranges.push((
12190 language_settings.clone(),
12191 Point::new(current_range_start, 0)
12192 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12193 current_range_indent,
12194 current_range_comment_delimiters,
12195 current_range_rewrap_prefix,
12196 from_empty_selection,
12197 ));
12198
12199 ranges
12200 });
12201
12202 let mut edits = Vec::new();
12203 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12204
12205 for (
12206 language_settings,
12207 wrap_range,
12208 mut indent_size,
12209 comment_prefix,
12210 rewrap_prefix,
12211 from_empty_selection,
12212 ) in wrap_ranges
12213 {
12214 let mut start_row = wrap_range.start.row;
12215 let mut end_row = wrap_range.end.row;
12216
12217 // Skip selections that overlap with a range that has already been rewrapped.
12218 let selection_range = start_row..end_row;
12219 if rewrapped_row_ranges
12220 .iter()
12221 .any(|range| range.overlaps(&selection_range))
12222 {
12223 continue;
12224 }
12225
12226 let tab_size = language_settings.tab_size;
12227
12228 let (line_prefix, inside_comment) = match &comment_prefix {
12229 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12230 (Some(prefix.as_str()), true)
12231 }
12232 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12233 (Some(prefix.as_ref()), true)
12234 }
12235 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12236 start: _,
12237 end: _,
12238 prefix,
12239 tab_size,
12240 })) => {
12241 indent_size.len += tab_size;
12242 (Some(prefix.as_ref()), true)
12243 }
12244 None => (None, false),
12245 };
12246 let indent_prefix = indent_size.chars().collect::<String>();
12247 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12248
12249 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12250 RewrapBehavior::InComments => inside_comment,
12251 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12252 RewrapBehavior::Anywhere => true,
12253 };
12254
12255 let should_rewrap = options.override_language_settings
12256 || allow_rewrap_based_on_language
12257 || self.hard_wrap.is_some();
12258 if !should_rewrap {
12259 continue;
12260 }
12261
12262 if from_empty_selection {
12263 'expand_upwards: while start_row > 0 {
12264 let prev_row = start_row - 1;
12265 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12266 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12267 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12268 {
12269 start_row = prev_row;
12270 } else {
12271 break 'expand_upwards;
12272 }
12273 }
12274
12275 'expand_downwards: while end_row < buffer.max_point().row {
12276 let next_row = end_row + 1;
12277 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12278 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12279 && !buffer.is_line_blank(MultiBufferRow(next_row))
12280 {
12281 end_row = next_row;
12282 } else {
12283 break 'expand_downwards;
12284 }
12285 }
12286 }
12287
12288 let start = Point::new(start_row, 0);
12289 let start_offset = ToOffset::to_offset(&start, &buffer);
12290 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12291 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12292 let mut first_line_delimiter = None;
12293 let mut last_line_delimiter = None;
12294 let Some(lines_without_prefixes) = selection_text
12295 .lines()
12296 .enumerate()
12297 .map(|(ix, line)| {
12298 let line_trimmed = line.trim_start();
12299 if rewrap_prefix.is_some() && ix > 0 {
12300 Ok(line_trimmed)
12301 } else if let Some(
12302 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12303 start,
12304 prefix,
12305 end,
12306 tab_size,
12307 })
12308 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12309 start,
12310 prefix,
12311 end,
12312 tab_size,
12313 }),
12314 ) = &comment_prefix
12315 {
12316 let line_trimmed = line_trimmed
12317 .strip_prefix(start.as_ref())
12318 .map(|s| {
12319 let mut indent_size = indent_size;
12320 indent_size.len -= tab_size;
12321 let indent_prefix: String = indent_size.chars().collect();
12322 first_line_delimiter = Some((indent_prefix, start));
12323 s.trim_start()
12324 })
12325 .unwrap_or(line_trimmed);
12326 let line_trimmed = line_trimmed
12327 .strip_suffix(end.as_ref())
12328 .map(|s| {
12329 last_line_delimiter = Some(end);
12330 s.trim_end()
12331 })
12332 .unwrap_or(line_trimmed);
12333 let line_trimmed = line_trimmed
12334 .strip_prefix(prefix.as_ref())
12335 .unwrap_or(line_trimmed);
12336 Ok(line_trimmed)
12337 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12338 line_trimmed.strip_prefix(prefix).with_context(|| {
12339 format!("line did not start with prefix {prefix:?}: {line:?}")
12340 })
12341 } else {
12342 line_trimmed
12343 .strip_prefix(&line_prefix.trim_start())
12344 .with_context(|| {
12345 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12346 })
12347 }
12348 })
12349 .collect::<Result<Vec<_>, _>>()
12350 .log_err()
12351 else {
12352 continue;
12353 };
12354
12355 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12356 buffer
12357 .language_settings_at(Point::new(start_row, 0), cx)
12358 .preferred_line_length as usize
12359 });
12360
12361 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12362 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12363 } else {
12364 line_prefix.clone()
12365 };
12366
12367 let wrapped_text = {
12368 let mut wrapped_text = wrap_with_prefix(
12369 line_prefix,
12370 subsequent_lines_prefix,
12371 lines_without_prefixes.join("\n"),
12372 wrap_column,
12373 tab_size,
12374 options.preserve_existing_whitespace,
12375 );
12376
12377 if let Some((indent, delimiter)) = first_line_delimiter {
12378 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12379 }
12380 if let Some(last_line) = last_line_delimiter {
12381 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12382 }
12383
12384 wrapped_text
12385 };
12386
12387 // TODO: should always use char-based diff while still supporting cursor behavior that
12388 // matches vim.
12389 let mut diff_options = DiffOptions::default();
12390 if options.override_language_settings {
12391 diff_options.max_word_diff_len = 0;
12392 diff_options.max_word_diff_line_count = 0;
12393 } else {
12394 diff_options.max_word_diff_len = usize::MAX;
12395 diff_options.max_word_diff_line_count = usize::MAX;
12396 }
12397
12398 for (old_range, new_text) in
12399 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12400 {
12401 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12402 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12403 edits.push((edit_start..edit_end, new_text));
12404 }
12405
12406 rewrapped_row_ranges.push(start_row..=end_row);
12407 }
12408
12409 self.buffer
12410 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12411 }
12412
12413 pub fn cut_common(
12414 &mut self,
12415 cut_no_selection_line: bool,
12416 window: &mut Window,
12417 cx: &mut Context<Self>,
12418 ) -> ClipboardItem {
12419 let mut text = String::new();
12420 let buffer = self.buffer.read(cx).snapshot(cx);
12421 let mut selections = self.selections.all::<Point>(cx);
12422 let mut clipboard_selections = Vec::with_capacity(selections.len());
12423 {
12424 let max_point = buffer.max_point();
12425 let mut is_first = true;
12426 for selection in &mut selections {
12427 let is_entire_line =
12428 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12429 if is_entire_line {
12430 selection.start = Point::new(selection.start.row, 0);
12431 if !selection.is_empty() && selection.end.column == 0 {
12432 selection.end = cmp::min(max_point, selection.end);
12433 } else {
12434 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12435 }
12436 selection.goal = SelectionGoal::None;
12437 }
12438 if is_first {
12439 is_first = false;
12440 } else {
12441 text += "\n";
12442 }
12443 let mut len = 0;
12444 for chunk in buffer.text_for_range(selection.start..selection.end) {
12445 text.push_str(chunk);
12446 len += chunk.len();
12447 }
12448 clipboard_selections.push(ClipboardSelection {
12449 len,
12450 is_entire_line,
12451 first_line_indent: buffer
12452 .indent_size_for_line(MultiBufferRow(selection.start.row))
12453 .len,
12454 });
12455 }
12456 }
12457
12458 self.transact(window, cx, |this, window, cx| {
12459 this.change_selections(Default::default(), window, cx, |s| {
12460 s.select(selections);
12461 });
12462 this.insert("", window, cx);
12463 });
12464 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12465 }
12466
12467 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12468 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12469 let item = self.cut_common(true, window, cx);
12470 cx.write_to_clipboard(item);
12471 }
12472
12473 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12474 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12475 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12476 s.move_with(|snapshot, sel| {
12477 if sel.is_empty() {
12478 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12479 }
12480 if sel.is_empty() {
12481 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12482 }
12483 });
12484 });
12485 let item = self.cut_common(false, window, cx);
12486 cx.set_global(KillRing(item))
12487 }
12488
12489 pub fn kill_ring_yank(
12490 &mut self,
12491 _: &KillRingYank,
12492 window: &mut Window,
12493 cx: &mut Context<Self>,
12494 ) {
12495 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12496 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12497 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12498 (kill_ring.text().to_string(), kill_ring.metadata_json())
12499 } else {
12500 return;
12501 }
12502 } else {
12503 return;
12504 };
12505 self.do_paste(&text, metadata, false, window, cx);
12506 }
12507
12508 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12509 self.do_copy(true, cx);
12510 }
12511
12512 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12513 self.do_copy(false, cx);
12514 }
12515
12516 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12517 let selections = self.selections.all::<Point>(cx);
12518 let buffer = self.buffer.read(cx).read(cx);
12519 let mut text = String::new();
12520
12521 let mut clipboard_selections = Vec::with_capacity(selections.len());
12522 {
12523 let max_point = buffer.max_point();
12524 let mut is_first = true;
12525 for selection in &selections {
12526 let mut start = selection.start;
12527 let mut end = selection.end;
12528 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12529 let mut add_trailing_newline = false;
12530 if is_entire_line {
12531 start = Point::new(start.row, 0);
12532 let next_line_start = Point::new(end.row + 1, 0);
12533 if next_line_start <= max_point {
12534 end = next_line_start;
12535 } else {
12536 // We're on the last line without a trailing newline.
12537 // Copy to the end of the line and add a newline afterwards.
12538 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12539 add_trailing_newline = true;
12540 }
12541 }
12542
12543 let mut trimmed_selections = Vec::new();
12544 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12545 let row = MultiBufferRow(start.row);
12546 let first_indent = buffer.indent_size_for_line(row);
12547 if first_indent.len == 0 || start.column > first_indent.len {
12548 trimmed_selections.push(start..end);
12549 } else {
12550 trimmed_selections.push(
12551 Point::new(row.0, first_indent.len)
12552 ..Point::new(row.0, buffer.line_len(row)),
12553 );
12554 for row in start.row + 1..=end.row {
12555 let mut line_len = buffer.line_len(MultiBufferRow(row));
12556 if row == end.row {
12557 line_len = end.column;
12558 }
12559 if line_len == 0 {
12560 trimmed_selections
12561 .push(Point::new(row, 0)..Point::new(row, line_len));
12562 continue;
12563 }
12564 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12565 if row_indent_size.len >= first_indent.len {
12566 trimmed_selections.push(
12567 Point::new(row, first_indent.len)..Point::new(row, line_len),
12568 );
12569 } else {
12570 trimmed_selections.clear();
12571 trimmed_selections.push(start..end);
12572 break;
12573 }
12574 }
12575 }
12576 } else {
12577 trimmed_selections.push(start..end);
12578 }
12579
12580 for trimmed_range in trimmed_selections {
12581 if is_first {
12582 is_first = false;
12583 } else {
12584 text += "\n";
12585 }
12586 let mut len = 0;
12587 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12588 text.push_str(chunk);
12589 len += chunk.len();
12590 }
12591 if add_trailing_newline {
12592 text.push('\n');
12593 len += 1;
12594 }
12595 clipboard_selections.push(ClipboardSelection {
12596 len,
12597 is_entire_line,
12598 first_line_indent: buffer
12599 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12600 .len,
12601 });
12602 }
12603 }
12604 }
12605
12606 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12607 text,
12608 clipboard_selections,
12609 ));
12610 }
12611
12612 pub fn do_paste(
12613 &mut self,
12614 text: &String,
12615 clipboard_selections: Option<Vec<ClipboardSelection>>,
12616 handle_entire_lines: bool,
12617 window: &mut Window,
12618 cx: &mut Context<Self>,
12619 ) {
12620 if self.read_only(cx) {
12621 return;
12622 }
12623
12624 let clipboard_text = Cow::Borrowed(text.as_str());
12625
12626 self.transact(window, cx, |this, window, cx| {
12627 let had_active_edit_prediction = this.has_active_edit_prediction();
12628 let old_selections = this.selections.all::<usize>(cx);
12629 let cursor_offset = this.selections.last::<usize>(cx).head();
12630
12631 if let Some(mut clipboard_selections) = clipboard_selections {
12632 let all_selections_were_entire_line =
12633 clipboard_selections.iter().all(|s| s.is_entire_line);
12634 let first_selection_indent_column =
12635 clipboard_selections.first().map(|s| s.first_line_indent);
12636 if clipboard_selections.len() != old_selections.len() {
12637 clipboard_selections.drain(..);
12638 }
12639 let mut auto_indent_on_paste = true;
12640
12641 this.buffer.update(cx, |buffer, cx| {
12642 let snapshot = buffer.read(cx);
12643 auto_indent_on_paste = snapshot
12644 .language_settings_at(cursor_offset, cx)
12645 .auto_indent_on_paste;
12646
12647 let mut start_offset = 0;
12648 let mut edits = Vec::new();
12649 let mut original_indent_columns = Vec::new();
12650 for (ix, selection) in old_selections.iter().enumerate() {
12651 let to_insert;
12652 let entire_line;
12653 let original_indent_column;
12654 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12655 let end_offset = start_offset + clipboard_selection.len;
12656 to_insert = &clipboard_text[start_offset..end_offset];
12657 entire_line = clipboard_selection.is_entire_line;
12658 start_offset = end_offset + 1;
12659 original_indent_column = Some(clipboard_selection.first_line_indent);
12660 } else {
12661 to_insert = &*clipboard_text;
12662 entire_line = all_selections_were_entire_line;
12663 original_indent_column = first_selection_indent_column
12664 }
12665
12666 let (range, to_insert) =
12667 if selection.is_empty() && handle_entire_lines && entire_line {
12668 // If the corresponding selection was empty when this slice of the
12669 // clipboard text was written, then the entire line containing the
12670 // selection was copied. If this selection is also currently empty,
12671 // then paste the line before the current line of the buffer.
12672 let column = selection.start.to_point(&snapshot).column as usize;
12673 let line_start = selection.start - column;
12674 (line_start..line_start, Cow::Borrowed(to_insert))
12675 } else {
12676 let language = snapshot.language_at(selection.head());
12677 let range = selection.range();
12678 if let Some(language) = language
12679 && language.name() == "Markdown".into()
12680 {
12681 edit_for_markdown_paste(
12682 &snapshot,
12683 range,
12684 to_insert,
12685 url::Url::parse(to_insert).ok(),
12686 )
12687 } else {
12688 (range, Cow::Borrowed(to_insert))
12689 }
12690 };
12691
12692 edits.push((range, to_insert));
12693 original_indent_columns.push(original_indent_column);
12694 }
12695 drop(snapshot);
12696
12697 buffer.edit(
12698 edits,
12699 if auto_indent_on_paste {
12700 Some(AutoindentMode::Block {
12701 original_indent_columns,
12702 })
12703 } else {
12704 None
12705 },
12706 cx,
12707 );
12708 });
12709
12710 let selections = this.selections.all::<usize>(cx);
12711 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12712 } else {
12713 let url = url::Url::parse(&clipboard_text).ok();
12714
12715 let auto_indent_mode = if !clipboard_text.is_empty() {
12716 Some(AutoindentMode::Block {
12717 original_indent_columns: Vec::new(),
12718 })
12719 } else {
12720 None
12721 };
12722
12723 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12724 let snapshot = buffer.snapshot(cx);
12725
12726 let anchors = old_selections
12727 .iter()
12728 .map(|s| {
12729 let anchor = snapshot.anchor_after(s.head());
12730 s.map(|_| anchor)
12731 })
12732 .collect::<Vec<_>>();
12733
12734 let mut edits = Vec::new();
12735
12736 for selection in old_selections.iter() {
12737 let language = snapshot.language_at(selection.head());
12738 let range = selection.range();
12739
12740 let (edit_range, edit_text) = if let Some(language) = language
12741 && language.name() == "Markdown".into()
12742 {
12743 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12744 } else {
12745 (range, clipboard_text.clone())
12746 };
12747
12748 edits.push((edit_range, edit_text));
12749 }
12750
12751 drop(snapshot);
12752 buffer.edit(edits, auto_indent_mode, cx);
12753
12754 anchors
12755 });
12756
12757 this.change_selections(Default::default(), window, cx, |s| {
12758 s.select_anchors(selection_anchors);
12759 });
12760 }
12761
12762 let trigger_in_words =
12763 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12764
12765 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12766 });
12767 }
12768
12769 pub fn diff_clipboard_with_selection(
12770 &mut self,
12771 _: &DiffClipboardWithSelection,
12772 window: &mut Window,
12773 cx: &mut Context<Self>,
12774 ) {
12775 let selections = self.selections.all::<usize>(cx);
12776
12777 if selections.is_empty() {
12778 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12779 return;
12780 };
12781
12782 let clipboard_text = match cx.read_from_clipboard() {
12783 Some(item) => match item.entries().first() {
12784 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12785 _ => None,
12786 },
12787 None => None,
12788 };
12789
12790 let Some(clipboard_text) = clipboard_text else {
12791 log::warn!("Clipboard doesn't contain text.");
12792 return;
12793 };
12794
12795 window.dispatch_action(
12796 Box::new(DiffClipboardWithSelectionData {
12797 clipboard_text,
12798 editor: cx.entity(),
12799 }),
12800 cx,
12801 );
12802 }
12803
12804 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12805 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12806 if let Some(item) = cx.read_from_clipboard() {
12807 let entries = item.entries();
12808
12809 match entries.first() {
12810 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12811 // of all the pasted entries.
12812 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12813 .do_paste(
12814 clipboard_string.text(),
12815 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12816 true,
12817 window,
12818 cx,
12819 ),
12820 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12821 }
12822 }
12823 }
12824
12825 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12826 if self.read_only(cx) {
12827 return;
12828 }
12829
12830 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12831
12832 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12833 if let Some((selections, _)) =
12834 self.selection_history.transaction(transaction_id).cloned()
12835 {
12836 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12837 s.select_anchors(selections.to_vec());
12838 });
12839 } else {
12840 log::error!(
12841 "No entry in selection_history found for undo. \
12842 This may correspond to a bug where undo does not update the selection. \
12843 If this is occurring, please add details to \
12844 https://github.com/zed-industries/zed/issues/22692"
12845 );
12846 }
12847 self.request_autoscroll(Autoscroll::fit(), cx);
12848 self.unmark_text(window, cx);
12849 self.refresh_edit_prediction(true, false, window, cx);
12850 cx.emit(EditorEvent::Edited { transaction_id });
12851 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12852 }
12853 }
12854
12855 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12856 if self.read_only(cx) {
12857 return;
12858 }
12859
12860 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12861
12862 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12863 if let Some((_, Some(selections))) =
12864 self.selection_history.transaction(transaction_id).cloned()
12865 {
12866 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12867 s.select_anchors(selections.to_vec());
12868 });
12869 } else {
12870 log::error!(
12871 "No entry in selection_history found for redo. \
12872 This may correspond to a bug where undo does not update the selection. \
12873 If this is occurring, please add details to \
12874 https://github.com/zed-industries/zed/issues/22692"
12875 );
12876 }
12877 self.request_autoscroll(Autoscroll::fit(), cx);
12878 self.unmark_text(window, cx);
12879 self.refresh_edit_prediction(true, false, window, cx);
12880 cx.emit(EditorEvent::Edited { transaction_id });
12881 }
12882 }
12883
12884 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12885 self.buffer
12886 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12887 }
12888
12889 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12890 self.buffer
12891 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12892 }
12893
12894 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12895 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12896 self.change_selections(Default::default(), window, cx, |s| {
12897 s.move_with(|map, selection| {
12898 let cursor = if selection.is_empty() {
12899 movement::left(map, selection.start)
12900 } else {
12901 selection.start
12902 };
12903 selection.collapse_to(cursor, SelectionGoal::None);
12904 });
12905 })
12906 }
12907
12908 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12909 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12910 self.change_selections(Default::default(), window, cx, |s| {
12911 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12912 })
12913 }
12914
12915 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12916 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12917 self.change_selections(Default::default(), window, cx, |s| {
12918 s.move_with(|map, selection| {
12919 let cursor = if selection.is_empty() {
12920 movement::right(map, selection.end)
12921 } else {
12922 selection.end
12923 };
12924 selection.collapse_to(cursor, SelectionGoal::None)
12925 });
12926 })
12927 }
12928
12929 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12930 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12931 self.change_selections(Default::default(), window, cx, |s| {
12932 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12933 });
12934 }
12935
12936 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12937 if self.take_rename(true, window, cx).is_some() {
12938 return;
12939 }
12940
12941 if self.mode.is_single_line() {
12942 cx.propagate();
12943 return;
12944 }
12945
12946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12947
12948 let text_layout_details = &self.text_layout_details(window);
12949 let selection_count = self.selections.count();
12950 let first_selection = self.selections.first_anchor();
12951
12952 self.change_selections(Default::default(), window, cx, |s| {
12953 s.move_with(|map, selection| {
12954 if !selection.is_empty() {
12955 selection.goal = SelectionGoal::None;
12956 }
12957 let (cursor, goal) = movement::up(
12958 map,
12959 selection.start,
12960 selection.goal,
12961 false,
12962 text_layout_details,
12963 );
12964 selection.collapse_to(cursor, goal);
12965 });
12966 });
12967
12968 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12969 {
12970 cx.propagate();
12971 }
12972 }
12973
12974 pub fn move_up_by_lines(
12975 &mut self,
12976 action: &MoveUpByLines,
12977 window: &mut Window,
12978 cx: &mut Context<Self>,
12979 ) {
12980 if self.take_rename(true, window, cx).is_some() {
12981 return;
12982 }
12983
12984 if self.mode.is_single_line() {
12985 cx.propagate();
12986 return;
12987 }
12988
12989 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12990
12991 let text_layout_details = &self.text_layout_details(window);
12992
12993 self.change_selections(Default::default(), window, cx, |s| {
12994 s.move_with(|map, selection| {
12995 if !selection.is_empty() {
12996 selection.goal = SelectionGoal::None;
12997 }
12998 let (cursor, goal) = movement::up_by_rows(
12999 map,
13000 selection.start,
13001 action.lines,
13002 selection.goal,
13003 false,
13004 text_layout_details,
13005 );
13006 selection.collapse_to(cursor, goal);
13007 });
13008 })
13009 }
13010
13011 pub fn move_down_by_lines(
13012 &mut self,
13013 action: &MoveDownByLines,
13014 window: &mut Window,
13015 cx: &mut Context<Self>,
13016 ) {
13017 if self.take_rename(true, window, cx).is_some() {
13018 return;
13019 }
13020
13021 if self.mode.is_single_line() {
13022 cx.propagate();
13023 return;
13024 }
13025
13026 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13027
13028 let text_layout_details = &self.text_layout_details(window);
13029
13030 self.change_selections(Default::default(), window, cx, |s| {
13031 s.move_with(|map, selection| {
13032 if !selection.is_empty() {
13033 selection.goal = SelectionGoal::None;
13034 }
13035 let (cursor, goal) = movement::down_by_rows(
13036 map,
13037 selection.start,
13038 action.lines,
13039 selection.goal,
13040 false,
13041 text_layout_details,
13042 );
13043 selection.collapse_to(cursor, goal);
13044 });
13045 })
13046 }
13047
13048 pub fn select_down_by_lines(
13049 &mut self,
13050 action: &SelectDownByLines,
13051 window: &mut Window,
13052 cx: &mut Context<Self>,
13053 ) {
13054 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13055 let text_layout_details = &self.text_layout_details(window);
13056 self.change_selections(Default::default(), window, cx, |s| {
13057 s.move_heads_with(|map, head, goal| {
13058 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13059 })
13060 })
13061 }
13062
13063 pub fn select_up_by_lines(
13064 &mut self,
13065 action: &SelectUpByLines,
13066 window: &mut Window,
13067 cx: &mut Context<Self>,
13068 ) {
13069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13070 let text_layout_details = &self.text_layout_details(window);
13071 self.change_selections(Default::default(), window, cx, |s| {
13072 s.move_heads_with(|map, head, goal| {
13073 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13074 })
13075 })
13076 }
13077
13078 pub fn select_page_up(
13079 &mut self,
13080 _: &SelectPageUp,
13081 window: &mut Window,
13082 cx: &mut Context<Self>,
13083 ) {
13084 let Some(row_count) = self.visible_row_count() else {
13085 return;
13086 };
13087
13088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13089
13090 let text_layout_details = &self.text_layout_details(window);
13091
13092 self.change_selections(Default::default(), window, cx, |s| {
13093 s.move_heads_with(|map, head, goal| {
13094 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13095 })
13096 })
13097 }
13098
13099 pub fn move_page_up(
13100 &mut self,
13101 action: &MovePageUp,
13102 window: &mut Window,
13103 cx: &mut Context<Self>,
13104 ) {
13105 if self.take_rename(true, window, cx).is_some() {
13106 return;
13107 }
13108
13109 if self
13110 .context_menu
13111 .borrow_mut()
13112 .as_mut()
13113 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13114 .unwrap_or(false)
13115 {
13116 return;
13117 }
13118
13119 if matches!(self.mode, EditorMode::SingleLine) {
13120 cx.propagate();
13121 return;
13122 }
13123
13124 let Some(row_count) = self.visible_row_count() else {
13125 return;
13126 };
13127
13128 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13129
13130 let effects = if action.center_cursor {
13131 SelectionEffects::scroll(Autoscroll::center())
13132 } else {
13133 SelectionEffects::default()
13134 };
13135
13136 let text_layout_details = &self.text_layout_details(window);
13137
13138 self.change_selections(effects, window, cx, |s| {
13139 s.move_with(|map, selection| {
13140 if !selection.is_empty() {
13141 selection.goal = SelectionGoal::None;
13142 }
13143 let (cursor, goal) = movement::up_by_rows(
13144 map,
13145 selection.end,
13146 row_count,
13147 selection.goal,
13148 false,
13149 text_layout_details,
13150 );
13151 selection.collapse_to(cursor, goal);
13152 });
13153 });
13154 }
13155
13156 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13157 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13158 let text_layout_details = &self.text_layout_details(window);
13159 self.change_selections(Default::default(), window, cx, |s| {
13160 s.move_heads_with(|map, head, goal| {
13161 movement::up(map, head, goal, false, text_layout_details)
13162 })
13163 })
13164 }
13165
13166 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13167 self.take_rename(true, window, cx);
13168
13169 if self.mode.is_single_line() {
13170 cx.propagate();
13171 return;
13172 }
13173
13174 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13175
13176 let text_layout_details = &self.text_layout_details(window);
13177 let selection_count = self.selections.count();
13178 let first_selection = self.selections.first_anchor();
13179
13180 self.change_selections(Default::default(), window, cx, |s| {
13181 s.move_with(|map, selection| {
13182 if !selection.is_empty() {
13183 selection.goal = SelectionGoal::None;
13184 }
13185 let (cursor, goal) = movement::down(
13186 map,
13187 selection.end,
13188 selection.goal,
13189 false,
13190 text_layout_details,
13191 );
13192 selection.collapse_to(cursor, goal);
13193 });
13194 });
13195
13196 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13197 {
13198 cx.propagate();
13199 }
13200 }
13201
13202 pub fn select_page_down(
13203 &mut self,
13204 _: &SelectPageDown,
13205 window: &mut Window,
13206 cx: &mut Context<Self>,
13207 ) {
13208 let Some(row_count) = self.visible_row_count() else {
13209 return;
13210 };
13211
13212 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13213
13214 let text_layout_details = &self.text_layout_details(window);
13215
13216 self.change_selections(Default::default(), window, cx, |s| {
13217 s.move_heads_with(|map, head, goal| {
13218 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13219 })
13220 })
13221 }
13222
13223 pub fn move_page_down(
13224 &mut self,
13225 action: &MovePageDown,
13226 window: &mut Window,
13227 cx: &mut Context<Self>,
13228 ) {
13229 if self.take_rename(true, window, cx).is_some() {
13230 return;
13231 }
13232
13233 if self
13234 .context_menu
13235 .borrow_mut()
13236 .as_mut()
13237 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13238 .unwrap_or(false)
13239 {
13240 return;
13241 }
13242
13243 if matches!(self.mode, EditorMode::SingleLine) {
13244 cx.propagate();
13245 return;
13246 }
13247
13248 let Some(row_count) = self.visible_row_count() else {
13249 return;
13250 };
13251
13252 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13253
13254 let effects = if action.center_cursor {
13255 SelectionEffects::scroll(Autoscroll::center())
13256 } else {
13257 SelectionEffects::default()
13258 };
13259
13260 let text_layout_details = &self.text_layout_details(window);
13261 self.change_selections(effects, window, cx, |s| {
13262 s.move_with(|map, selection| {
13263 if !selection.is_empty() {
13264 selection.goal = SelectionGoal::None;
13265 }
13266 let (cursor, goal) = movement::down_by_rows(
13267 map,
13268 selection.end,
13269 row_count,
13270 selection.goal,
13271 false,
13272 text_layout_details,
13273 );
13274 selection.collapse_to(cursor, goal);
13275 });
13276 });
13277 }
13278
13279 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13280 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13281 let text_layout_details = &self.text_layout_details(window);
13282 self.change_selections(Default::default(), window, cx, |s| {
13283 s.move_heads_with(|map, head, goal| {
13284 movement::down(map, head, goal, false, text_layout_details)
13285 })
13286 });
13287 }
13288
13289 pub fn context_menu_first(
13290 &mut self,
13291 _: &ContextMenuFirst,
13292 window: &mut Window,
13293 cx: &mut Context<Self>,
13294 ) {
13295 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13296 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13297 }
13298 }
13299
13300 pub fn context_menu_prev(
13301 &mut self,
13302 _: &ContextMenuPrevious,
13303 window: &mut Window,
13304 cx: &mut Context<Self>,
13305 ) {
13306 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13307 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13308 }
13309 }
13310
13311 pub fn context_menu_next(
13312 &mut self,
13313 _: &ContextMenuNext,
13314 window: &mut Window,
13315 cx: &mut Context<Self>,
13316 ) {
13317 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13318 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13319 }
13320 }
13321
13322 pub fn context_menu_last(
13323 &mut self,
13324 _: &ContextMenuLast,
13325 window: &mut Window,
13326 cx: &mut Context<Self>,
13327 ) {
13328 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13329 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13330 }
13331 }
13332
13333 pub fn signature_help_prev(
13334 &mut self,
13335 _: &SignatureHelpPrevious,
13336 _: &mut Window,
13337 cx: &mut Context<Self>,
13338 ) {
13339 if let Some(popover) = self.signature_help_state.popover_mut() {
13340 if popover.current_signature == 0 {
13341 popover.current_signature = popover.signatures.len() - 1;
13342 } else {
13343 popover.current_signature -= 1;
13344 }
13345 cx.notify();
13346 }
13347 }
13348
13349 pub fn signature_help_next(
13350 &mut self,
13351 _: &SignatureHelpNext,
13352 _: &mut Window,
13353 cx: &mut Context<Self>,
13354 ) {
13355 if let Some(popover) = self.signature_help_state.popover_mut() {
13356 if popover.current_signature + 1 == popover.signatures.len() {
13357 popover.current_signature = 0;
13358 } else {
13359 popover.current_signature += 1;
13360 }
13361 cx.notify();
13362 }
13363 }
13364
13365 pub fn move_to_previous_word_start(
13366 &mut self,
13367 _: &MoveToPreviousWordStart,
13368 window: &mut Window,
13369 cx: &mut Context<Self>,
13370 ) {
13371 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13372 self.change_selections(Default::default(), window, cx, |s| {
13373 s.move_cursors_with(|map, head, _| {
13374 (
13375 movement::previous_word_start(map, head),
13376 SelectionGoal::None,
13377 )
13378 });
13379 })
13380 }
13381
13382 pub fn move_to_previous_subword_start(
13383 &mut self,
13384 _: &MoveToPreviousSubwordStart,
13385 window: &mut Window,
13386 cx: &mut Context<Self>,
13387 ) {
13388 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13389 self.change_selections(Default::default(), window, cx, |s| {
13390 s.move_cursors_with(|map, head, _| {
13391 (
13392 movement::previous_subword_start(map, head),
13393 SelectionGoal::None,
13394 )
13395 });
13396 })
13397 }
13398
13399 pub fn select_to_previous_word_start(
13400 &mut self,
13401 _: &SelectToPreviousWordStart,
13402 window: &mut Window,
13403 cx: &mut Context<Self>,
13404 ) {
13405 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13406 self.change_selections(Default::default(), window, cx, |s| {
13407 s.move_heads_with(|map, head, _| {
13408 (
13409 movement::previous_word_start(map, head),
13410 SelectionGoal::None,
13411 )
13412 });
13413 })
13414 }
13415
13416 pub fn select_to_previous_subword_start(
13417 &mut self,
13418 _: &SelectToPreviousSubwordStart,
13419 window: &mut Window,
13420 cx: &mut Context<Self>,
13421 ) {
13422 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13423 self.change_selections(Default::default(), window, cx, |s| {
13424 s.move_heads_with(|map, head, _| {
13425 (
13426 movement::previous_subword_start(map, head),
13427 SelectionGoal::None,
13428 )
13429 });
13430 })
13431 }
13432
13433 pub fn delete_to_previous_word_start(
13434 &mut self,
13435 action: &DeleteToPreviousWordStart,
13436 window: &mut Window,
13437 cx: &mut Context<Self>,
13438 ) {
13439 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13440 self.transact(window, cx, |this, window, cx| {
13441 this.select_autoclose_pair(window, cx);
13442 this.change_selections(Default::default(), window, cx, |s| {
13443 s.move_with(|map, selection| {
13444 if selection.is_empty() {
13445 let mut cursor = if action.ignore_newlines {
13446 movement::previous_word_start(map, selection.head())
13447 } else {
13448 movement::previous_word_start_or_newline(map, selection.head())
13449 };
13450 cursor = movement::adjust_greedy_deletion(
13451 map,
13452 selection.head(),
13453 cursor,
13454 action.ignore_brackets,
13455 );
13456 selection.set_head(cursor, SelectionGoal::None);
13457 }
13458 });
13459 });
13460 this.insert("", window, cx);
13461 });
13462 }
13463
13464 pub fn delete_to_previous_subword_start(
13465 &mut self,
13466 _: &DeleteToPreviousSubwordStart,
13467 window: &mut Window,
13468 cx: &mut Context<Self>,
13469 ) {
13470 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13471 self.transact(window, cx, |this, window, cx| {
13472 this.select_autoclose_pair(window, cx);
13473 this.change_selections(Default::default(), window, cx, |s| {
13474 s.move_with(|map, selection| {
13475 if selection.is_empty() {
13476 let mut cursor = movement::previous_subword_start(map, selection.head());
13477 cursor =
13478 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13479 selection.set_head(cursor, SelectionGoal::None);
13480 }
13481 });
13482 });
13483 this.insert("", window, cx);
13484 });
13485 }
13486
13487 pub fn move_to_next_word_end(
13488 &mut self,
13489 _: &MoveToNextWordEnd,
13490 window: &mut Window,
13491 cx: &mut Context<Self>,
13492 ) {
13493 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13494 self.change_selections(Default::default(), window, cx, |s| {
13495 s.move_cursors_with(|map, head, _| {
13496 (movement::next_word_end(map, head), SelectionGoal::None)
13497 });
13498 })
13499 }
13500
13501 pub fn move_to_next_subword_end(
13502 &mut self,
13503 _: &MoveToNextSubwordEnd,
13504 window: &mut Window,
13505 cx: &mut Context<Self>,
13506 ) {
13507 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13508 self.change_selections(Default::default(), window, cx, |s| {
13509 s.move_cursors_with(|map, head, _| {
13510 (movement::next_subword_end(map, head), SelectionGoal::None)
13511 });
13512 })
13513 }
13514
13515 pub fn select_to_next_word_end(
13516 &mut self,
13517 _: &SelectToNextWordEnd,
13518 window: &mut Window,
13519 cx: &mut Context<Self>,
13520 ) {
13521 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13522 self.change_selections(Default::default(), window, cx, |s| {
13523 s.move_heads_with(|map, head, _| {
13524 (movement::next_word_end(map, head), SelectionGoal::None)
13525 });
13526 })
13527 }
13528
13529 pub fn select_to_next_subword_end(
13530 &mut self,
13531 _: &SelectToNextSubwordEnd,
13532 window: &mut Window,
13533 cx: &mut Context<Self>,
13534 ) {
13535 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13536 self.change_selections(Default::default(), window, cx, |s| {
13537 s.move_heads_with(|map, head, _| {
13538 (movement::next_subword_end(map, head), SelectionGoal::None)
13539 });
13540 })
13541 }
13542
13543 pub fn delete_to_next_word_end(
13544 &mut self,
13545 action: &DeleteToNextWordEnd,
13546 window: &mut Window,
13547 cx: &mut Context<Self>,
13548 ) {
13549 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13550 self.transact(window, cx, |this, window, cx| {
13551 this.change_selections(Default::default(), window, cx, |s| {
13552 s.move_with(|map, selection| {
13553 if selection.is_empty() {
13554 let mut cursor = if action.ignore_newlines {
13555 movement::next_word_end(map, selection.head())
13556 } else {
13557 movement::next_word_end_or_newline(map, selection.head())
13558 };
13559 cursor = movement::adjust_greedy_deletion(
13560 map,
13561 selection.head(),
13562 cursor,
13563 action.ignore_brackets,
13564 );
13565 selection.set_head(cursor, SelectionGoal::None);
13566 }
13567 });
13568 });
13569 this.insert("", window, cx);
13570 });
13571 }
13572
13573 pub fn delete_to_next_subword_end(
13574 &mut self,
13575 _: &DeleteToNextSubwordEnd,
13576 window: &mut Window,
13577 cx: &mut Context<Self>,
13578 ) {
13579 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13580 self.transact(window, cx, |this, window, cx| {
13581 this.change_selections(Default::default(), window, cx, |s| {
13582 s.move_with(|map, selection| {
13583 if selection.is_empty() {
13584 let mut cursor = movement::next_subword_end(map, selection.head());
13585 cursor =
13586 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13587 selection.set_head(cursor, SelectionGoal::None);
13588 }
13589 });
13590 });
13591 this.insert("", window, cx);
13592 });
13593 }
13594
13595 pub fn move_to_beginning_of_line(
13596 &mut self,
13597 action: &MoveToBeginningOfLine,
13598 window: &mut Window,
13599 cx: &mut Context<Self>,
13600 ) {
13601 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13602 self.change_selections(Default::default(), window, cx, |s| {
13603 s.move_cursors_with(|map, head, _| {
13604 (
13605 movement::indented_line_beginning(
13606 map,
13607 head,
13608 action.stop_at_soft_wraps,
13609 action.stop_at_indent,
13610 ),
13611 SelectionGoal::None,
13612 )
13613 });
13614 })
13615 }
13616
13617 pub fn select_to_beginning_of_line(
13618 &mut self,
13619 action: &SelectToBeginningOfLine,
13620 window: &mut Window,
13621 cx: &mut Context<Self>,
13622 ) {
13623 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13624 self.change_selections(Default::default(), window, cx, |s| {
13625 s.move_heads_with(|map, head, _| {
13626 (
13627 movement::indented_line_beginning(
13628 map,
13629 head,
13630 action.stop_at_soft_wraps,
13631 action.stop_at_indent,
13632 ),
13633 SelectionGoal::None,
13634 )
13635 });
13636 });
13637 }
13638
13639 pub fn delete_to_beginning_of_line(
13640 &mut self,
13641 action: &DeleteToBeginningOfLine,
13642 window: &mut Window,
13643 cx: &mut Context<Self>,
13644 ) {
13645 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13646 self.transact(window, cx, |this, window, cx| {
13647 this.change_selections(Default::default(), window, cx, |s| {
13648 s.move_with(|_, selection| {
13649 selection.reversed = true;
13650 });
13651 });
13652
13653 this.select_to_beginning_of_line(
13654 &SelectToBeginningOfLine {
13655 stop_at_soft_wraps: false,
13656 stop_at_indent: action.stop_at_indent,
13657 },
13658 window,
13659 cx,
13660 );
13661 this.backspace(&Backspace, window, cx);
13662 });
13663 }
13664
13665 pub fn move_to_end_of_line(
13666 &mut self,
13667 action: &MoveToEndOfLine,
13668 window: &mut Window,
13669 cx: &mut Context<Self>,
13670 ) {
13671 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13672 self.change_selections(Default::default(), window, cx, |s| {
13673 s.move_cursors_with(|map, head, _| {
13674 (
13675 movement::line_end(map, head, action.stop_at_soft_wraps),
13676 SelectionGoal::None,
13677 )
13678 });
13679 })
13680 }
13681
13682 pub fn select_to_end_of_line(
13683 &mut self,
13684 action: &SelectToEndOfLine,
13685 window: &mut Window,
13686 cx: &mut Context<Self>,
13687 ) {
13688 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13689 self.change_selections(Default::default(), window, cx, |s| {
13690 s.move_heads_with(|map, head, _| {
13691 (
13692 movement::line_end(map, head, action.stop_at_soft_wraps),
13693 SelectionGoal::None,
13694 )
13695 });
13696 })
13697 }
13698
13699 pub fn delete_to_end_of_line(
13700 &mut self,
13701 _: &DeleteToEndOfLine,
13702 window: &mut Window,
13703 cx: &mut Context<Self>,
13704 ) {
13705 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13706 self.transact(window, cx, |this, window, cx| {
13707 this.select_to_end_of_line(
13708 &SelectToEndOfLine {
13709 stop_at_soft_wraps: false,
13710 },
13711 window,
13712 cx,
13713 );
13714 this.delete(&Delete, window, cx);
13715 });
13716 }
13717
13718 pub fn cut_to_end_of_line(
13719 &mut self,
13720 action: &CutToEndOfLine,
13721 window: &mut Window,
13722 cx: &mut Context<Self>,
13723 ) {
13724 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13725 self.transact(window, cx, |this, window, cx| {
13726 this.select_to_end_of_line(
13727 &SelectToEndOfLine {
13728 stop_at_soft_wraps: false,
13729 },
13730 window,
13731 cx,
13732 );
13733 if !action.stop_at_newlines {
13734 this.change_selections(Default::default(), window, cx, |s| {
13735 s.move_with(|_, sel| {
13736 if sel.is_empty() {
13737 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13738 }
13739 });
13740 });
13741 }
13742 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13743 let item = this.cut_common(false, window, cx);
13744 cx.write_to_clipboard(item);
13745 });
13746 }
13747
13748 pub fn move_to_start_of_paragraph(
13749 &mut self,
13750 _: &MoveToStartOfParagraph,
13751 window: &mut Window,
13752 cx: &mut Context<Self>,
13753 ) {
13754 if matches!(self.mode, EditorMode::SingleLine) {
13755 cx.propagate();
13756 return;
13757 }
13758 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13759 self.change_selections(Default::default(), window, cx, |s| {
13760 s.move_with(|map, selection| {
13761 selection.collapse_to(
13762 movement::start_of_paragraph(map, selection.head(), 1),
13763 SelectionGoal::None,
13764 )
13765 });
13766 })
13767 }
13768
13769 pub fn move_to_end_of_paragraph(
13770 &mut self,
13771 _: &MoveToEndOfParagraph,
13772 window: &mut Window,
13773 cx: &mut Context<Self>,
13774 ) {
13775 if matches!(self.mode, EditorMode::SingleLine) {
13776 cx.propagate();
13777 return;
13778 }
13779 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13780 self.change_selections(Default::default(), window, cx, |s| {
13781 s.move_with(|map, selection| {
13782 selection.collapse_to(
13783 movement::end_of_paragraph(map, selection.head(), 1),
13784 SelectionGoal::None,
13785 )
13786 });
13787 })
13788 }
13789
13790 pub fn select_to_start_of_paragraph(
13791 &mut self,
13792 _: &SelectToStartOfParagraph,
13793 window: &mut Window,
13794 cx: &mut Context<Self>,
13795 ) {
13796 if matches!(self.mode, EditorMode::SingleLine) {
13797 cx.propagate();
13798 return;
13799 }
13800 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13801 self.change_selections(Default::default(), window, cx, |s| {
13802 s.move_heads_with(|map, head, _| {
13803 (
13804 movement::start_of_paragraph(map, head, 1),
13805 SelectionGoal::None,
13806 )
13807 });
13808 })
13809 }
13810
13811 pub fn select_to_end_of_paragraph(
13812 &mut self,
13813 _: &SelectToEndOfParagraph,
13814 window: &mut Window,
13815 cx: &mut Context<Self>,
13816 ) {
13817 if matches!(self.mode, EditorMode::SingleLine) {
13818 cx.propagate();
13819 return;
13820 }
13821 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13822 self.change_selections(Default::default(), window, cx, |s| {
13823 s.move_heads_with(|map, head, _| {
13824 (
13825 movement::end_of_paragraph(map, head, 1),
13826 SelectionGoal::None,
13827 )
13828 });
13829 })
13830 }
13831
13832 pub fn move_to_start_of_excerpt(
13833 &mut self,
13834 _: &MoveToStartOfExcerpt,
13835 window: &mut Window,
13836 cx: &mut Context<Self>,
13837 ) {
13838 if matches!(self.mode, EditorMode::SingleLine) {
13839 cx.propagate();
13840 return;
13841 }
13842 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13843 self.change_selections(Default::default(), window, cx, |s| {
13844 s.move_with(|map, selection| {
13845 selection.collapse_to(
13846 movement::start_of_excerpt(
13847 map,
13848 selection.head(),
13849 workspace::searchable::Direction::Prev,
13850 ),
13851 SelectionGoal::None,
13852 )
13853 });
13854 })
13855 }
13856
13857 pub fn move_to_start_of_next_excerpt(
13858 &mut self,
13859 _: &MoveToStartOfNextExcerpt,
13860 window: &mut Window,
13861 cx: &mut Context<Self>,
13862 ) {
13863 if matches!(self.mode, EditorMode::SingleLine) {
13864 cx.propagate();
13865 return;
13866 }
13867
13868 self.change_selections(Default::default(), window, cx, |s| {
13869 s.move_with(|map, selection| {
13870 selection.collapse_to(
13871 movement::start_of_excerpt(
13872 map,
13873 selection.head(),
13874 workspace::searchable::Direction::Next,
13875 ),
13876 SelectionGoal::None,
13877 )
13878 });
13879 })
13880 }
13881
13882 pub fn move_to_end_of_excerpt(
13883 &mut self,
13884 _: &MoveToEndOfExcerpt,
13885 window: &mut Window,
13886 cx: &mut Context<Self>,
13887 ) {
13888 if matches!(self.mode, EditorMode::SingleLine) {
13889 cx.propagate();
13890 return;
13891 }
13892 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13893 self.change_selections(Default::default(), window, cx, |s| {
13894 s.move_with(|map, selection| {
13895 selection.collapse_to(
13896 movement::end_of_excerpt(
13897 map,
13898 selection.head(),
13899 workspace::searchable::Direction::Next,
13900 ),
13901 SelectionGoal::None,
13902 )
13903 });
13904 })
13905 }
13906
13907 pub fn move_to_end_of_previous_excerpt(
13908 &mut self,
13909 _: &MoveToEndOfPreviousExcerpt,
13910 window: &mut Window,
13911 cx: &mut Context<Self>,
13912 ) {
13913 if matches!(self.mode, EditorMode::SingleLine) {
13914 cx.propagate();
13915 return;
13916 }
13917 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13918 self.change_selections(Default::default(), window, cx, |s| {
13919 s.move_with(|map, selection| {
13920 selection.collapse_to(
13921 movement::end_of_excerpt(
13922 map,
13923 selection.head(),
13924 workspace::searchable::Direction::Prev,
13925 ),
13926 SelectionGoal::None,
13927 )
13928 });
13929 })
13930 }
13931
13932 pub fn select_to_start_of_excerpt(
13933 &mut self,
13934 _: &SelectToStartOfExcerpt,
13935 window: &mut Window,
13936 cx: &mut Context<Self>,
13937 ) {
13938 if matches!(self.mode, EditorMode::SingleLine) {
13939 cx.propagate();
13940 return;
13941 }
13942 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13943 self.change_selections(Default::default(), window, cx, |s| {
13944 s.move_heads_with(|map, head, _| {
13945 (
13946 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13947 SelectionGoal::None,
13948 )
13949 });
13950 })
13951 }
13952
13953 pub fn select_to_start_of_next_excerpt(
13954 &mut self,
13955 _: &SelectToStartOfNextExcerpt,
13956 window: &mut Window,
13957 cx: &mut Context<Self>,
13958 ) {
13959 if matches!(self.mode, EditorMode::SingleLine) {
13960 cx.propagate();
13961 return;
13962 }
13963 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13964 self.change_selections(Default::default(), window, cx, |s| {
13965 s.move_heads_with(|map, head, _| {
13966 (
13967 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13968 SelectionGoal::None,
13969 )
13970 });
13971 })
13972 }
13973
13974 pub fn select_to_end_of_excerpt(
13975 &mut self,
13976 _: &SelectToEndOfExcerpt,
13977 window: &mut Window,
13978 cx: &mut Context<Self>,
13979 ) {
13980 if matches!(self.mode, EditorMode::SingleLine) {
13981 cx.propagate();
13982 return;
13983 }
13984 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13985 self.change_selections(Default::default(), window, cx, |s| {
13986 s.move_heads_with(|map, head, _| {
13987 (
13988 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13989 SelectionGoal::None,
13990 )
13991 });
13992 })
13993 }
13994
13995 pub fn select_to_end_of_previous_excerpt(
13996 &mut self,
13997 _: &SelectToEndOfPreviousExcerpt,
13998 window: &mut Window,
13999 cx: &mut Context<Self>,
14000 ) {
14001 if matches!(self.mode, EditorMode::SingleLine) {
14002 cx.propagate();
14003 return;
14004 }
14005 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14006 self.change_selections(Default::default(), window, cx, |s| {
14007 s.move_heads_with(|map, head, _| {
14008 (
14009 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14010 SelectionGoal::None,
14011 )
14012 });
14013 })
14014 }
14015
14016 pub fn move_to_beginning(
14017 &mut self,
14018 _: &MoveToBeginning,
14019 window: &mut Window,
14020 cx: &mut Context<Self>,
14021 ) {
14022 if matches!(self.mode, EditorMode::SingleLine) {
14023 cx.propagate();
14024 return;
14025 }
14026 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14027 self.change_selections(Default::default(), window, cx, |s| {
14028 s.select_ranges(vec![0..0]);
14029 });
14030 }
14031
14032 pub fn select_to_beginning(
14033 &mut self,
14034 _: &SelectToBeginning,
14035 window: &mut Window,
14036 cx: &mut Context<Self>,
14037 ) {
14038 let mut selection = self.selections.last::<Point>(cx);
14039 selection.set_head(Point::zero(), SelectionGoal::None);
14040 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14041 self.change_selections(Default::default(), window, cx, |s| {
14042 s.select(vec![selection]);
14043 });
14044 }
14045
14046 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14047 if matches!(self.mode, EditorMode::SingleLine) {
14048 cx.propagate();
14049 return;
14050 }
14051 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14052 let cursor = self.buffer.read(cx).read(cx).len();
14053 self.change_selections(Default::default(), window, cx, |s| {
14054 s.select_ranges(vec![cursor..cursor])
14055 });
14056 }
14057
14058 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14059 self.nav_history = nav_history;
14060 }
14061
14062 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14063 self.nav_history.as_ref()
14064 }
14065
14066 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14067 self.push_to_nav_history(
14068 self.selections.newest_anchor().head(),
14069 None,
14070 false,
14071 true,
14072 cx,
14073 );
14074 }
14075
14076 fn push_to_nav_history(
14077 &mut self,
14078 cursor_anchor: Anchor,
14079 new_position: Option<Point>,
14080 is_deactivate: bool,
14081 always: bool,
14082 cx: &mut Context<Self>,
14083 ) {
14084 if let Some(nav_history) = self.nav_history.as_mut() {
14085 let buffer = self.buffer.read(cx).read(cx);
14086 let cursor_position = cursor_anchor.to_point(&buffer);
14087 let scroll_state = self.scroll_manager.anchor();
14088 let scroll_top_row = scroll_state.top_row(&buffer);
14089 drop(buffer);
14090
14091 if let Some(new_position) = new_position {
14092 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14093 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14094 return;
14095 }
14096 }
14097
14098 nav_history.push(
14099 Some(NavigationData {
14100 cursor_anchor,
14101 cursor_position,
14102 scroll_anchor: scroll_state,
14103 scroll_top_row,
14104 }),
14105 cx,
14106 );
14107 cx.emit(EditorEvent::PushedToNavHistory {
14108 anchor: cursor_anchor,
14109 is_deactivate,
14110 })
14111 }
14112 }
14113
14114 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14115 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14116 let buffer = self.buffer.read(cx).snapshot(cx);
14117 let mut selection = self.selections.first::<usize>(cx);
14118 selection.set_head(buffer.len(), SelectionGoal::None);
14119 self.change_selections(Default::default(), window, cx, |s| {
14120 s.select(vec![selection]);
14121 });
14122 }
14123
14124 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14125 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14126 let end = self.buffer.read(cx).read(cx).len();
14127 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14128 s.select_ranges(vec![0..end]);
14129 });
14130 }
14131
14132 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14133 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14134 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14135 let mut selections = self.selections.all::<Point>(cx);
14136 let max_point = display_map.buffer_snapshot().max_point();
14137 for selection in &mut selections {
14138 let rows = selection.spanned_rows(true, &display_map);
14139 selection.start = Point::new(rows.start.0, 0);
14140 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14141 selection.reversed = false;
14142 }
14143 self.change_selections(Default::default(), window, cx, |s| {
14144 s.select(selections);
14145 });
14146 }
14147
14148 pub fn split_selection_into_lines(
14149 &mut self,
14150 action: &SplitSelectionIntoLines,
14151 window: &mut Window,
14152 cx: &mut Context<Self>,
14153 ) {
14154 let selections = self
14155 .selections
14156 .all::<Point>(cx)
14157 .into_iter()
14158 .map(|selection| selection.start..selection.end)
14159 .collect::<Vec<_>>();
14160 self.unfold_ranges(&selections, true, true, cx);
14161
14162 let mut new_selection_ranges = Vec::new();
14163 {
14164 let buffer = self.buffer.read(cx).read(cx);
14165 for selection in selections {
14166 for row in selection.start.row..selection.end.row {
14167 let line_start = Point::new(row, 0);
14168 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14169
14170 if action.keep_selections {
14171 // Keep the selection range for each line
14172 let selection_start = if row == selection.start.row {
14173 selection.start
14174 } else {
14175 line_start
14176 };
14177 new_selection_ranges.push(selection_start..line_end);
14178 } else {
14179 // Collapse to cursor at end of line
14180 new_selection_ranges.push(line_end..line_end);
14181 }
14182 }
14183
14184 let is_multiline_selection = selection.start.row != selection.end.row;
14185 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14186 // so this action feels more ergonomic when paired with other selection operations
14187 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14188 if !should_skip_last {
14189 if action.keep_selections {
14190 if is_multiline_selection {
14191 let line_start = Point::new(selection.end.row, 0);
14192 new_selection_ranges.push(line_start..selection.end);
14193 } else {
14194 new_selection_ranges.push(selection.start..selection.end);
14195 }
14196 } else {
14197 new_selection_ranges.push(selection.end..selection.end);
14198 }
14199 }
14200 }
14201 }
14202 self.change_selections(Default::default(), window, cx, |s| {
14203 s.select_ranges(new_selection_ranges);
14204 });
14205 }
14206
14207 pub fn add_selection_above(
14208 &mut self,
14209 _: &AddSelectionAbove,
14210 window: &mut Window,
14211 cx: &mut Context<Self>,
14212 ) {
14213 self.add_selection(true, window, cx);
14214 }
14215
14216 pub fn add_selection_below(
14217 &mut self,
14218 _: &AddSelectionBelow,
14219 window: &mut Window,
14220 cx: &mut Context<Self>,
14221 ) {
14222 self.add_selection(false, window, cx);
14223 }
14224
14225 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14226 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14227
14228 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14229 let all_selections = self.selections.all::<Point>(cx);
14230 let text_layout_details = self.text_layout_details(window);
14231
14232 let (mut columnar_selections, new_selections_to_columnarize) = {
14233 if let Some(state) = self.add_selections_state.as_ref() {
14234 let columnar_selection_ids: HashSet<_> = state
14235 .groups
14236 .iter()
14237 .flat_map(|group| group.stack.iter())
14238 .copied()
14239 .collect();
14240
14241 all_selections
14242 .into_iter()
14243 .partition(|s| columnar_selection_ids.contains(&s.id))
14244 } else {
14245 (Vec::new(), all_selections)
14246 }
14247 };
14248
14249 let mut state = self
14250 .add_selections_state
14251 .take()
14252 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14253
14254 for selection in new_selections_to_columnarize {
14255 let range = selection.display_range(&display_map).sorted();
14256 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14257 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14258 let positions = start_x.min(end_x)..start_x.max(end_x);
14259 let mut stack = Vec::new();
14260 for row in range.start.row().0..=range.end.row().0 {
14261 if let Some(selection) = self.selections.build_columnar_selection(
14262 &display_map,
14263 DisplayRow(row),
14264 &positions,
14265 selection.reversed,
14266 &text_layout_details,
14267 ) {
14268 stack.push(selection.id);
14269 columnar_selections.push(selection);
14270 }
14271 }
14272 if !stack.is_empty() {
14273 if above {
14274 stack.reverse();
14275 }
14276 state.groups.push(AddSelectionsGroup { above, stack });
14277 }
14278 }
14279
14280 let mut final_selections = Vec::new();
14281 let end_row = if above {
14282 DisplayRow(0)
14283 } else {
14284 display_map.max_point().row()
14285 };
14286
14287 let mut last_added_item_per_group = HashMap::default();
14288 for group in state.groups.iter_mut() {
14289 if let Some(last_id) = group.stack.last() {
14290 last_added_item_per_group.insert(*last_id, group);
14291 }
14292 }
14293
14294 for selection in columnar_selections {
14295 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14296 if above == group.above {
14297 let range = selection.display_range(&display_map).sorted();
14298 debug_assert_eq!(range.start.row(), range.end.row());
14299 let mut row = range.start.row();
14300 let positions =
14301 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14302 Pixels::from(start)..Pixels::from(end)
14303 } else {
14304 let start_x =
14305 display_map.x_for_display_point(range.start, &text_layout_details);
14306 let end_x =
14307 display_map.x_for_display_point(range.end, &text_layout_details);
14308 start_x.min(end_x)..start_x.max(end_x)
14309 };
14310
14311 let mut maybe_new_selection = None;
14312 while row != end_row {
14313 if above {
14314 row.0 -= 1;
14315 } else {
14316 row.0 += 1;
14317 }
14318 if let Some(new_selection) = self.selections.build_columnar_selection(
14319 &display_map,
14320 row,
14321 &positions,
14322 selection.reversed,
14323 &text_layout_details,
14324 ) {
14325 maybe_new_selection = Some(new_selection);
14326 break;
14327 }
14328 }
14329
14330 if let Some(new_selection) = maybe_new_selection {
14331 group.stack.push(new_selection.id);
14332 if above {
14333 final_selections.push(new_selection);
14334 final_selections.push(selection);
14335 } else {
14336 final_selections.push(selection);
14337 final_selections.push(new_selection);
14338 }
14339 } else {
14340 final_selections.push(selection);
14341 }
14342 } else {
14343 group.stack.pop();
14344 }
14345 } else {
14346 final_selections.push(selection);
14347 }
14348 }
14349
14350 self.change_selections(Default::default(), window, cx, |s| {
14351 s.select(final_selections);
14352 });
14353
14354 let final_selection_ids: HashSet<_> = self
14355 .selections
14356 .all::<Point>(cx)
14357 .iter()
14358 .map(|s| s.id)
14359 .collect();
14360 state.groups.retain_mut(|group| {
14361 // selections might get merged above so we remove invalid items from stacks
14362 group.stack.retain(|id| final_selection_ids.contains(id));
14363
14364 // single selection in stack can be treated as initial state
14365 group.stack.len() > 1
14366 });
14367
14368 if !state.groups.is_empty() {
14369 self.add_selections_state = Some(state);
14370 }
14371 }
14372
14373 fn select_match_ranges(
14374 &mut self,
14375 range: Range<usize>,
14376 reversed: bool,
14377 replace_newest: bool,
14378 auto_scroll: Option<Autoscroll>,
14379 window: &mut Window,
14380 cx: &mut Context<Editor>,
14381 ) {
14382 self.unfold_ranges(
14383 std::slice::from_ref(&range),
14384 false,
14385 auto_scroll.is_some(),
14386 cx,
14387 );
14388 let effects = if let Some(scroll) = auto_scroll {
14389 SelectionEffects::scroll(scroll)
14390 } else {
14391 SelectionEffects::no_scroll()
14392 };
14393 self.change_selections(effects, window, cx, |s| {
14394 if replace_newest {
14395 s.delete(s.newest_anchor().id);
14396 }
14397 if reversed {
14398 s.insert_range(range.end..range.start);
14399 } else {
14400 s.insert_range(range);
14401 }
14402 });
14403 }
14404
14405 pub fn select_next_match_internal(
14406 &mut self,
14407 display_map: &DisplaySnapshot,
14408 replace_newest: bool,
14409 autoscroll: Option<Autoscroll>,
14410 window: &mut Window,
14411 cx: &mut Context<Self>,
14412 ) -> Result<()> {
14413 let buffer = display_map.buffer_snapshot();
14414 let mut selections = self.selections.all::<usize>(cx);
14415 if let Some(mut select_next_state) = self.select_next_state.take() {
14416 let query = &select_next_state.query;
14417 if !select_next_state.done {
14418 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14419 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14420 let mut next_selected_range = None;
14421
14422 let bytes_after_last_selection =
14423 buffer.bytes_in_range(last_selection.end..buffer.len());
14424 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14425 let query_matches = query
14426 .stream_find_iter(bytes_after_last_selection)
14427 .map(|result| (last_selection.end, result))
14428 .chain(
14429 query
14430 .stream_find_iter(bytes_before_first_selection)
14431 .map(|result| (0, result)),
14432 );
14433
14434 for (start_offset, query_match) in query_matches {
14435 let query_match = query_match.unwrap(); // can only fail due to I/O
14436 let offset_range =
14437 start_offset + query_match.start()..start_offset + query_match.end();
14438
14439 if !select_next_state.wordwise
14440 || (!buffer.is_inside_word(offset_range.start, None)
14441 && !buffer.is_inside_word(offset_range.end, None))
14442 {
14443 // TODO: This is n^2, because we might check all the selections
14444 if !selections
14445 .iter()
14446 .any(|selection| selection.range().overlaps(&offset_range))
14447 {
14448 next_selected_range = Some(offset_range);
14449 break;
14450 }
14451 }
14452 }
14453
14454 if let Some(next_selected_range) = next_selected_range {
14455 self.select_match_ranges(
14456 next_selected_range,
14457 last_selection.reversed,
14458 replace_newest,
14459 autoscroll,
14460 window,
14461 cx,
14462 );
14463 } else {
14464 select_next_state.done = true;
14465 }
14466 }
14467
14468 self.select_next_state = Some(select_next_state);
14469 } else {
14470 let mut only_carets = true;
14471 let mut same_text_selected = true;
14472 let mut selected_text = None;
14473
14474 let mut selections_iter = selections.iter().peekable();
14475 while let Some(selection) = selections_iter.next() {
14476 if selection.start != selection.end {
14477 only_carets = false;
14478 }
14479
14480 if same_text_selected {
14481 if selected_text.is_none() {
14482 selected_text =
14483 Some(buffer.text_for_range(selection.range()).collect::<String>());
14484 }
14485
14486 if let Some(next_selection) = selections_iter.peek() {
14487 if next_selection.range().len() == selection.range().len() {
14488 let next_selected_text = buffer
14489 .text_for_range(next_selection.range())
14490 .collect::<String>();
14491 if Some(next_selected_text) != selected_text {
14492 same_text_selected = false;
14493 selected_text = None;
14494 }
14495 } else {
14496 same_text_selected = false;
14497 selected_text = None;
14498 }
14499 }
14500 }
14501 }
14502
14503 if only_carets {
14504 for selection in &mut selections {
14505 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14506 selection.start = word_range.start;
14507 selection.end = word_range.end;
14508 selection.goal = SelectionGoal::None;
14509 selection.reversed = false;
14510 self.select_match_ranges(
14511 selection.start..selection.end,
14512 selection.reversed,
14513 replace_newest,
14514 autoscroll,
14515 window,
14516 cx,
14517 );
14518 }
14519
14520 if selections.len() == 1 {
14521 let selection = selections
14522 .last()
14523 .expect("ensured that there's only one selection");
14524 let query = buffer
14525 .text_for_range(selection.start..selection.end)
14526 .collect::<String>();
14527 let is_empty = query.is_empty();
14528 let select_state = SelectNextState {
14529 query: AhoCorasick::new(&[query])?,
14530 wordwise: true,
14531 done: is_empty,
14532 };
14533 self.select_next_state = Some(select_state);
14534 } else {
14535 self.select_next_state = None;
14536 }
14537 } else if let Some(selected_text) = selected_text {
14538 self.select_next_state = Some(SelectNextState {
14539 query: AhoCorasick::new(&[selected_text])?,
14540 wordwise: false,
14541 done: false,
14542 });
14543 self.select_next_match_internal(
14544 display_map,
14545 replace_newest,
14546 autoscroll,
14547 window,
14548 cx,
14549 )?;
14550 }
14551 }
14552 Ok(())
14553 }
14554
14555 pub fn select_all_matches(
14556 &mut self,
14557 _action: &SelectAllMatches,
14558 window: &mut Window,
14559 cx: &mut Context<Self>,
14560 ) -> Result<()> {
14561 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14562
14563 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14564
14565 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14566 let Some(select_next_state) = self.select_next_state.as_mut() else {
14567 return Ok(());
14568 };
14569 if select_next_state.done {
14570 return Ok(());
14571 }
14572
14573 let mut new_selections = Vec::new();
14574
14575 let reversed = self.selections.oldest::<usize>(cx).reversed;
14576 let buffer = display_map.buffer_snapshot();
14577 let query_matches = select_next_state
14578 .query
14579 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14580
14581 for query_match in query_matches.into_iter() {
14582 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14583 let offset_range = if reversed {
14584 query_match.end()..query_match.start()
14585 } else {
14586 query_match.start()..query_match.end()
14587 };
14588
14589 if !select_next_state.wordwise
14590 || (!buffer.is_inside_word(offset_range.start, None)
14591 && !buffer.is_inside_word(offset_range.end, None))
14592 {
14593 new_selections.push(offset_range.start..offset_range.end);
14594 }
14595 }
14596
14597 select_next_state.done = true;
14598
14599 if new_selections.is_empty() {
14600 log::error!("bug: new_selections is empty in select_all_matches");
14601 return Ok(());
14602 }
14603
14604 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14605 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14606 selections.select_ranges(new_selections)
14607 });
14608
14609 Ok(())
14610 }
14611
14612 pub fn select_next(
14613 &mut self,
14614 action: &SelectNext,
14615 window: &mut Window,
14616 cx: &mut Context<Self>,
14617 ) -> Result<()> {
14618 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14619 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14620 self.select_next_match_internal(
14621 &display_map,
14622 action.replace_newest,
14623 Some(Autoscroll::newest()),
14624 window,
14625 cx,
14626 )?;
14627 Ok(())
14628 }
14629
14630 pub fn select_previous(
14631 &mut self,
14632 action: &SelectPrevious,
14633 window: &mut Window,
14634 cx: &mut Context<Self>,
14635 ) -> Result<()> {
14636 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14637 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14638 let buffer = display_map.buffer_snapshot();
14639 let mut selections = self.selections.all::<usize>(cx);
14640 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14641 let query = &select_prev_state.query;
14642 if !select_prev_state.done {
14643 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14644 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14645 let mut next_selected_range = None;
14646 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14647 let bytes_before_last_selection =
14648 buffer.reversed_bytes_in_range(0..last_selection.start);
14649 let bytes_after_first_selection =
14650 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14651 let query_matches = query
14652 .stream_find_iter(bytes_before_last_selection)
14653 .map(|result| (last_selection.start, result))
14654 .chain(
14655 query
14656 .stream_find_iter(bytes_after_first_selection)
14657 .map(|result| (buffer.len(), result)),
14658 );
14659 for (end_offset, query_match) in query_matches {
14660 let query_match = query_match.unwrap(); // can only fail due to I/O
14661 let offset_range =
14662 end_offset - query_match.end()..end_offset - query_match.start();
14663
14664 if !select_prev_state.wordwise
14665 || (!buffer.is_inside_word(offset_range.start, None)
14666 && !buffer.is_inside_word(offset_range.end, None))
14667 {
14668 next_selected_range = Some(offset_range);
14669 break;
14670 }
14671 }
14672
14673 if let Some(next_selected_range) = next_selected_range {
14674 self.select_match_ranges(
14675 next_selected_range,
14676 last_selection.reversed,
14677 action.replace_newest,
14678 Some(Autoscroll::newest()),
14679 window,
14680 cx,
14681 );
14682 } else {
14683 select_prev_state.done = true;
14684 }
14685 }
14686
14687 self.select_prev_state = Some(select_prev_state);
14688 } else {
14689 let mut only_carets = true;
14690 let mut same_text_selected = true;
14691 let mut selected_text = None;
14692
14693 let mut selections_iter = selections.iter().peekable();
14694 while let Some(selection) = selections_iter.next() {
14695 if selection.start != selection.end {
14696 only_carets = false;
14697 }
14698
14699 if same_text_selected {
14700 if selected_text.is_none() {
14701 selected_text =
14702 Some(buffer.text_for_range(selection.range()).collect::<String>());
14703 }
14704
14705 if let Some(next_selection) = selections_iter.peek() {
14706 if next_selection.range().len() == selection.range().len() {
14707 let next_selected_text = buffer
14708 .text_for_range(next_selection.range())
14709 .collect::<String>();
14710 if Some(next_selected_text) != selected_text {
14711 same_text_selected = false;
14712 selected_text = None;
14713 }
14714 } else {
14715 same_text_selected = false;
14716 selected_text = None;
14717 }
14718 }
14719 }
14720 }
14721
14722 if only_carets {
14723 for selection in &mut selections {
14724 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14725 selection.start = word_range.start;
14726 selection.end = word_range.end;
14727 selection.goal = SelectionGoal::None;
14728 selection.reversed = false;
14729 self.select_match_ranges(
14730 selection.start..selection.end,
14731 selection.reversed,
14732 action.replace_newest,
14733 Some(Autoscroll::newest()),
14734 window,
14735 cx,
14736 );
14737 }
14738 if selections.len() == 1 {
14739 let selection = selections
14740 .last()
14741 .expect("ensured that there's only one selection");
14742 let query = buffer
14743 .text_for_range(selection.start..selection.end)
14744 .collect::<String>();
14745 let is_empty = query.is_empty();
14746 let select_state = SelectNextState {
14747 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14748 wordwise: true,
14749 done: is_empty,
14750 };
14751 self.select_prev_state = Some(select_state);
14752 } else {
14753 self.select_prev_state = None;
14754 }
14755 } else if let Some(selected_text) = selected_text {
14756 self.select_prev_state = Some(SelectNextState {
14757 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14758 wordwise: false,
14759 done: false,
14760 });
14761 self.select_previous(action, window, cx)?;
14762 }
14763 }
14764 Ok(())
14765 }
14766
14767 pub fn find_next_match(
14768 &mut self,
14769 _: &FindNextMatch,
14770 window: &mut Window,
14771 cx: &mut Context<Self>,
14772 ) -> Result<()> {
14773 let selections = self.selections.disjoint_anchors_arc();
14774 match selections.first() {
14775 Some(first) if selections.len() >= 2 => {
14776 self.change_selections(Default::default(), window, cx, |s| {
14777 s.select_ranges([first.range()]);
14778 });
14779 }
14780 _ => self.select_next(
14781 &SelectNext {
14782 replace_newest: true,
14783 },
14784 window,
14785 cx,
14786 )?,
14787 }
14788 Ok(())
14789 }
14790
14791 pub fn find_previous_match(
14792 &mut self,
14793 _: &FindPreviousMatch,
14794 window: &mut Window,
14795 cx: &mut Context<Self>,
14796 ) -> Result<()> {
14797 let selections = self.selections.disjoint_anchors_arc();
14798 match selections.last() {
14799 Some(last) if selections.len() >= 2 => {
14800 self.change_selections(Default::default(), window, cx, |s| {
14801 s.select_ranges([last.range()]);
14802 });
14803 }
14804 _ => self.select_previous(
14805 &SelectPrevious {
14806 replace_newest: true,
14807 },
14808 window,
14809 cx,
14810 )?,
14811 }
14812 Ok(())
14813 }
14814
14815 pub fn toggle_comments(
14816 &mut self,
14817 action: &ToggleComments,
14818 window: &mut Window,
14819 cx: &mut Context<Self>,
14820 ) {
14821 if self.read_only(cx) {
14822 return;
14823 }
14824 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14825 let text_layout_details = &self.text_layout_details(window);
14826 self.transact(window, cx, |this, window, cx| {
14827 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14828 let mut edits = Vec::new();
14829 let mut selection_edit_ranges = Vec::new();
14830 let mut last_toggled_row = None;
14831 let snapshot = this.buffer.read(cx).read(cx);
14832 let empty_str: Arc<str> = Arc::default();
14833 let mut suffixes_inserted = Vec::new();
14834 let ignore_indent = action.ignore_indent;
14835
14836 fn comment_prefix_range(
14837 snapshot: &MultiBufferSnapshot,
14838 row: MultiBufferRow,
14839 comment_prefix: &str,
14840 comment_prefix_whitespace: &str,
14841 ignore_indent: bool,
14842 ) -> Range<Point> {
14843 let indent_size = if ignore_indent {
14844 0
14845 } else {
14846 snapshot.indent_size_for_line(row).len
14847 };
14848
14849 let start = Point::new(row.0, indent_size);
14850
14851 let mut line_bytes = snapshot
14852 .bytes_in_range(start..snapshot.max_point())
14853 .flatten()
14854 .copied();
14855
14856 // If this line currently begins with the line comment prefix, then record
14857 // the range containing the prefix.
14858 if line_bytes
14859 .by_ref()
14860 .take(comment_prefix.len())
14861 .eq(comment_prefix.bytes())
14862 {
14863 // Include any whitespace that matches the comment prefix.
14864 let matching_whitespace_len = line_bytes
14865 .zip(comment_prefix_whitespace.bytes())
14866 .take_while(|(a, b)| a == b)
14867 .count() as u32;
14868 let end = Point::new(
14869 start.row,
14870 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14871 );
14872 start..end
14873 } else {
14874 start..start
14875 }
14876 }
14877
14878 fn comment_suffix_range(
14879 snapshot: &MultiBufferSnapshot,
14880 row: MultiBufferRow,
14881 comment_suffix: &str,
14882 comment_suffix_has_leading_space: bool,
14883 ) -> Range<Point> {
14884 let end = Point::new(row.0, snapshot.line_len(row));
14885 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14886
14887 let mut line_end_bytes = snapshot
14888 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14889 .flatten()
14890 .copied();
14891
14892 let leading_space_len = if suffix_start_column > 0
14893 && line_end_bytes.next() == Some(b' ')
14894 && comment_suffix_has_leading_space
14895 {
14896 1
14897 } else {
14898 0
14899 };
14900
14901 // If this line currently begins with the line comment prefix, then record
14902 // the range containing the prefix.
14903 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14904 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14905 start..end
14906 } else {
14907 end..end
14908 }
14909 }
14910
14911 // TODO: Handle selections that cross excerpts
14912 for selection in &mut selections {
14913 let start_column = snapshot
14914 .indent_size_for_line(MultiBufferRow(selection.start.row))
14915 .len;
14916 let language = if let Some(language) =
14917 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14918 {
14919 language
14920 } else {
14921 continue;
14922 };
14923
14924 selection_edit_ranges.clear();
14925
14926 // If multiple selections contain a given row, avoid processing that
14927 // row more than once.
14928 let mut start_row = MultiBufferRow(selection.start.row);
14929 if last_toggled_row == Some(start_row) {
14930 start_row = start_row.next_row();
14931 }
14932 let end_row =
14933 if selection.end.row > selection.start.row && selection.end.column == 0 {
14934 MultiBufferRow(selection.end.row - 1)
14935 } else {
14936 MultiBufferRow(selection.end.row)
14937 };
14938 last_toggled_row = Some(end_row);
14939
14940 if start_row > end_row {
14941 continue;
14942 }
14943
14944 // If the language has line comments, toggle those.
14945 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14946
14947 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14948 if ignore_indent {
14949 full_comment_prefixes = full_comment_prefixes
14950 .into_iter()
14951 .map(|s| Arc::from(s.trim_end()))
14952 .collect();
14953 }
14954
14955 if !full_comment_prefixes.is_empty() {
14956 let first_prefix = full_comment_prefixes
14957 .first()
14958 .expect("prefixes is non-empty");
14959 let prefix_trimmed_lengths = full_comment_prefixes
14960 .iter()
14961 .map(|p| p.trim_end_matches(' ').len())
14962 .collect::<SmallVec<[usize; 4]>>();
14963
14964 let mut all_selection_lines_are_comments = true;
14965
14966 for row in start_row.0..=end_row.0 {
14967 let row = MultiBufferRow(row);
14968 if start_row < end_row && snapshot.is_line_blank(row) {
14969 continue;
14970 }
14971
14972 let prefix_range = full_comment_prefixes
14973 .iter()
14974 .zip(prefix_trimmed_lengths.iter().copied())
14975 .map(|(prefix, trimmed_prefix_len)| {
14976 comment_prefix_range(
14977 snapshot.deref(),
14978 row,
14979 &prefix[..trimmed_prefix_len],
14980 &prefix[trimmed_prefix_len..],
14981 ignore_indent,
14982 )
14983 })
14984 .max_by_key(|range| range.end.column - range.start.column)
14985 .expect("prefixes is non-empty");
14986
14987 if prefix_range.is_empty() {
14988 all_selection_lines_are_comments = false;
14989 }
14990
14991 selection_edit_ranges.push(prefix_range);
14992 }
14993
14994 if all_selection_lines_are_comments {
14995 edits.extend(
14996 selection_edit_ranges
14997 .iter()
14998 .cloned()
14999 .map(|range| (range, empty_str.clone())),
15000 );
15001 } else {
15002 let min_column = selection_edit_ranges
15003 .iter()
15004 .map(|range| range.start.column)
15005 .min()
15006 .unwrap_or(0);
15007 edits.extend(selection_edit_ranges.iter().map(|range| {
15008 let position = Point::new(range.start.row, min_column);
15009 (position..position, first_prefix.clone())
15010 }));
15011 }
15012 } else if let Some(BlockCommentConfig {
15013 start: full_comment_prefix,
15014 end: comment_suffix,
15015 ..
15016 }) = language.block_comment()
15017 {
15018 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15019 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15020 let prefix_range = comment_prefix_range(
15021 snapshot.deref(),
15022 start_row,
15023 comment_prefix,
15024 comment_prefix_whitespace,
15025 ignore_indent,
15026 );
15027 let suffix_range = comment_suffix_range(
15028 snapshot.deref(),
15029 end_row,
15030 comment_suffix.trim_start_matches(' '),
15031 comment_suffix.starts_with(' '),
15032 );
15033
15034 if prefix_range.is_empty() || suffix_range.is_empty() {
15035 edits.push((
15036 prefix_range.start..prefix_range.start,
15037 full_comment_prefix.clone(),
15038 ));
15039 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15040 suffixes_inserted.push((end_row, comment_suffix.len()));
15041 } else {
15042 edits.push((prefix_range, empty_str.clone()));
15043 edits.push((suffix_range, empty_str.clone()));
15044 }
15045 } else {
15046 continue;
15047 }
15048 }
15049
15050 drop(snapshot);
15051 this.buffer.update(cx, |buffer, cx| {
15052 buffer.edit(edits, None, cx);
15053 });
15054
15055 // Adjust selections so that they end before any comment suffixes that
15056 // were inserted.
15057 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15058 let mut selections = this.selections.all::<Point>(cx);
15059 let snapshot = this.buffer.read(cx).read(cx);
15060 for selection in &mut selections {
15061 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15062 match row.cmp(&MultiBufferRow(selection.end.row)) {
15063 Ordering::Less => {
15064 suffixes_inserted.next();
15065 continue;
15066 }
15067 Ordering::Greater => break,
15068 Ordering::Equal => {
15069 if selection.end.column == snapshot.line_len(row) {
15070 if selection.is_empty() {
15071 selection.start.column -= suffix_len as u32;
15072 }
15073 selection.end.column -= suffix_len as u32;
15074 }
15075 break;
15076 }
15077 }
15078 }
15079 }
15080
15081 drop(snapshot);
15082 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15083
15084 let selections = this.selections.all::<Point>(cx);
15085 let selections_on_single_row = selections.windows(2).all(|selections| {
15086 selections[0].start.row == selections[1].start.row
15087 && selections[0].end.row == selections[1].end.row
15088 && selections[0].start.row == selections[0].end.row
15089 });
15090 let selections_selecting = selections
15091 .iter()
15092 .any(|selection| selection.start != selection.end);
15093 let advance_downwards = action.advance_downwards
15094 && selections_on_single_row
15095 && !selections_selecting
15096 && !matches!(this.mode, EditorMode::SingleLine);
15097
15098 if advance_downwards {
15099 let snapshot = this.buffer.read(cx).snapshot(cx);
15100
15101 this.change_selections(Default::default(), window, cx, |s| {
15102 s.move_cursors_with(|display_snapshot, display_point, _| {
15103 let mut point = display_point.to_point(display_snapshot);
15104 point.row += 1;
15105 point = snapshot.clip_point(point, Bias::Left);
15106 let display_point = point.to_display_point(display_snapshot);
15107 let goal = SelectionGoal::HorizontalPosition(
15108 display_snapshot
15109 .x_for_display_point(display_point, text_layout_details)
15110 .into(),
15111 );
15112 (display_point, goal)
15113 })
15114 });
15115 }
15116 });
15117 }
15118
15119 pub fn select_enclosing_symbol(
15120 &mut self,
15121 _: &SelectEnclosingSymbol,
15122 window: &mut Window,
15123 cx: &mut Context<Self>,
15124 ) {
15125 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15126
15127 let buffer = self.buffer.read(cx).snapshot(cx);
15128 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
15129
15130 fn update_selection(
15131 selection: &Selection<usize>,
15132 buffer_snap: &MultiBufferSnapshot,
15133 ) -> Option<Selection<usize>> {
15134 let cursor = selection.head();
15135 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15136 for symbol in symbols.iter().rev() {
15137 let start = symbol.range.start.to_offset(buffer_snap);
15138 let end = symbol.range.end.to_offset(buffer_snap);
15139 let new_range = start..end;
15140 if start < selection.start || end > selection.end {
15141 return Some(Selection {
15142 id: selection.id,
15143 start: new_range.start,
15144 end: new_range.end,
15145 goal: SelectionGoal::None,
15146 reversed: selection.reversed,
15147 });
15148 }
15149 }
15150 None
15151 }
15152
15153 let mut selected_larger_symbol = false;
15154 let new_selections = old_selections
15155 .iter()
15156 .map(|selection| match update_selection(selection, &buffer) {
15157 Some(new_selection) => {
15158 if new_selection.range() != selection.range() {
15159 selected_larger_symbol = true;
15160 }
15161 new_selection
15162 }
15163 None => selection.clone(),
15164 })
15165 .collect::<Vec<_>>();
15166
15167 if selected_larger_symbol {
15168 self.change_selections(Default::default(), window, cx, |s| {
15169 s.select(new_selections);
15170 });
15171 }
15172 }
15173
15174 pub fn select_larger_syntax_node(
15175 &mut self,
15176 _: &SelectLargerSyntaxNode,
15177 window: &mut Window,
15178 cx: &mut Context<Self>,
15179 ) {
15180 let Some(visible_row_count) = self.visible_row_count() else {
15181 return;
15182 };
15183 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15184 if old_selections.is_empty() {
15185 return;
15186 }
15187
15188 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15189
15190 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15191 let buffer = self.buffer.read(cx).snapshot(cx);
15192
15193 let mut selected_larger_node = false;
15194 let mut new_selections = old_selections
15195 .iter()
15196 .map(|selection| {
15197 let old_range = selection.start..selection.end;
15198
15199 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15200 // manually select word at selection
15201 if ["string_content", "inline"].contains(&node.kind()) {
15202 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15203 // ignore if word is already selected
15204 if !word_range.is_empty() && old_range != word_range {
15205 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15206 // only select word if start and end point belongs to same word
15207 if word_range == last_word_range {
15208 selected_larger_node = true;
15209 return Selection {
15210 id: selection.id,
15211 start: word_range.start,
15212 end: word_range.end,
15213 goal: SelectionGoal::None,
15214 reversed: selection.reversed,
15215 };
15216 }
15217 }
15218 }
15219 }
15220
15221 let mut new_range = old_range.clone();
15222 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15223 new_range = range;
15224 if !node.is_named() {
15225 continue;
15226 }
15227 if !display_map.intersects_fold(new_range.start)
15228 && !display_map.intersects_fold(new_range.end)
15229 {
15230 break;
15231 }
15232 }
15233
15234 selected_larger_node |= new_range != old_range;
15235 Selection {
15236 id: selection.id,
15237 start: new_range.start,
15238 end: new_range.end,
15239 goal: SelectionGoal::None,
15240 reversed: selection.reversed,
15241 }
15242 })
15243 .collect::<Vec<_>>();
15244
15245 if !selected_larger_node {
15246 return; // don't put this call in the history
15247 }
15248
15249 // scroll based on transformation done to the last selection created by the user
15250 let (last_old, last_new) = old_selections
15251 .last()
15252 .zip(new_selections.last().cloned())
15253 .expect("old_selections isn't empty");
15254
15255 // revert selection
15256 let is_selection_reversed = {
15257 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15258 new_selections.last_mut().expect("checked above").reversed =
15259 should_newest_selection_be_reversed;
15260 should_newest_selection_be_reversed
15261 };
15262
15263 if selected_larger_node {
15264 self.select_syntax_node_history.disable_clearing = true;
15265 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15266 s.select(new_selections.clone());
15267 });
15268 self.select_syntax_node_history.disable_clearing = false;
15269 }
15270
15271 let start_row = last_new.start.to_display_point(&display_map).row().0;
15272 let end_row = last_new.end.to_display_point(&display_map).row().0;
15273 let selection_height = end_row - start_row + 1;
15274 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15275
15276 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15277 let scroll_behavior = if fits_on_the_screen {
15278 self.request_autoscroll(Autoscroll::fit(), cx);
15279 SelectSyntaxNodeScrollBehavior::FitSelection
15280 } else if is_selection_reversed {
15281 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15282 SelectSyntaxNodeScrollBehavior::CursorTop
15283 } else {
15284 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15285 SelectSyntaxNodeScrollBehavior::CursorBottom
15286 };
15287
15288 self.select_syntax_node_history.push((
15289 old_selections,
15290 scroll_behavior,
15291 is_selection_reversed,
15292 ));
15293 }
15294
15295 pub fn select_smaller_syntax_node(
15296 &mut self,
15297 _: &SelectSmallerSyntaxNode,
15298 window: &mut Window,
15299 cx: &mut Context<Self>,
15300 ) {
15301 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15302
15303 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15304 self.select_syntax_node_history.pop()
15305 {
15306 if let Some(selection) = selections.last_mut() {
15307 selection.reversed = is_selection_reversed;
15308 }
15309
15310 self.select_syntax_node_history.disable_clearing = true;
15311 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15312 s.select(selections.to_vec());
15313 });
15314 self.select_syntax_node_history.disable_clearing = false;
15315
15316 match scroll_behavior {
15317 SelectSyntaxNodeScrollBehavior::CursorTop => {
15318 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15319 }
15320 SelectSyntaxNodeScrollBehavior::FitSelection => {
15321 self.request_autoscroll(Autoscroll::fit(), cx);
15322 }
15323 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15324 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15325 }
15326 }
15327 }
15328 }
15329
15330 pub fn unwrap_syntax_node(
15331 &mut self,
15332 _: &UnwrapSyntaxNode,
15333 window: &mut Window,
15334 cx: &mut Context<Self>,
15335 ) {
15336 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15337
15338 let buffer = self.buffer.read(cx).snapshot(cx);
15339 let selections = self
15340 .selections
15341 .all::<usize>(cx)
15342 .into_iter()
15343 // subtracting the offset requires sorting
15344 .sorted_by_key(|i| i.start);
15345
15346 let full_edits = selections
15347 .into_iter()
15348 .filter_map(|selection| {
15349 let child = if selection.is_empty()
15350 && let Some((_, ancestor_range)) =
15351 buffer.syntax_ancestor(selection.start..selection.end)
15352 {
15353 ancestor_range
15354 } else {
15355 selection.range()
15356 };
15357
15358 let mut parent = child.clone();
15359 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15360 parent = ancestor_range;
15361 if parent.start < child.start || parent.end > child.end {
15362 break;
15363 }
15364 }
15365
15366 if parent == child {
15367 return None;
15368 }
15369 let text = buffer.text_for_range(child).collect::<String>();
15370 Some((selection.id, parent, text))
15371 })
15372 .collect::<Vec<_>>();
15373 if full_edits.is_empty() {
15374 return;
15375 }
15376
15377 self.transact(window, cx, |this, window, cx| {
15378 this.buffer.update(cx, |buffer, cx| {
15379 buffer.edit(
15380 full_edits
15381 .iter()
15382 .map(|(_, p, t)| (p.clone(), t.clone()))
15383 .collect::<Vec<_>>(),
15384 None,
15385 cx,
15386 );
15387 });
15388 this.change_selections(Default::default(), window, cx, |s| {
15389 let mut offset = 0;
15390 let mut selections = vec![];
15391 for (id, parent, text) in full_edits {
15392 let start = parent.start - offset;
15393 offset += parent.len() - text.len();
15394 selections.push(Selection {
15395 id,
15396 start,
15397 end: start + text.len(),
15398 reversed: false,
15399 goal: Default::default(),
15400 });
15401 }
15402 s.select(selections);
15403 });
15404 });
15405 }
15406
15407 pub fn select_next_syntax_node(
15408 &mut self,
15409 _: &SelectNextSyntaxNode,
15410 window: &mut Window,
15411 cx: &mut Context<Self>,
15412 ) {
15413 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15414 if old_selections.is_empty() {
15415 return;
15416 }
15417
15418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15419
15420 let buffer = self.buffer.read(cx).snapshot(cx);
15421 let mut selected_sibling = false;
15422
15423 let new_selections = old_selections
15424 .iter()
15425 .map(|selection| {
15426 let old_range = selection.start..selection.end;
15427
15428 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15429 let new_range = node.byte_range();
15430 selected_sibling = true;
15431 Selection {
15432 id: selection.id,
15433 start: new_range.start,
15434 end: new_range.end,
15435 goal: SelectionGoal::None,
15436 reversed: selection.reversed,
15437 }
15438 } else {
15439 selection.clone()
15440 }
15441 })
15442 .collect::<Vec<_>>();
15443
15444 if selected_sibling {
15445 self.change_selections(
15446 SelectionEffects::scroll(Autoscroll::fit()),
15447 window,
15448 cx,
15449 |s| {
15450 s.select(new_selections);
15451 },
15452 );
15453 }
15454 }
15455
15456 pub fn select_prev_syntax_node(
15457 &mut self,
15458 _: &SelectPreviousSyntaxNode,
15459 window: &mut Window,
15460 cx: &mut Context<Self>,
15461 ) {
15462 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15463 if old_selections.is_empty() {
15464 return;
15465 }
15466
15467 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15468
15469 let buffer = self.buffer.read(cx).snapshot(cx);
15470 let mut selected_sibling = false;
15471
15472 let new_selections = old_selections
15473 .iter()
15474 .map(|selection| {
15475 let old_range = selection.start..selection.end;
15476
15477 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15478 let new_range = node.byte_range();
15479 selected_sibling = true;
15480 Selection {
15481 id: selection.id,
15482 start: new_range.start,
15483 end: new_range.end,
15484 goal: SelectionGoal::None,
15485 reversed: selection.reversed,
15486 }
15487 } else {
15488 selection.clone()
15489 }
15490 })
15491 .collect::<Vec<_>>();
15492
15493 if selected_sibling {
15494 self.change_selections(
15495 SelectionEffects::scroll(Autoscroll::fit()),
15496 window,
15497 cx,
15498 |s| {
15499 s.select(new_selections);
15500 },
15501 );
15502 }
15503 }
15504
15505 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15506 if !EditorSettings::get_global(cx).gutter.runnables {
15507 self.clear_tasks();
15508 return Task::ready(());
15509 }
15510 let project = self.project().map(Entity::downgrade);
15511 let task_sources = self.lsp_task_sources(cx);
15512 let multi_buffer = self.buffer.downgrade();
15513 cx.spawn_in(window, async move |editor, cx| {
15514 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15515 let Some(project) = project.and_then(|p| p.upgrade()) else {
15516 return;
15517 };
15518 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15519 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15520 }) else {
15521 return;
15522 };
15523
15524 let hide_runnables = project
15525 .update(cx, |project, _| project.is_via_collab())
15526 .unwrap_or(true);
15527 if hide_runnables {
15528 return;
15529 }
15530 let new_rows =
15531 cx.background_spawn({
15532 let snapshot = display_snapshot.clone();
15533 async move {
15534 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15535 }
15536 })
15537 .await;
15538 let Ok(lsp_tasks) =
15539 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15540 else {
15541 return;
15542 };
15543 let lsp_tasks = lsp_tasks.await;
15544
15545 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15546 lsp_tasks
15547 .into_iter()
15548 .flat_map(|(kind, tasks)| {
15549 tasks.into_iter().filter_map(move |(location, task)| {
15550 Some((kind.clone(), location?, task))
15551 })
15552 })
15553 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15554 let buffer = location.target.buffer;
15555 let buffer_snapshot = buffer.read(cx).snapshot();
15556 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15557 |(excerpt_id, snapshot, _)| {
15558 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15559 display_snapshot
15560 .buffer_snapshot()
15561 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15562 } else {
15563 None
15564 }
15565 },
15566 );
15567 if let Some(offset) = offset {
15568 let task_buffer_range =
15569 location.target.range.to_point(&buffer_snapshot);
15570 let context_buffer_range =
15571 task_buffer_range.to_offset(&buffer_snapshot);
15572 let context_range = BufferOffset(context_buffer_range.start)
15573 ..BufferOffset(context_buffer_range.end);
15574
15575 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15576 .or_insert_with(|| RunnableTasks {
15577 templates: Vec::new(),
15578 offset,
15579 column: task_buffer_range.start.column,
15580 extra_variables: HashMap::default(),
15581 context_range,
15582 })
15583 .templates
15584 .push((kind, task.original_task().clone()));
15585 }
15586
15587 acc
15588 })
15589 }) else {
15590 return;
15591 };
15592
15593 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15594 buffer.language_settings(cx).tasks.prefer_lsp
15595 }) else {
15596 return;
15597 };
15598
15599 let rows = Self::runnable_rows(
15600 project,
15601 display_snapshot,
15602 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15603 new_rows,
15604 cx.clone(),
15605 )
15606 .await;
15607 editor
15608 .update(cx, |editor, _| {
15609 editor.clear_tasks();
15610 for (key, mut value) in rows {
15611 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15612 value.templates.extend(lsp_tasks.templates);
15613 }
15614
15615 editor.insert_tasks(key, value);
15616 }
15617 for (key, value) in lsp_tasks_by_rows {
15618 editor.insert_tasks(key, value);
15619 }
15620 })
15621 .ok();
15622 })
15623 }
15624 fn fetch_runnable_ranges(
15625 snapshot: &DisplaySnapshot,
15626 range: Range<Anchor>,
15627 ) -> Vec<language::RunnableRange> {
15628 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15629 }
15630
15631 fn runnable_rows(
15632 project: Entity<Project>,
15633 snapshot: DisplaySnapshot,
15634 prefer_lsp: bool,
15635 runnable_ranges: Vec<RunnableRange>,
15636 cx: AsyncWindowContext,
15637 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15638 cx.spawn(async move |cx| {
15639 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15640 for mut runnable in runnable_ranges {
15641 let Some(tasks) = cx
15642 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15643 .ok()
15644 else {
15645 continue;
15646 };
15647 let mut tasks = tasks.await;
15648
15649 if prefer_lsp {
15650 tasks.retain(|(task_kind, _)| {
15651 !matches!(task_kind, TaskSourceKind::Language { .. })
15652 });
15653 }
15654 if tasks.is_empty() {
15655 continue;
15656 }
15657
15658 let point = runnable
15659 .run_range
15660 .start
15661 .to_point(&snapshot.buffer_snapshot());
15662 let Some(row) = snapshot
15663 .buffer_snapshot()
15664 .buffer_line_for_row(MultiBufferRow(point.row))
15665 .map(|(_, range)| range.start.row)
15666 else {
15667 continue;
15668 };
15669
15670 let context_range =
15671 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15672 runnable_rows.push((
15673 (runnable.buffer_id, row),
15674 RunnableTasks {
15675 templates: tasks,
15676 offset: snapshot
15677 .buffer_snapshot()
15678 .anchor_before(runnable.run_range.start),
15679 context_range,
15680 column: point.column,
15681 extra_variables: runnable.extra_captures,
15682 },
15683 ));
15684 }
15685 runnable_rows
15686 })
15687 }
15688
15689 fn templates_with_tags(
15690 project: &Entity<Project>,
15691 runnable: &mut Runnable,
15692 cx: &mut App,
15693 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15694 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15695 let (worktree_id, file) = project
15696 .buffer_for_id(runnable.buffer, cx)
15697 .and_then(|buffer| buffer.read(cx).file())
15698 .map(|file| (file.worktree_id(cx), file.clone()))
15699 .unzip();
15700
15701 (
15702 project.task_store().read(cx).task_inventory().cloned(),
15703 worktree_id,
15704 file,
15705 )
15706 });
15707
15708 let tags = mem::take(&mut runnable.tags);
15709 let language = runnable.language.clone();
15710 cx.spawn(async move |cx| {
15711 let mut templates_with_tags = Vec::new();
15712 if let Some(inventory) = inventory {
15713 for RunnableTag(tag) in tags {
15714 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15715 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15716 }) else {
15717 return templates_with_tags;
15718 };
15719 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15720 move |(_, template)| {
15721 template.tags.iter().any(|source_tag| source_tag == &tag)
15722 },
15723 ));
15724 }
15725 }
15726 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15727
15728 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15729 // Strongest source wins; if we have worktree tag binding, prefer that to
15730 // global and language bindings;
15731 // if we have a global binding, prefer that to language binding.
15732 let first_mismatch = templates_with_tags
15733 .iter()
15734 .position(|(tag_source, _)| tag_source != leading_tag_source);
15735 if let Some(index) = first_mismatch {
15736 templates_with_tags.truncate(index);
15737 }
15738 }
15739
15740 templates_with_tags
15741 })
15742 }
15743
15744 pub fn move_to_enclosing_bracket(
15745 &mut self,
15746 _: &MoveToEnclosingBracket,
15747 window: &mut Window,
15748 cx: &mut Context<Self>,
15749 ) {
15750 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15751 self.change_selections(Default::default(), window, cx, |s| {
15752 s.move_offsets_with(|snapshot, selection| {
15753 let Some(enclosing_bracket_ranges) =
15754 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15755 else {
15756 return;
15757 };
15758
15759 let mut best_length = usize::MAX;
15760 let mut best_inside = false;
15761 let mut best_in_bracket_range = false;
15762 let mut best_destination = None;
15763 for (open, close) in enclosing_bracket_ranges {
15764 let close = close.to_inclusive();
15765 let length = close.end() - open.start;
15766 let inside = selection.start >= open.end && selection.end <= *close.start();
15767 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15768 || close.contains(&selection.head());
15769
15770 // If best is next to a bracket and current isn't, skip
15771 if !in_bracket_range && best_in_bracket_range {
15772 continue;
15773 }
15774
15775 // Prefer smaller lengths unless best is inside and current isn't
15776 if length > best_length && (best_inside || !inside) {
15777 continue;
15778 }
15779
15780 best_length = length;
15781 best_inside = inside;
15782 best_in_bracket_range = in_bracket_range;
15783 best_destination = Some(
15784 if close.contains(&selection.start) && close.contains(&selection.end) {
15785 if inside { open.end } else { open.start }
15786 } else if inside {
15787 *close.start()
15788 } else {
15789 *close.end()
15790 },
15791 );
15792 }
15793
15794 if let Some(destination) = best_destination {
15795 selection.collapse_to(destination, SelectionGoal::None);
15796 }
15797 })
15798 });
15799 }
15800
15801 pub fn undo_selection(
15802 &mut self,
15803 _: &UndoSelection,
15804 window: &mut Window,
15805 cx: &mut Context<Self>,
15806 ) {
15807 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15808 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15809 self.selection_history.mode = SelectionHistoryMode::Undoing;
15810 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15811 this.end_selection(window, cx);
15812 this.change_selections(
15813 SelectionEffects::scroll(Autoscroll::newest()),
15814 window,
15815 cx,
15816 |s| s.select_anchors(entry.selections.to_vec()),
15817 );
15818 });
15819 self.selection_history.mode = SelectionHistoryMode::Normal;
15820
15821 self.select_next_state = entry.select_next_state;
15822 self.select_prev_state = entry.select_prev_state;
15823 self.add_selections_state = entry.add_selections_state;
15824 }
15825 }
15826
15827 pub fn redo_selection(
15828 &mut self,
15829 _: &RedoSelection,
15830 window: &mut Window,
15831 cx: &mut Context<Self>,
15832 ) {
15833 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15834 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15835 self.selection_history.mode = SelectionHistoryMode::Redoing;
15836 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15837 this.end_selection(window, cx);
15838 this.change_selections(
15839 SelectionEffects::scroll(Autoscroll::newest()),
15840 window,
15841 cx,
15842 |s| s.select_anchors(entry.selections.to_vec()),
15843 );
15844 });
15845 self.selection_history.mode = SelectionHistoryMode::Normal;
15846
15847 self.select_next_state = entry.select_next_state;
15848 self.select_prev_state = entry.select_prev_state;
15849 self.add_selections_state = entry.add_selections_state;
15850 }
15851 }
15852
15853 pub fn expand_excerpts(
15854 &mut self,
15855 action: &ExpandExcerpts,
15856 _: &mut Window,
15857 cx: &mut Context<Self>,
15858 ) {
15859 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15860 }
15861
15862 pub fn expand_excerpts_down(
15863 &mut self,
15864 action: &ExpandExcerptsDown,
15865 _: &mut Window,
15866 cx: &mut Context<Self>,
15867 ) {
15868 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15869 }
15870
15871 pub fn expand_excerpts_up(
15872 &mut self,
15873 action: &ExpandExcerptsUp,
15874 _: &mut Window,
15875 cx: &mut Context<Self>,
15876 ) {
15877 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15878 }
15879
15880 pub fn expand_excerpts_for_direction(
15881 &mut self,
15882 lines: u32,
15883 direction: ExpandExcerptDirection,
15884
15885 cx: &mut Context<Self>,
15886 ) {
15887 let selections = self.selections.disjoint_anchors_arc();
15888
15889 let lines = if lines == 0 {
15890 EditorSettings::get_global(cx).expand_excerpt_lines
15891 } else {
15892 lines
15893 };
15894
15895 self.buffer.update(cx, |buffer, cx| {
15896 let snapshot = buffer.snapshot(cx);
15897 let mut excerpt_ids = selections
15898 .iter()
15899 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15900 .collect::<Vec<_>>();
15901 excerpt_ids.sort();
15902 excerpt_ids.dedup();
15903 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15904 })
15905 }
15906
15907 pub fn expand_excerpt(
15908 &mut self,
15909 excerpt: ExcerptId,
15910 direction: ExpandExcerptDirection,
15911 window: &mut Window,
15912 cx: &mut Context<Self>,
15913 ) {
15914 let current_scroll_position = self.scroll_position(cx);
15915 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15916 let mut should_scroll_up = false;
15917
15918 if direction == ExpandExcerptDirection::Down {
15919 let multi_buffer = self.buffer.read(cx);
15920 let snapshot = multi_buffer.snapshot(cx);
15921 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15922 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15923 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15924 {
15925 let buffer_snapshot = buffer.read(cx).snapshot();
15926 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15927 let last_row = buffer_snapshot.max_point().row;
15928 let lines_below = last_row.saturating_sub(excerpt_end_row);
15929 should_scroll_up = lines_below >= lines_to_expand;
15930 }
15931 }
15932
15933 self.buffer.update(cx, |buffer, cx| {
15934 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15935 });
15936
15937 if should_scroll_up {
15938 let new_scroll_position =
15939 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15940 self.set_scroll_position(new_scroll_position, window, cx);
15941 }
15942 }
15943
15944 pub fn go_to_singleton_buffer_point(
15945 &mut self,
15946 point: Point,
15947 window: &mut Window,
15948 cx: &mut Context<Self>,
15949 ) {
15950 self.go_to_singleton_buffer_range(point..point, window, cx);
15951 }
15952
15953 pub fn go_to_singleton_buffer_range(
15954 &mut self,
15955 range: Range<Point>,
15956 window: &mut Window,
15957 cx: &mut Context<Self>,
15958 ) {
15959 let multibuffer = self.buffer().read(cx);
15960 let Some(buffer) = multibuffer.as_singleton() else {
15961 return;
15962 };
15963 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15964 return;
15965 };
15966 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15967 return;
15968 };
15969 self.change_selections(
15970 SelectionEffects::default().nav_history(true),
15971 window,
15972 cx,
15973 |s| s.select_anchor_ranges([start..end]),
15974 );
15975 }
15976
15977 pub fn go_to_diagnostic(
15978 &mut self,
15979 action: &GoToDiagnostic,
15980 window: &mut Window,
15981 cx: &mut Context<Self>,
15982 ) {
15983 if !self.diagnostics_enabled() {
15984 return;
15985 }
15986 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15987 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15988 }
15989
15990 pub fn go_to_prev_diagnostic(
15991 &mut self,
15992 action: &GoToPreviousDiagnostic,
15993 window: &mut Window,
15994 cx: &mut Context<Self>,
15995 ) {
15996 if !self.diagnostics_enabled() {
15997 return;
15998 }
15999 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16000 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16001 }
16002
16003 pub fn go_to_diagnostic_impl(
16004 &mut self,
16005 direction: Direction,
16006 severity: GoToDiagnosticSeverityFilter,
16007 window: &mut Window,
16008 cx: &mut Context<Self>,
16009 ) {
16010 let buffer = self.buffer.read(cx).snapshot(cx);
16011 let selection = self.selections.newest::<usize>(cx);
16012
16013 let mut active_group_id = None;
16014 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16015 && active_group.active_range.start.to_offset(&buffer) == selection.start
16016 {
16017 active_group_id = Some(active_group.group_id);
16018 }
16019
16020 fn filtered<'a>(
16021 snapshot: EditorSnapshot,
16022 severity: GoToDiagnosticSeverityFilter,
16023 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16024 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16025 diagnostics
16026 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16027 .filter(|entry| entry.range.start != entry.range.end)
16028 .filter(|entry| !entry.diagnostic.is_unnecessary)
16029 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16030 }
16031
16032 let snapshot = self.snapshot(window, cx);
16033 let before = filtered(
16034 snapshot.clone(),
16035 severity,
16036 buffer
16037 .diagnostics_in_range(0..selection.start)
16038 .filter(|entry| entry.range.start <= selection.start),
16039 );
16040 let after = filtered(
16041 snapshot,
16042 severity,
16043 buffer
16044 .diagnostics_in_range(selection.start..buffer.len())
16045 .filter(|entry| entry.range.start >= selection.start),
16046 );
16047
16048 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16049 if direction == Direction::Prev {
16050 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16051 {
16052 for diagnostic in prev_diagnostics.into_iter().rev() {
16053 if diagnostic.range.start != selection.start
16054 || active_group_id
16055 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16056 {
16057 found = Some(diagnostic);
16058 break 'outer;
16059 }
16060 }
16061 }
16062 } else {
16063 for diagnostic in after.chain(before) {
16064 if diagnostic.range.start != selection.start
16065 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16066 {
16067 found = Some(diagnostic);
16068 break;
16069 }
16070 }
16071 }
16072 let Some(next_diagnostic) = found else {
16073 return;
16074 };
16075
16076 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16077 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16078 return;
16079 };
16080 self.change_selections(Default::default(), window, cx, |s| {
16081 s.select_ranges(vec![
16082 next_diagnostic.range.start..next_diagnostic.range.start,
16083 ])
16084 });
16085 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16086 self.refresh_edit_prediction(false, true, window, cx);
16087 }
16088
16089 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16090 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16091 let snapshot = self.snapshot(window, cx);
16092 let selection = self.selections.newest::<Point>(cx);
16093 self.go_to_hunk_before_or_after_position(
16094 &snapshot,
16095 selection.head(),
16096 Direction::Next,
16097 window,
16098 cx,
16099 );
16100 }
16101
16102 pub fn go_to_hunk_before_or_after_position(
16103 &mut self,
16104 snapshot: &EditorSnapshot,
16105 position: Point,
16106 direction: Direction,
16107 window: &mut Window,
16108 cx: &mut Context<Editor>,
16109 ) {
16110 let row = if direction == Direction::Next {
16111 self.hunk_after_position(snapshot, position)
16112 .map(|hunk| hunk.row_range.start)
16113 } else {
16114 self.hunk_before_position(snapshot, position)
16115 };
16116
16117 if let Some(row) = row {
16118 let destination = Point::new(row.0, 0);
16119 let autoscroll = Autoscroll::center();
16120
16121 self.unfold_ranges(&[destination..destination], false, false, cx);
16122 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16123 s.select_ranges([destination..destination]);
16124 });
16125 }
16126 }
16127
16128 fn hunk_after_position(
16129 &mut self,
16130 snapshot: &EditorSnapshot,
16131 position: Point,
16132 ) -> Option<MultiBufferDiffHunk> {
16133 snapshot
16134 .buffer_snapshot()
16135 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16136 .find(|hunk| hunk.row_range.start.0 > position.row)
16137 .or_else(|| {
16138 snapshot
16139 .buffer_snapshot()
16140 .diff_hunks_in_range(Point::zero()..position)
16141 .find(|hunk| hunk.row_range.end.0 < position.row)
16142 })
16143 }
16144
16145 fn go_to_prev_hunk(
16146 &mut self,
16147 _: &GoToPreviousHunk,
16148 window: &mut Window,
16149 cx: &mut Context<Self>,
16150 ) {
16151 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16152 let snapshot = self.snapshot(window, cx);
16153 let selection = self.selections.newest::<Point>(cx);
16154 self.go_to_hunk_before_or_after_position(
16155 &snapshot,
16156 selection.head(),
16157 Direction::Prev,
16158 window,
16159 cx,
16160 );
16161 }
16162
16163 fn hunk_before_position(
16164 &mut self,
16165 snapshot: &EditorSnapshot,
16166 position: Point,
16167 ) -> Option<MultiBufferRow> {
16168 snapshot
16169 .buffer_snapshot()
16170 .diff_hunk_before(position)
16171 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16172 }
16173
16174 fn go_to_next_change(
16175 &mut self,
16176 _: &GoToNextChange,
16177 window: &mut Window,
16178 cx: &mut Context<Self>,
16179 ) {
16180 if let Some(selections) = self
16181 .change_list
16182 .next_change(1, Direction::Next)
16183 .map(|s| s.to_vec())
16184 {
16185 self.change_selections(Default::default(), window, cx, |s| {
16186 let map = s.display_map();
16187 s.select_display_ranges(selections.iter().map(|a| {
16188 let point = a.to_display_point(&map);
16189 point..point
16190 }))
16191 })
16192 }
16193 }
16194
16195 fn go_to_previous_change(
16196 &mut self,
16197 _: &GoToPreviousChange,
16198 window: &mut Window,
16199 cx: &mut Context<Self>,
16200 ) {
16201 if let Some(selections) = self
16202 .change_list
16203 .next_change(1, Direction::Prev)
16204 .map(|s| s.to_vec())
16205 {
16206 self.change_selections(Default::default(), window, cx, |s| {
16207 let map = s.display_map();
16208 s.select_display_ranges(selections.iter().map(|a| {
16209 let point = a.to_display_point(&map);
16210 point..point
16211 }))
16212 })
16213 }
16214 }
16215
16216 pub fn go_to_next_document_highlight(
16217 &mut self,
16218 _: &GoToNextDocumentHighlight,
16219 window: &mut Window,
16220 cx: &mut Context<Self>,
16221 ) {
16222 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16223 }
16224
16225 pub fn go_to_prev_document_highlight(
16226 &mut self,
16227 _: &GoToPreviousDocumentHighlight,
16228 window: &mut Window,
16229 cx: &mut Context<Self>,
16230 ) {
16231 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16232 }
16233
16234 pub fn go_to_document_highlight_before_or_after_position(
16235 &mut self,
16236 direction: Direction,
16237 window: &mut Window,
16238 cx: &mut Context<Editor>,
16239 ) {
16240 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16241 let snapshot = self.snapshot(window, cx);
16242 let buffer = &snapshot.buffer_snapshot();
16243 let position = self.selections.newest::<Point>(cx).head();
16244 let anchor_position = buffer.anchor_after(position);
16245
16246 // Get all document highlights (both read and write)
16247 let mut all_highlights = Vec::new();
16248
16249 if let Some((_, read_highlights)) = self
16250 .background_highlights
16251 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16252 {
16253 all_highlights.extend(read_highlights.iter());
16254 }
16255
16256 if let Some((_, write_highlights)) = self
16257 .background_highlights
16258 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16259 {
16260 all_highlights.extend(write_highlights.iter());
16261 }
16262
16263 if all_highlights.is_empty() {
16264 return;
16265 }
16266
16267 // Sort highlights by position
16268 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16269
16270 let target_highlight = match direction {
16271 Direction::Next => {
16272 // Find the first highlight after the current position
16273 all_highlights
16274 .iter()
16275 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16276 }
16277 Direction::Prev => {
16278 // Find the last highlight before the current position
16279 all_highlights
16280 .iter()
16281 .rev()
16282 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16283 }
16284 };
16285
16286 if let Some(highlight) = target_highlight {
16287 let destination = highlight.start.to_point(buffer);
16288 let autoscroll = Autoscroll::center();
16289
16290 self.unfold_ranges(&[destination..destination], false, false, cx);
16291 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16292 s.select_ranges([destination..destination]);
16293 });
16294 }
16295 }
16296
16297 fn go_to_line<T: 'static>(
16298 &mut self,
16299 position: Anchor,
16300 highlight_color: Option<Hsla>,
16301 window: &mut Window,
16302 cx: &mut Context<Self>,
16303 ) {
16304 let snapshot = self.snapshot(window, cx).display_snapshot;
16305 let position = position.to_point(&snapshot.buffer_snapshot());
16306 let start = snapshot
16307 .buffer_snapshot()
16308 .clip_point(Point::new(position.row, 0), Bias::Left);
16309 let end = start + Point::new(1, 0);
16310 let start = snapshot.buffer_snapshot().anchor_before(start);
16311 let end = snapshot.buffer_snapshot().anchor_before(end);
16312
16313 self.highlight_rows::<T>(
16314 start..end,
16315 highlight_color
16316 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16317 Default::default(),
16318 cx,
16319 );
16320
16321 if self.buffer.read(cx).is_singleton() {
16322 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16323 }
16324 }
16325
16326 pub fn go_to_definition(
16327 &mut self,
16328 _: &GoToDefinition,
16329 window: &mut Window,
16330 cx: &mut Context<Self>,
16331 ) -> Task<Result<Navigated>> {
16332 let definition =
16333 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16334 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16335 cx.spawn_in(window, async move |editor, cx| {
16336 if definition.await? == Navigated::Yes {
16337 return Ok(Navigated::Yes);
16338 }
16339 match fallback_strategy {
16340 GoToDefinitionFallback::None => Ok(Navigated::No),
16341 GoToDefinitionFallback::FindAllReferences => {
16342 match editor.update_in(cx, |editor, window, cx| {
16343 editor.find_all_references(&FindAllReferences, window, cx)
16344 })? {
16345 Some(references) => references.await,
16346 None => Ok(Navigated::No),
16347 }
16348 }
16349 }
16350 })
16351 }
16352
16353 pub fn go_to_declaration(
16354 &mut self,
16355 _: &GoToDeclaration,
16356 window: &mut Window,
16357 cx: &mut Context<Self>,
16358 ) -> Task<Result<Navigated>> {
16359 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16360 }
16361
16362 pub fn go_to_declaration_split(
16363 &mut self,
16364 _: &GoToDeclaration,
16365 window: &mut Window,
16366 cx: &mut Context<Self>,
16367 ) -> Task<Result<Navigated>> {
16368 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16369 }
16370
16371 pub fn go_to_implementation(
16372 &mut self,
16373 _: &GoToImplementation,
16374 window: &mut Window,
16375 cx: &mut Context<Self>,
16376 ) -> Task<Result<Navigated>> {
16377 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16378 }
16379
16380 pub fn go_to_implementation_split(
16381 &mut self,
16382 _: &GoToImplementationSplit,
16383 window: &mut Window,
16384 cx: &mut Context<Self>,
16385 ) -> Task<Result<Navigated>> {
16386 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16387 }
16388
16389 pub fn go_to_type_definition(
16390 &mut self,
16391 _: &GoToTypeDefinition,
16392 window: &mut Window,
16393 cx: &mut Context<Self>,
16394 ) -> Task<Result<Navigated>> {
16395 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16396 }
16397
16398 pub fn go_to_definition_split(
16399 &mut self,
16400 _: &GoToDefinitionSplit,
16401 window: &mut Window,
16402 cx: &mut Context<Self>,
16403 ) -> Task<Result<Navigated>> {
16404 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16405 }
16406
16407 pub fn go_to_type_definition_split(
16408 &mut self,
16409 _: &GoToTypeDefinitionSplit,
16410 window: &mut Window,
16411 cx: &mut Context<Self>,
16412 ) -> Task<Result<Navigated>> {
16413 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16414 }
16415
16416 fn go_to_definition_of_kind(
16417 &mut self,
16418 kind: GotoDefinitionKind,
16419 split: bool,
16420 window: &mut Window,
16421 cx: &mut Context<Self>,
16422 ) -> Task<Result<Navigated>> {
16423 let Some(provider) = self.semantics_provider.clone() else {
16424 return Task::ready(Ok(Navigated::No));
16425 };
16426 let head = self.selections.newest::<usize>(cx).head();
16427 let buffer = self.buffer.read(cx);
16428 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16429 return Task::ready(Ok(Navigated::No));
16430 };
16431 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16432 return Task::ready(Ok(Navigated::No));
16433 };
16434
16435 cx.spawn_in(window, async move |editor, cx| {
16436 let Some(definitions) = definitions.await? else {
16437 return Ok(Navigated::No);
16438 };
16439 let navigated = editor
16440 .update_in(cx, |editor, window, cx| {
16441 editor.navigate_to_hover_links(
16442 Some(kind),
16443 definitions
16444 .into_iter()
16445 .filter(|location| {
16446 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16447 })
16448 .map(HoverLink::Text)
16449 .collect::<Vec<_>>(),
16450 split,
16451 window,
16452 cx,
16453 )
16454 })?
16455 .await?;
16456 anyhow::Ok(navigated)
16457 })
16458 }
16459
16460 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16461 let selection = self.selections.newest_anchor();
16462 let head = selection.head();
16463 let tail = selection.tail();
16464
16465 let Some((buffer, start_position)) =
16466 self.buffer.read(cx).text_anchor_for_position(head, cx)
16467 else {
16468 return;
16469 };
16470
16471 let end_position = if head != tail {
16472 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16473 return;
16474 };
16475 Some(pos)
16476 } else {
16477 None
16478 };
16479
16480 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16481 let url = if let Some(end_pos) = end_position {
16482 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16483 } else {
16484 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16485 };
16486
16487 if let Some(url) = url {
16488 cx.update(|window, cx| {
16489 if parse_zed_link(&url, cx).is_some() {
16490 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16491 } else {
16492 cx.open_url(&url);
16493 }
16494 })?;
16495 }
16496
16497 anyhow::Ok(())
16498 });
16499
16500 url_finder.detach();
16501 }
16502
16503 pub fn open_selected_filename(
16504 &mut self,
16505 _: &OpenSelectedFilename,
16506 window: &mut Window,
16507 cx: &mut Context<Self>,
16508 ) {
16509 let Some(workspace) = self.workspace() else {
16510 return;
16511 };
16512
16513 let position = self.selections.newest_anchor().head();
16514
16515 let Some((buffer, buffer_position)) =
16516 self.buffer.read(cx).text_anchor_for_position(position, cx)
16517 else {
16518 return;
16519 };
16520
16521 let project = self.project.clone();
16522
16523 cx.spawn_in(window, async move |_, cx| {
16524 let result = find_file(&buffer, project, buffer_position, cx).await;
16525
16526 if let Some((_, path)) = result {
16527 workspace
16528 .update_in(cx, |workspace, window, cx| {
16529 workspace.open_resolved_path(path, window, cx)
16530 })?
16531 .await?;
16532 }
16533 anyhow::Ok(())
16534 })
16535 .detach();
16536 }
16537
16538 pub(crate) fn navigate_to_hover_links(
16539 &mut self,
16540 kind: Option<GotoDefinitionKind>,
16541 definitions: Vec<HoverLink>,
16542 split: bool,
16543 window: &mut Window,
16544 cx: &mut Context<Editor>,
16545 ) -> Task<Result<Navigated>> {
16546 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16547 let mut first_url_or_file = None;
16548 let definitions: Vec<_> = definitions
16549 .into_iter()
16550 .filter_map(|def| match def {
16551 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16552 HoverLink::InlayHint(lsp_location, server_id) => {
16553 let computation =
16554 self.compute_target_location(lsp_location, server_id, window, cx);
16555 Some(cx.background_spawn(computation))
16556 }
16557 HoverLink::Url(url) => {
16558 first_url_or_file = Some(Either::Left(url));
16559 None
16560 }
16561 HoverLink::File(path) => {
16562 first_url_or_file = Some(Either::Right(path));
16563 None
16564 }
16565 })
16566 .collect();
16567
16568 let workspace = self.workspace();
16569
16570 cx.spawn_in(window, async move |editor, cx| {
16571 let locations: Vec<Location> = future::join_all(definitions)
16572 .await
16573 .into_iter()
16574 .filter_map(|location| location.transpose())
16575 .collect::<Result<_>>()
16576 .context("location tasks")?;
16577 let mut locations = cx.update(|_, cx| {
16578 locations
16579 .into_iter()
16580 .map(|location| {
16581 let buffer = location.buffer.read(cx);
16582 (location.buffer, location.range.to_point(buffer))
16583 })
16584 .into_group_map()
16585 })?;
16586 let mut num_locations = 0;
16587 for ranges in locations.values_mut() {
16588 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16589 ranges.dedup();
16590 num_locations += ranges.len();
16591 }
16592
16593 if num_locations > 1 {
16594 let Some(workspace) = workspace else {
16595 return Ok(Navigated::No);
16596 };
16597
16598 let tab_kind = match kind {
16599 Some(GotoDefinitionKind::Implementation) => "Implementations",
16600 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16601 Some(GotoDefinitionKind::Declaration) => "Declarations",
16602 Some(GotoDefinitionKind::Type) => "Types",
16603 };
16604 let title = editor
16605 .update_in(cx, |_, _, cx| {
16606 let target = locations
16607 .iter()
16608 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16609 .map(|(buffer, location)| {
16610 buffer
16611 .read(cx)
16612 .text_for_range(location.clone())
16613 .collect::<String>()
16614 })
16615 .filter(|text| !text.contains('\n'))
16616 .unique()
16617 .take(3)
16618 .join(", ");
16619 if target.is_empty() {
16620 tab_kind.to_owned()
16621 } else {
16622 format!("{tab_kind} for {target}")
16623 }
16624 })
16625 .context("buffer title")?;
16626
16627 let opened = workspace
16628 .update_in(cx, |workspace, window, cx| {
16629 Self::open_locations_in_multibuffer(
16630 workspace,
16631 locations,
16632 title,
16633 split,
16634 MultibufferSelectionMode::First,
16635 window,
16636 cx,
16637 )
16638 })
16639 .is_ok();
16640
16641 anyhow::Ok(Navigated::from_bool(opened))
16642 } else if num_locations == 0 {
16643 // If there is one url or file, open it directly
16644 match first_url_or_file {
16645 Some(Either::Left(url)) => {
16646 cx.update(|_, cx| cx.open_url(&url))?;
16647 Ok(Navigated::Yes)
16648 }
16649 Some(Either::Right(path)) => {
16650 let Some(workspace) = workspace else {
16651 return Ok(Navigated::No);
16652 };
16653
16654 workspace
16655 .update_in(cx, |workspace, window, cx| {
16656 workspace.open_resolved_path(path, window, cx)
16657 })?
16658 .await?;
16659 Ok(Navigated::Yes)
16660 }
16661 None => Ok(Navigated::No),
16662 }
16663 } else {
16664 let Some(workspace) = workspace else {
16665 return Ok(Navigated::No);
16666 };
16667
16668 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16669 let target_range = target_ranges.first().unwrap().clone();
16670
16671 editor.update_in(cx, |editor, window, cx| {
16672 let range = target_range.to_point(target_buffer.read(cx));
16673 let range = editor.range_for_match(&range);
16674 let range = collapse_multiline_range(range);
16675
16676 if !split
16677 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16678 {
16679 editor.go_to_singleton_buffer_range(range, window, cx);
16680 } else {
16681 let pane = workspace.read(cx).active_pane().clone();
16682 window.defer(cx, move |window, cx| {
16683 let target_editor: Entity<Self> =
16684 workspace.update(cx, |workspace, cx| {
16685 let pane = if split {
16686 workspace.adjacent_pane(window, cx)
16687 } else {
16688 workspace.active_pane().clone()
16689 };
16690
16691 workspace.open_project_item(
16692 pane,
16693 target_buffer.clone(),
16694 true,
16695 true,
16696 window,
16697 cx,
16698 )
16699 });
16700 target_editor.update(cx, |target_editor, cx| {
16701 // When selecting a definition in a different buffer, disable the nav history
16702 // to avoid creating a history entry at the previous cursor location.
16703 pane.update(cx, |pane, _| pane.disable_history());
16704 target_editor.go_to_singleton_buffer_range(range, window, cx);
16705 pane.update(cx, |pane, _| pane.enable_history());
16706 });
16707 });
16708 }
16709 Navigated::Yes
16710 })
16711 }
16712 })
16713 }
16714
16715 fn compute_target_location(
16716 &self,
16717 lsp_location: lsp::Location,
16718 server_id: LanguageServerId,
16719 window: &mut Window,
16720 cx: &mut Context<Self>,
16721 ) -> Task<anyhow::Result<Option<Location>>> {
16722 let Some(project) = self.project.clone() else {
16723 return Task::ready(Ok(None));
16724 };
16725
16726 cx.spawn_in(window, async move |editor, cx| {
16727 let location_task = editor.update(cx, |_, cx| {
16728 project.update(cx, |project, cx| {
16729 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16730 })
16731 })?;
16732 let location = Some({
16733 let target_buffer_handle = location_task.await.context("open local buffer")?;
16734 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16735 let target_start = target_buffer
16736 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16737 let target_end = target_buffer
16738 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16739 target_buffer.anchor_after(target_start)
16740 ..target_buffer.anchor_before(target_end)
16741 })?;
16742 Location {
16743 buffer: target_buffer_handle,
16744 range,
16745 }
16746 });
16747 Ok(location)
16748 })
16749 }
16750
16751 pub fn find_all_references(
16752 &mut self,
16753 _: &FindAllReferences,
16754 window: &mut Window,
16755 cx: &mut Context<Self>,
16756 ) -> Option<Task<Result<Navigated>>> {
16757 let selection = self.selections.newest::<usize>(cx);
16758 let multi_buffer = self.buffer.read(cx);
16759 let head = selection.head();
16760
16761 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16762 let head_anchor = multi_buffer_snapshot.anchor_at(
16763 head,
16764 if head < selection.tail() {
16765 Bias::Right
16766 } else {
16767 Bias::Left
16768 },
16769 );
16770
16771 match self
16772 .find_all_references_task_sources
16773 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16774 {
16775 Ok(_) => {
16776 log::info!(
16777 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16778 );
16779 return None;
16780 }
16781 Err(i) => {
16782 self.find_all_references_task_sources.insert(i, head_anchor);
16783 }
16784 }
16785
16786 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16787 let workspace = self.workspace()?;
16788 let project = workspace.read(cx).project().clone();
16789 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16790 Some(cx.spawn_in(window, async move |editor, cx| {
16791 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16792 if let Ok(i) = editor
16793 .find_all_references_task_sources
16794 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16795 {
16796 editor.find_all_references_task_sources.remove(i);
16797 }
16798 });
16799
16800 let Some(locations) = references.await? else {
16801 return anyhow::Ok(Navigated::No);
16802 };
16803 let mut locations = cx.update(|_, cx| {
16804 locations
16805 .into_iter()
16806 .map(|location| {
16807 let buffer = location.buffer.read(cx);
16808 (location.buffer, location.range.to_point(buffer))
16809 })
16810 .into_group_map()
16811 })?;
16812 if locations.is_empty() {
16813 return anyhow::Ok(Navigated::No);
16814 }
16815 for ranges in locations.values_mut() {
16816 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16817 ranges.dedup();
16818 }
16819
16820 workspace.update_in(cx, |workspace, window, cx| {
16821 let target = locations
16822 .iter()
16823 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16824 .map(|(buffer, location)| {
16825 buffer
16826 .read(cx)
16827 .text_for_range(location.clone())
16828 .collect::<String>()
16829 })
16830 .filter(|text| !text.contains('\n'))
16831 .unique()
16832 .take(3)
16833 .join(", ");
16834 let title = if target.is_empty() {
16835 "References".to_owned()
16836 } else {
16837 format!("References to {target}")
16838 };
16839 Self::open_locations_in_multibuffer(
16840 workspace,
16841 locations,
16842 title,
16843 false,
16844 MultibufferSelectionMode::First,
16845 window,
16846 cx,
16847 );
16848 Navigated::Yes
16849 })
16850 }))
16851 }
16852
16853 /// Opens a multibuffer with the given project locations in it
16854 pub fn open_locations_in_multibuffer(
16855 workspace: &mut Workspace,
16856 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16857 title: String,
16858 split: bool,
16859 multibuffer_selection_mode: MultibufferSelectionMode,
16860 window: &mut Window,
16861 cx: &mut Context<Workspace>,
16862 ) {
16863 if locations.is_empty() {
16864 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16865 return;
16866 }
16867
16868 let capability = workspace.project().read(cx).capability();
16869 let mut ranges = <Vec<Range<Anchor>>>::new();
16870
16871 // a key to find existing multibuffer editors with the same set of locations
16872 // to prevent us from opening more and more multibuffer tabs for searches and the like
16873 let mut key = (title.clone(), vec![]);
16874 let excerpt_buffer = cx.new(|cx| {
16875 let key = &mut key.1;
16876 let mut multibuffer = MultiBuffer::new(capability);
16877 for (buffer, mut ranges_for_buffer) in locations {
16878 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16879 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16880 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16881 PathKey::for_buffer(&buffer, cx),
16882 buffer.clone(),
16883 ranges_for_buffer,
16884 multibuffer_context_lines(cx),
16885 cx,
16886 );
16887 ranges.extend(new_ranges)
16888 }
16889
16890 multibuffer.with_title(title)
16891 });
16892 let existing = workspace.active_pane().update(cx, |pane, cx| {
16893 pane.items()
16894 .filter_map(|item| item.downcast::<Editor>())
16895 .find(|editor| {
16896 editor
16897 .read(cx)
16898 .lookup_key
16899 .as_ref()
16900 .and_then(|it| {
16901 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16902 })
16903 .is_some_and(|it| *it == key)
16904 })
16905 });
16906 let editor = existing.unwrap_or_else(|| {
16907 cx.new(|cx| {
16908 let mut editor = Editor::for_multibuffer(
16909 excerpt_buffer,
16910 Some(workspace.project().clone()),
16911 window,
16912 cx,
16913 );
16914 editor.lookup_key = Some(Box::new(key));
16915 editor
16916 })
16917 });
16918 editor.update(cx, |editor, cx| {
16919 match multibuffer_selection_mode {
16920 MultibufferSelectionMode::First => {
16921 if let Some(first_range) = ranges.first() {
16922 editor.change_selections(
16923 SelectionEffects::no_scroll(),
16924 window,
16925 cx,
16926 |selections| {
16927 selections.clear_disjoint();
16928 selections
16929 .select_anchor_ranges(std::iter::once(first_range.clone()));
16930 },
16931 );
16932 }
16933 editor.highlight_background::<Self>(
16934 &ranges,
16935 |theme| theme.colors().editor_highlighted_line_background,
16936 cx,
16937 );
16938 }
16939 MultibufferSelectionMode::All => {
16940 editor.change_selections(
16941 SelectionEffects::no_scroll(),
16942 window,
16943 cx,
16944 |selections| {
16945 selections.clear_disjoint();
16946 selections.select_anchor_ranges(ranges);
16947 },
16948 );
16949 }
16950 }
16951 editor.register_buffers_with_language_servers(cx);
16952 });
16953
16954 let item = Box::new(editor);
16955 let item_id = item.item_id();
16956
16957 if split {
16958 let pane = workspace.adjacent_pane(window, cx);
16959 workspace.add_item(pane, item, None, true, true, window, cx);
16960 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16961 let (preview_item_id, preview_item_idx) =
16962 workspace.active_pane().read_with(cx, |pane, _| {
16963 (pane.preview_item_id(), pane.preview_item_idx())
16964 });
16965
16966 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16967
16968 if let Some(preview_item_id) = preview_item_id {
16969 workspace.active_pane().update(cx, |pane, cx| {
16970 pane.remove_item(preview_item_id, false, false, window, cx);
16971 });
16972 }
16973 } else {
16974 workspace.add_item_to_active_pane(item, None, true, window, cx);
16975 }
16976 workspace.active_pane().update(cx, |pane, cx| {
16977 pane.set_preview_item_id(Some(item_id), cx);
16978 });
16979 }
16980
16981 pub fn rename(
16982 &mut self,
16983 _: &Rename,
16984 window: &mut Window,
16985 cx: &mut Context<Self>,
16986 ) -> Option<Task<Result<()>>> {
16987 use language::ToOffset as _;
16988
16989 let provider = self.semantics_provider.clone()?;
16990 let selection = self.selections.newest_anchor().clone();
16991 let (cursor_buffer, cursor_buffer_position) = self
16992 .buffer
16993 .read(cx)
16994 .text_anchor_for_position(selection.head(), cx)?;
16995 let (tail_buffer, cursor_buffer_position_end) = self
16996 .buffer
16997 .read(cx)
16998 .text_anchor_for_position(selection.tail(), cx)?;
16999 if tail_buffer != cursor_buffer {
17000 return None;
17001 }
17002
17003 let snapshot = cursor_buffer.read(cx).snapshot();
17004 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17005 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17006 let prepare_rename = provider
17007 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17008 .unwrap_or_else(|| Task::ready(Ok(None)));
17009 drop(snapshot);
17010
17011 Some(cx.spawn_in(window, async move |this, cx| {
17012 let rename_range = if let Some(range) = prepare_rename.await? {
17013 Some(range)
17014 } else {
17015 this.update(cx, |this, cx| {
17016 let buffer = this.buffer.read(cx).snapshot(cx);
17017 let mut buffer_highlights = this
17018 .document_highlights_for_position(selection.head(), &buffer)
17019 .filter(|highlight| {
17020 highlight.start.excerpt_id == selection.head().excerpt_id
17021 && highlight.end.excerpt_id == selection.head().excerpt_id
17022 });
17023 buffer_highlights
17024 .next()
17025 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17026 })?
17027 };
17028 if let Some(rename_range) = rename_range {
17029 this.update_in(cx, |this, window, cx| {
17030 let snapshot = cursor_buffer.read(cx).snapshot();
17031 let rename_buffer_range = rename_range.to_offset(&snapshot);
17032 let cursor_offset_in_rename_range =
17033 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17034 let cursor_offset_in_rename_range_end =
17035 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17036
17037 this.take_rename(false, window, cx);
17038 let buffer = this.buffer.read(cx).read(cx);
17039 let cursor_offset = selection.head().to_offset(&buffer);
17040 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17041 let rename_end = rename_start + rename_buffer_range.len();
17042 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17043 let mut old_highlight_id = None;
17044 let old_name: Arc<str> = buffer
17045 .chunks(rename_start..rename_end, true)
17046 .map(|chunk| {
17047 if old_highlight_id.is_none() {
17048 old_highlight_id = chunk.syntax_highlight_id;
17049 }
17050 chunk.text
17051 })
17052 .collect::<String>()
17053 .into();
17054
17055 drop(buffer);
17056
17057 // Position the selection in the rename editor so that it matches the current selection.
17058 this.show_local_selections = false;
17059 let rename_editor = cx.new(|cx| {
17060 let mut editor = Editor::single_line(window, cx);
17061 editor.buffer.update(cx, |buffer, cx| {
17062 buffer.edit([(0..0, old_name.clone())], None, cx)
17063 });
17064 let rename_selection_range = match cursor_offset_in_rename_range
17065 .cmp(&cursor_offset_in_rename_range_end)
17066 {
17067 Ordering::Equal => {
17068 editor.select_all(&SelectAll, window, cx);
17069 return editor;
17070 }
17071 Ordering::Less => {
17072 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17073 }
17074 Ordering::Greater => {
17075 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17076 }
17077 };
17078 if rename_selection_range.end > old_name.len() {
17079 editor.select_all(&SelectAll, window, cx);
17080 } else {
17081 editor.change_selections(Default::default(), window, cx, |s| {
17082 s.select_ranges([rename_selection_range]);
17083 });
17084 }
17085 editor
17086 });
17087 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17088 if e == &EditorEvent::Focused {
17089 cx.emit(EditorEvent::FocusedIn)
17090 }
17091 })
17092 .detach();
17093
17094 let write_highlights =
17095 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17096 let read_highlights =
17097 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17098 let ranges = write_highlights
17099 .iter()
17100 .flat_map(|(_, ranges)| ranges.iter())
17101 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17102 .cloned()
17103 .collect();
17104
17105 this.highlight_text::<Rename>(
17106 ranges,
17107 HighlightStyle {
17108 fade_out: Some(0.6),
17109 ..Default::default()
17110 },
17111 cx,
17112 );
17113 let rename_focus_handle = rename_editor.focus_handle(cx);
17114 window.focus(&rename_focus_handle);
17115 let block_id = this.insert_blocks(
17116 [BlockProperties {
17117 style: BlockStyle::Flex,
17118 placement: BlockPlacement::Below(range.start),
17119 height: Some(1),
17120 render: Arc::new({
17121 let rename_editor = rename_editor.clone();
17122 move |cx: &mut BlockContext| {
17123 let mut text_style = cx.editor_style.text.clone();
17124 if let Some(highlight_style) = old_highlight_id
17125 .and_then(|h| h.style(&cx.editor_style.syntax))
17126 {
17127 text_style = text_style.highlight(highlight_style);
17128 }
17129 div()
17130 .block_mouse_except_scroll()
17131 .pl(cx.anchor_x)
17132 .child(EditorElement::new(
17133 &rename_editor,
17134 EditorStyle {
17135 background: cx.theme().system().transparent,
17136 local_player: cx.editor_style.local_player,
17137 text: text_style,
17138 scrollbar_width: cx.editor_style.scrollbar_width,
17139 syntax: cx.editor_style.syntax.clone(),
17140 status: cx.editor_style.status.clone(),
17141 inlay_hints_style: HighlightStyle {
17142 font_weight: Some(FontWeight::BOLD),
17143 ..make_inlay_hints_style(cx.app)
17144 },
17145 edit_prediction_styles: make_suggestion_styles(
17146 cx.app,
17147 ),
17148 ..EditorStyle::default()
17149 },
17150 ))
17151 .into_any_element()
17152 }
17153 }),
17154 priority: 0,
17155 }],
17156 Some(Autoscroll::fit()),
17157 cx,
17158 )[0];
17159 this.pending_rename = Some(RenameState {
17160 range,
17161 old_name,
17162 editor: rename_editor,
17163 block_id,
17164 });
17165 })?;
17166 }
17167
17168 Ok(())
17169 }))
17170 }
17171
17172 pub fn confirm_rename(
17173 &mut self,
17174 _: &ConfirmRename,
17175 window: &mut Window,
17176 cx: &mut Context<Self>,
17177 ) -> Option<Task<Result<()>>> {
17178 let rename = self.take_rename(false, window, cx)?;
17179 let workspace = self.workspace()?.downgrade();
17180 let (buffer, start) = self
17181 .buffer
17182 .read(cx)
17183 .text_anchor_for_position(rename.range.start, cx)?;
17184 let (end_buffer, _) = self
17185 .buffer
17186 .read(cx)
17187 .text_anchor_for_position(rename.range.end, cx)?;
17188 if buffer != end_buffer {
17189 return None;
17190 }
17191
17192 let old_name = rename.old_name;
17193 let new_name = rename.editor.read(cx).text(cx);
17194
17195 let rename = self.semantics_provider.as_ref()?.perform_rename(
17196 &buffer,
17197 start,
17198 new_name.clone(),
17199 cx,
17200 )?;
17201
17202 Some(cx.spawn_in(window, async move |editor, cx| {
17203 let project_transaction = rename.await?;
17204 Self::open_project_transaction(
17205 &editor,
17206 workspace,
17207 project_transaction,
17208 format!("Rename: {} → {}", old_name, new_name),
17209 cx,
17210 )
17211 .await?;
17212
17213 editor.update(cx, |editor, cx| {
17214 editor.refresh_document_highlights(cx);
17215 })?;
17216 Ok(())
17217 }))
17218 }
17219
17220 fn take_rename(
17221 &mut self,
17222 moving_cursor: bool,
17223 window: &mut Window,
17224 cx: &mut Context<Self>,
17225 ) -> Option<RenameState> {
17226 let rename = self.pending_rename.take()?;
17227 if rename.editor.focus_handle(cx).is_focused(window) {
17228 window.focus(&self.focus_handle);
17229 }
17230
17231 self.remove_blocks(
17232 [rename.block_id].into_iter().collect(),
17233 Some(Autoscroll::fit()),
17234 cx,
17235 );
17236 self.clear_highlights::<Rename>(cx);
17237 self.show_local_selections = true;
17238
17239 if moving_cursor {
17240 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17241 editor.selections.newest::<usize>(cx).head()
17242 });
17243
17244 // Update the selection to match the position of the selection inside
17245 // the rename editor.
17246 let snapshot = self.buffer.read(cx).read(cx);
17247 let rename_range = rename.range.to_offset(&snapshot);
17248 let cursor_in_editor = snapshot
17249 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17250 .min(rename_range.end);
17251 drop(snapshot);
17252
17253 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17254 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17255 });
17256 } else {
17257 self.refresh_document_highlights(cx);
17258 }
17259
17260 Some(rename)
17261 }
17262
17263 pub fn pending_rename(&self) -> Option<&RenameState> {
17264 self.pending_rename.as_ref()
17265 }
17266
17267 fn format(
17268 &mut self,
17269 _: &Format,
17270 window: &mut Window,
17271 cx: &mut Context<Self>,
17272 ) -> Option<Task<Result<()>>> {
17273 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17274
17275 let project = match &self.project {
17276 Some(project) => project.clone(),
17277 None => return None,
17278 };
17279
17280 Some(self.perform_format(
17281 project,
17282 FormatTrigger::Manual,
17283 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17284 window,
17285 cx,
17286 ))
17287 }
17288
17289 fn format_selections(
17290 &mut self,
17291 _: &FormatSelections,
17292 window: &mut Window,
17293 cx: &mut Context<Self>,
17294 ) -> Option<Task<Result<()>>> {
17295 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17296
17297 let project = match &self.project {
17298 Some(project) => project.clone(),
17299 None => return None,
17300 };
17301
17302 let ranges = self
17303 .selections
17304 .all_adjusted(cx)
17305 .into_iter()
17306 .map(|selection| selection.range())
17307 .collect_vec();
17308
17309 Some(self.perform_format(
17310 project,
17311 FormatTrigger::Manual,
17312 FormatTarget::Ranges(ranges),
17313 window,
17314 cx,
17315 ))
17316 }
17317
17318 fn perform_format(
17319 &mut self,
17320 project: Entity<Project>,
17321 trigger: FormatTrigger,
17322 target: FormatTarget,
17323 window: &mut Window,
17324 cx: &mut Context<Self>,
17325 ) -> Task<Result<()>> {
17326 let buffer = self.buffer.clone();
17327 let (buffers, target) = match target {
17328 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17329 FormatTarget::Ranges(selection_ranges) => {
17330 let multi_buffer = buffer.read(cx);
17331 let snapshot = multi_buffer.read(cx);
17332 let mut buffers = HashSet::default();
17333 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17334 BTreeMap::new();
17335 for selection_range in selection_ranges {
17336 for (buffer, buffer_range, _) in
17337 snapshot.range_to_buffer_ranges(selection_range)
17338 {
17339 let buffer_id = buffer.remote_id();
17340 let start = buffer.anchor_before(buffer_range.start);
17341 let end = buffer.anchor_after(buffer_range.end);
17342 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17343 buffer_id_to_ranges
17344 .entry(buffer_id)
17345 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17346 .or_insert_with(|| vec![start..end]);
17347 }
17348 }
17349 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17350 }
17351 };
17352
17353 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17354 let selections_prev = transaction_id_prev
17355 .and_then(|transaction_id_prev| {
17356 // default to selections as they were after the last edit, if we have them,
17357 // instead of how they are now.
17358 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17359 // will take you back to where you made the last edit, instead of staying where you scrolled
17360 self.selection_history
17361 .transaction(transaction_id_prev)
17362 .map(|t| t.0.clone())
17363 })
17364 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17365
17366 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17367 let format = project.update(cx, |project, cx| {
17368 project.format(buffers, target, true, trigger, cx)
17369 });
17370
17371 cx.spawn_in(window, async move |editor, cx| {
17372 let transaction = futures::select_biased! {
17373 transaction = format.log_err().fuse() => transaction,
17374 () = timeout => {
17375 log::warn!("timed out waiting for formatting");
17376 None
17377 }
17378 };
17379
17380 buffer
17381 .update(cx, |buffer, cx| {
17382 if let Some(transaction) = transaction
17383 && !buffer.is_singleton()
17384 {
17385 buffer.push_transaction(&transaction.0, cx);
17386 }
17387 cx.notify();
17388 })
17389 .ok();
17390
17391 if let Some(transaction_id_now) =
17392 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17393 {
17394 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17395 if has_new_transaction {
17396 _ = editor.update(cx, |editor, _| {
17397 editor
17398 .selection_history
17399 .insert_transaction(transaction_id_now, selections_prev);
17400 });
17401 }
17402 }
17403
17404 Ok(())
17405 })
17406 }
17407
17408 fn organize_imports(
17409 &mut self,
17410 _: &OrganizeImports,
17411 window: &mut Window,
17412 cx: &mut Context<Self>,
17413 ) -> Option<Task<Result<()>>> {
17414 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17415 let project = match &self.project {
17416 Some(project) => project.clone(),
17417 None => return None,
17418 };
17419 Some(self.perform_code_action_kind(
17420 project,
17421 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17422 window,
17423 cx,
17424 ))
17425 }
17426
17427 fn perform_code_action_kind(
17428 &mut self,
17429 project: Entity<Project>,
17430 kind: CodeActionKind,
17431 window: &mut Window,
17432 cx: &mut Context<Self>,
17433 ) -> Task<Result<()>> {
17434 let buffer = self.buffer.clone();
17435 let buffers = buffer.read(cx).all_buffers();
17436 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17437 let apply_action = project.update(cx, |project, cx| {
17438 project.apply_code_action_kind(buffers, kind, true, cx)
17439 });
17440 cx.spawn_in(window, async move |_, cx| {
17441 let transaction = futures::select_biased! {
17442 () = timeout => {
17443 log::warn!("timed out waiting for executing code action");
17444 None
17445 }
17446 transaction = apply_action.log_err().fuse() => transaction,
17447 };
17448 buffer
17449 .update(cx, |buffer, cx| {
17450 // check if we need this
17451 if let Some(transaction) = transaction
17452 && !buffer.is_singleton()
17453 {
17454 buffer.push_transaction(&transaction.0, cx);
17455 }
17456 cx.notify();
17457 })
17458 .ok();
17459 Ok(())
17460 })
17461 }
17462
17463 pub fn restart_language_server(
17464 &mut self,
17465 _: &RestartLanguageServer,
17466 _: &mut Window,
17467 cx: &mut Context<Self>,
17468 ) {
17469 if let Some(project) = self.project.clone() {
17470 self.buffer.update(cx, |multi_buffer, cx| {
17471 project.update(cx, |project, cx| {
17472 project.restart_language_servers_for_buffers(
17473 multi_buffer.all_buffers().into_iter().collect(),
17474 HashSet::default(),
17475 cx,
17476 );
17477 });
17478 })
17479 }
17480 }
17481
17482 pub fn stop_language_server(
17483 &mut self,
17484 _: &StopLanguageServer,
17485 _: &mut Window,
17486 cx: &mut Context<Self>,
17487 ) {
17488 if let Some(project) = self.project.clone() {
17489 self.buffer.update(cx, |multi_buffer, cx| {
17490 project.update(cx, |project, cx| {
17491 project.stop_language_servers_for_buffers(
17492 multi_buffer.all_buffers().into_iter().collect(),
17493 HashSet::default(),
17494 cx,
17495 );
17496 cx.emit(project::Event::RefreshInlayHints);
17497 });
17498 });
17499 }
17500 }
17501
17502 fn cancel_language_server_work(
17503 workspace: &mut Workspace,
17504 _: &actions::CancelLanguageServerWork,
17505 _: &mut Window,
17506 cx: &mut Context<Workspace>,
17507 ) {
17508 let project = workspace.project();
17509 let buffers = workspace
17510 .active_item(cx)
17511 .and_then(|item| item.act_as::<Editor>(cx))
17512 .map_or(HashSet::default(), |editor| {
17513 editor.read(cx).buffer.read(cx).all_buffers()
17514 });
17515 project.update(cx, |project, cx| {
17516 project.cancel_language_server_work_for_buffers(buffers, cx);
17517 });
17518 }
17519
17520 fn show_character_palette(
17521 &mut self,
17522 _: &ShowCharacterPalette,
17523 window: &mut Window,
17524 _: &mut Context<Self>,
17525 ) {
17526 window.show_character_palette();
17527 }
17528
17529 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17530 if !self.diagnostics_enabled() {
17531 return;
17532 }
17533
17534 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17535 let buffer = self.buffer.read(cx).snapshot(cx);
17536 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17537 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17538 let is_valid = buffer
17539 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17540 .any(|entry| {
17541 entry.diagnostic.is_primary
17542 && !entry.range.is_empty()
17543 && entry.range.start == primary_range_start
17544 && entry.diagnostic.message == active_diagnostics.active_message
17545 });
17546
17547 if !is_valid {
17548 self.dismiss_diagnostics(cx);
17549 }
17550 }
17551 }
17552
17553 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17554 match &self.active_diagnostics {
17555 ActiveDiagnostic::Group(group) => Some(group),
17556 _ => None,
17557 }
17558 }
17559
17560 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17561 if !self.diagnostics_enabled() {
17562 return;
17563 }
17564 self.dismiss_diagnostics(cx);
17565 self.active_diagnostics = ActiveDiagnostic::All;
17566 }
17567
17568 fn activate_diagnostics(
17569 &mut self,
17570 buffer_id: BufferId,
17571 diagnostic: DiagnosticEntryRef<'_, usize>,
17572 window: &mut Window,
17573 cx: &mut Context<Self>,
17574 ) {
17575 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17576 return;
17577 }
17578 self.dismiss_diagnostics(cx);
17579 let snapshot = self.snapshot(window, cx);
17580 let buffer = self.buffer.read(cx).snapshot(cx);
17581 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17582 return;
17583 };
17584
17585 let diagnostic_group = buffer
17586 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17587 .collect::<Vec<_>>();
17588
17589 let blocks =
17590 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17591
17592 let blocks = self.display_map.update(cx, |display_map, cx| {
17593 display_map.insert_blocks(blocks, cx).into_iter().collect()
17594 });
17595 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17596 active_range: buffer.anchor_before(diagnostic.range.start)
17597 ..buffer.anchor_after(diagnostic.range.end),
17598 active_message: diagnostic.diagnostic.message.clone(),
17599 group_id: diagnostic.diagnostic.group_id,
17600 blocks,
17601 });
17602 cx.notify();
17603 }
17604
17605 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17606 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17607 return;
17608 };
17609
17610 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17611 if let ActiveDiagnostic::Group(group) = prev {
17612 self.display_map.update(cx, |display_map, cx| {
17613 display_map.remove_blocks(group.blocks, cx);
17614 });
17615 cx.notify();
17616 }
17617 }
17618
17619 /// Disable inline diagnostics rendering for this editor.
17620 pub fn disable_inline_diagnostics(&mut self) {
17621 self.inline_diagnostics_enabled = false;
17622 self.inline_diagnostics_update = Task::ready(());
17623 self.inline_diagnostics.clear();
17624 }
17625
17626 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17627 self.diagnostics_enabled = false;
17628 self.dismiss_diagnostics(cx);
17629 self.inline_diagnostics_update = Task::ready(());
17630 self.inline_diagnostics.clear();
17631 }
17632
17633 pub fn disable_word_completions(&mut self) {
17634 self.word_completions_enabled = false;
17635 }
17636
17637 pub fn diagnostics_enabled(&self) -> bool {
17638 self.diagnostics_enabled && self.mode.is_full()
17639 }
17640
17641 pub fn inline_diagnostics_enabled(&self) -> bool {
17642 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17643 }
17644
17645 pub fn show_inline_diagnostics(&self) -> bool {
17646 self.show_inline_diagnostics
17647 }
17648
17649 pub fn toggle_inline_diagnostics(
17650 &mut self,
17651 _: &ToggleInlineDiagnostics,
17652 window: &mut Window,
17653 cx: &mut Context<Editor>,
17654 ) {
17655 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17656 self.refresh_inline_diagnostics(false, window, cx);
17657 }
17658
17659 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17660 self.diagnostics_max_severity = severity;
17661 self.display_map.update(cx, |display_map, _| {
17662 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17663 });
17664 }
17665
17666 pub fn toggle_diagnostics(
17667 &mut self,
17668 _: &ToggleDiagnostics,
17669 window: &mut Window,
17670 cx: &mut Context<Editor>,
17671 ) {
17672 if !self.diagnostics_enabled() {
17673 return;
17674 }
17675
17676 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17677 EditorSettings::get_global(cx)
17678 .diagnostics_max_severity
17679 .filter(|severity| severity != &DiagnosticSeverity::Off)
17680 .unwrap_or(DiagnosticSeverity::Hint)
17681 } else {
17682 DiagnosticSeverity::Off
17683 };
17684 self.set_max_diagnostics_severity(new_severity, cx);
17685 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17686 self.active_diagnostics = ActiveDiagnostic::None;
17687 self.inline_diagnostics_update = Task::ready(());
17688 self.inline_diagnostics.clear();
17689 } else {
17690 self.refresh_inline_diagnostics(false, window, cx);
17691 }
17692
17693 cx.notify();
17694 }
17695
17696 pub fn toggle_minimap(
17697 &mut self,
17698 _: &ToggleMinimap,
17699 window: &mut Window,
17700 cx: &mut Context<Editor>,
17701 ) {
17702 if self.supports_minimap(cx) {
17703 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17704 }
17705 }
17706
17707 fn refresh_inline_diagnostics(
17708 &mut self,
17709 debounce: bool,
17710 window: &mut Window,
17711 cx: &mut Context<Self>,
17712 ) {
17713 let max_severity = ProjectSettings::get_global(cx)
17714 .diagnostics
17715 .inline
17716 .max_severity
17717 .unwrap_or(self.diagnostics_max_severity);
17718
17719 if !self.inline_diagnostics_enabled()
17720 || !self.show_inline_diagnostics
17721 || max_severity == DiagnosticSeverity::Off
17722 {
17723 self.inline_diagnostics_update = Task::ready(());
17724 self.inline_diagnostics.clear();
17725 return;
17726 }
17727
17728 let debounce_ms = ProjectSettings::get_global(cx)
17729 .diagnostics
17730 .inline
17731 .update_debounce_ms;
17732 let debounce = if debounce && debounce_ms > 0 {
17733 Some(Duration::from_millis(debounce_ms))
17734 } else {
17735 None
17736 };
17737 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17738 if let Some(debounce) = debounce {
17739 cx.background_executor().timer(debounce).await;
17740 }
17741 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17742 editor
17743 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17744 .ok()
17745 }) else {
17746 return;
17747 };
17748
17749 let new_inline_diagnostics = cx
17750 .background_spawn(async move {
17751 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17752 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17753 let message = diagnostic_entry
17754 .diagnostic
17755 .message
17756 .split_once('\n')
17757 .map(|(line, _)| line)
17758 .map(SharedString::new)
17759 .unwrap_or_else(|| {
17760 SharedString::new(&*diagnostic_entry.diagnostic.message)
17761 });
17762 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17763 let (Ok(i) | Err(i)) = inline_diagnostics
17764 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17765 inline_diagnostics.insert(
17766 i,
17767 (
17768 start_anchor,
17769 InlineDiagnostic {
17770 message,
17771 group_id: diagnostic_entry.diagnostic.group_id,
17772 start: diagnostic_entry.range.start.to_point(&snapshot),
17773 is_primary: diagnostic_entry.diagnostic.is_primary,
17774 severity: diagnostic_entry.diagnostic.severity,
17775 },
17776 ),
17777 );
17778 }
17779 inline_diagnostics
17780 })
17781 .await;
17782
17783 editor
17784 .update(cx, |editor, cx| {
17785 editor.inline_diagnostics = new_inline_diagnostics;
17786 cx.notify();
17787 })
17788 .ok();
17789 });
17790 }
17791
17792 fn pull_diagnostics(
17793 &mut self,
17794 buffer_id: Option<BufferId>,
17795 window: &Window,
17796 cx: &mut Context<Self>,
17797 ) -> Option<()> {
17798 if !self.mode().is_full() {
17799 return None;
17800 }
17801 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17802 .diagnostics
17803 .lsp_pull_diagnostics;
17804 if !pull_diagnostics_settings.enabled {
17805 return None;
17806 }
17807 let project = self.project()?.downgrade();
17808 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17809 let mut buffers = self.buffer.read(cx).all_buffers();
17810 if let Some(buffer_id) = buffer_id {
17811 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17812 }
17813
17814 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17815 cx.background_executor().timer(debounce).await;
17816
17817 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17818 buffers
17819 .into_iter()
17820 .filter_map(|buffer| {
17821 project
17822 .update(cx, |project, cx| {
17823 project.lsp_store().update(cx, |lsp_store, cx| {
17824 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17825 })
17826 })
17827 .ok()
17828 })
17829 .collect::<FuturesUnordered<_>>()
17830 }) else {
17831 return;
17832 };
17833
17834 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17835 match pull_task {
17836 Ok(()) => {
17837 if editor
17838 .update_in(cx, |editor, window, cx| {
17839 editor.update_diagnostics_state(window, cx);
17840 })
17841 .is_err()
17842 {
17843 return;
17844 }
17845 }
17846 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17847 }
17848 }
17849 });
17850
17851 Some(())
17852 }
17853
17854 pub fn set_selections_from_remote(
17855 &mut self,
17856 selections: Vec<Selection<Anchor>>,
17857 pending_selection: Option<Selection<Anchor>>,
17858 window: &mut Window,
17859 cx: &mut Context<Self>,
17860 ) {
17861 let old_cursor_position = self.selections.newest_anchor().head();
17862 self.selections.change_with(cx, |s| {
17863 s.select_anchors(selections);
17864 if let Some(pending_selection) = pending_selection {
17865 s.set_pending(pending_selection, SelectMode::Character);
17866 } else {
17867 s.clear_pending();
17868 }
17869 });
17870 self.selections_did_change(
17871 false,
17872 &old_cursor_position,
17873 SelectionEffects::default(),
17874 window,
17875 cx,
17876 );
17877 }
17878
17879 pub fn transact(
17880 &mut self,
17881 window: &mut Window,
17882 cx: &mut Context<Self>,
17883 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17884 ) -> Option<TransactionId> {
17885 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17886 this.start_transaction_at(Instant::now(), window, cx);
17887 update(this, window, cx);
17888 this.end_transaction_at(Instant::now(), cx)
17889 })
17890 }
17891
17892 pub fn start_transaction_at(
17893 &mut self,
17894 now: Instant,
17895 window: &mut Window,
17896 cx: &mut Context<Self>,
17897 ) -> Option<TransactionId> {
17898 self.end_selection(window, cx);
17899 if let Some(tx_id) = self
17900 .buffer
17901 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17902 {
17903 self.selection_history
17904 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17905 cx.emit(EditorEvent::TransactionBegun {
17906 transaction_id: tx_id,
17907 });
17908 Some(tx_id)
17909 } else {
17910 None
17911 }
17912 }
17913
17914 pub fn end_transaction_at(
17915 &mut self,
17916 now: Instant,
17917 cx: &mut Context<Self>,
17918 ) -> Option<TransactionId> {
17919 if let Some(transaction_id) = self
17920 .buffer
17921 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17922 {
17923 if let Some((_, end_selections)) =
17924 self.selection_history.transaction_mut(transaction_id)
17925 {
17926 *end_selections = Some(self.selections.disjoint_anchors_arc());
17927 } else {
17928 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17929 }
17930
17931 cx.emit(EditorEvent::Edited { transaction_id });
17932 Some(transaction_id)
17933 } else {
17934 None
17935 }
17936 }
17937
17938 pub fn modify_transaction_selection_history(
17939 &mut self,
17940 transaction_id: TransactionId,
17941 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17942 ) -> bool {
17943 self.selection_history
17944 .transaction_mut(transaction_id)
17945 .map(modify)
17946 .is_some()
17947 }
17948
17949 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17950 if self.selection_mark_mode {
17951 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17952 s.move_with(|_, sel| {
17953 sel.collapse_to(sel.head(), SelectionGoal::None);
17954 });
17955 })
17956 }
17957 self.selection_mark_mode = true;
17958 cx.notify();
17959 }
17960
17961 pub fn swap_selection_ends(
17962 &mut self,
17963 _: &actions::SwapSelectionEnds,
17964 window: &mut Window,
17965 cx: &mut Context<Self>,
17966 ) {
17967 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17968 s.move_with(|_, sel| {
17969 if sel.start != sel.end {
17970 sel.reversed = !sel.reversed
17971 }
17972 });
17973 });
17974 self.request_autoscroll(Autoscroll::newest(), cx);
17975 cx.notify();
17976 }
17977
17978 pub fn toggle_focus(
17979 workspace: &mut Workspace,
17980 _: &actions::ToggleFocus,
17981 window: &mut Window,
17982 cx: &mut Context<Workspace>,
17983 ) {
17984 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17985 return;
17986 };
17987 workspace.activate_item(&item, true, true, window, cx);
17988 }
17989
17990 pub fn toggle_fold(
17991 &mut self,
17992 _: &actions::ToggleFold,
17993 window: &mut Window,
17994 cx: &mut Context<Self>,
17995 ) {
17996 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
17997 let selection = self.selections.newest::<Point>(cx);
17998
17999 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18000 let range = if selection.is_empty() {
18001 let point = selection.head().to_display_point(&display_map);
18002 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18003 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18004 .to_point(&display_map);
18005 start..end
18006 } else {
18007 selection.range()
18008 };
18009 if display_map.folds_in_range(range).next().is_some() {
18010 self.unfold_lines(&Default::default(), window, cx)
18011 } else {
18012 self.fold(&Default::default(), window, cx)
18013 }
18014 } else {
18015 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18016 let buffer_ids: HashSet<_> = self
18017 .selections
18018 .disjoint_anchor_ranges()
18019 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18020 .collect();
18021
18022 let should_unfold = buffer_ids
18023 .iter()
18024 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18025
18026 for buffer_id in buffer_ids {
18027 if should_unfold {
18028 self.unfold_buffer(buffer_id, cx);
18029 } else {
18030 self.fold_buffer(buffer_id, cx);
18031 }
18032 }
18033 }
18034 }
18035
18036 pub fn toggle_fold_recursive(
18037 &mut self,
18038 _: &actions::ToggleFoldRecursive,
18039 window: &mut Window,
18040 cx: &mut Context<Self>,
18041 ) {
18042 let selection = self.selections.newest::<Point>(cx);
18043
18044 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18045 let range = if selection.is_empty() {
18046 let point = selection.head().to_display_point(&display_map);
18047 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18048 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18049 .to_point(&display_map);
18050 start..end
18051 } else {
18052 selection.range()
18053 };
18054 if display_map.folds_in_range(range).next().is_some() {
18055 self.unfold_recursive(&Default::default(), window, cx)
18056 } else {
18057 self.fold_recursive(&Default::default(), window, cx)
18058 }
18059 }
18060
18061 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18062 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18063 let mut to_fold = Vec::new();
18064 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18065 let selections = self.selections.all_adjusted(cx);
18066
18067 for selection in selections {
18068 let range = selection.range().sorted();
18069 let buffer_start_row = range.start.row;
18070
18071 if range.start.row != range.end.row {
18072 let mut found = false;
18073 let mut row = range.start.row;
18074 while row <= range.end.row {
18075 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18076 {
18077 found = true;
18078 row = crease.range().end.row + 1;
18079 to_fold.push(crease);
18080 } else {
18081 row += 1
18082 }
18083 }
18084 if found {
18085 continue;
18086 }
18087 }
18088
18089 for row in (0..=range.start.row).rev() {
18090 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18091 && crease.range().end.row >= buffer_start_row
18092 {
18093 to_fold.push(crease);
18094 if row <= range.start.row {
18095 break;
18096 }
18097 }
18098 }
18099 }
18100
18101 self.fold_creases(to_fold, true, window, cx);
18102 } else {
18103 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18104 let buffer_ids = self
18105 .selections
18106 .disjoint_anchor_ranges()
18107 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18108 .collect::<HashSet<_>>();
18109 for buffer_id in buffer_ids {
18110 self.fold_buffer(buffer_id, cx);
18111 }
18112 }
18113 }
18114
18115 pub fn toggle_fold_all(
18116 &mut self,
18117 _: &actions::ToggleFoldAll,
18118 window: &mut Window,
18119 cx: &mut Context<Self>,
18120 ) {
18121 if self.buffer.read(cx).is_singleton() {
18122 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18123 let has_folds = display_map
18124 .folds_in_range(0..display_map.buffer_snapshot().len())
18125 .next()
18126 .is_some();
18127
18128 if has_folds {
18129 self.unfold_all(&actions::UnfoldAll, window, cx);
18130 } else {
18131 self.fold_all(&actions::FoldAll, window, cx);
18132 }
18133 } else {
18134 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18135 let should_unfold = buffer_ids
18136 .iter()
18137 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18138
18139 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18140 editor
18141 .update_in(cx, |editor, _, cx| {
18142 for buffer_id in buffer_ids {
18143 if should_unfold {
18144 editor.unfold_buffer(buffer_id, cx);
18145 } else {
18146 editor.fold_buffer(buffer_id, cx);
18147 }
18148 }
18149 })
18150 .ok();
18151 });
18152 }
18153 }
18154
18155 fn fold_at_level(
18156 &mut self,
18157 fold_at: &FoldAtLevel,
18158 window: &mut Window,
18159 cx: &mut Context<Self>,
18160 ) {
18161 if !self.buffer.read(cx).is_singleton() {
18162 return;
18163 }
18164
18165 let fold_at_level = fold_at.0;
18166 let snapshot = self.buffer.read(cx).snapshot(cx);
18167 let mut to_fold = Vec::new();
18168 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18169
18170 let row_ranges_to_keep: Vec<Range<u32>> = self
18171 .selections
18172 .all::<Point>(cx)
18173 .into_iter()
18174 .map(|sel| sel.start.row..sel.end.row)
18175 .collect();
18176
18177 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18178 while start_row < end_row {
18179 match self
18180 .snapshot(window, cx)
18181 .crease_for_buffer_row(MultiBufferRow(start_row))
18182 {
18183 Some(crease) => {
18184 let nested_start_row = crease.range().start.row + 1;
18185 let nested_end_row = crease.range().end.row;
18186
18187 if current_level < fold_at_level {
18188 stack.push((nested_start_row, nested_end_row, current_level + 1));
18189 } else if current_level == fold_at_level {
18190 // Fold iff there is no selection completely contained within the fold region
18191 if !row_ranges_to_keep.iter().any(|selection| {
18192 selection.end >= nested_start_row
18193 && selection.start <= nested_end_row
18194 }) {
18195 to_fold.push(crease);
18196 }
18197 }
18198
18199 start_row = nested_end_row + 1;
18200 }
18201 None => start_row += 1,
18202 }
18203 }
18204 }
18205
18206 self.fold_creases(to_fold, true, window, cx);
18207 }
18208
18209 pub fn fold_at_level_1(
18210 &mut self,
18211 _: &actions::FoldAtLevel1,
18212 window: &mut Window,
18213 cx: &mut Context<Self>,
18214 ) {
18215 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18216 }
18217
18218 pub fn fold_at_level_2(
18219 &mut self,
18220 _: &actions::FoldAtLevel2,
18221 window: &mut Window,
18222 cx: &mut Context<Self>,
18223 ) {
18224 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18225 }
18226
18227 pub fn fold_at_level_3(
18228 &mut self,
18229 _: &actions::FoldAtLevel3,
18230 window: &mut Window,
18231 cx: &mut Context<Self>,
18232 ) {
18233 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18234 }
18235
18236 pub fn fold_at_level_4(
18237 &mut self,
18238 _: &actions::FoldAtLevel4,
18239 window: &mut Window,
18240 cx: &mut Context<Self>,
18241 ) {
18242 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18243 }
18244
18245 pub fn fold_at_level_5(
18246 &mut self,
18247 _: &actions::FoldAtLevel5,
18248 window: &mut Window,
18249 cx: &mut Context<Self>,
18250 ) {
18251 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18252 }
18253
18254 pub fn fold_at_level_6(
18255 &mut self,
18256 _: &actions::FoldAtLevel6,
18257 window: &mut Window,
18258 cx: &mut Context<Self>,
18259 ) {
18260 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18261 }
18262
18263 pub fn fold_at_level_7(
18264 &mut self,
18265 _: &actions::FoldAtLevel7,
18266 window: &mut Window,
18267 cx: &mut Context<Self>,
18268 ) {
18269 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18270 }
18271
18272 pub fn fold_at_level_8(
18273 &mut self,
18274 _: &actions::FoldAtLevel8,
18275 window: &mut Window,
18276 cx: &mut Context<Self>,
18277 ) {
18278 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18279 }
18280
18281 pub fn fold_at_level_9(
18282 &mut self,
18283 _: &actions::FoldAtLevel9,
18284 window: &mut Window,
18285 cx: &mut Context<Self>,
18286 ) {
18287 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18288 }
18289
18290 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18291 if self.buffer.read(cx).is_singleton() {
18292 let mut fold_ranges = Vec::new();
18293 let snapshot = self.buffer.read(cx).snapshot(cx);
18294
18295 for row in 0..snapshot.max_row().0 {
18296 if let Some(foldable_range) = self
18297 .snapshot(window, cx)
18298 .crease_for_buffer_row(MultiBufferRow(row))
18299 {
18300 fold_ranges.push(foldable_range);
18301 }
18302 }
18303
18304 self.fold_creases(fold_ranges, true, window, cx);
18305 } else {
18306 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18307 editor
18308 .update_in(cx, |editor, _, cx| {
18309 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18310 editor.fold_buffer(buffer_id, cx);
18311 }
18312 })
18313 .ok();
18314 });
18315 }
18316 }
18317
18318 pub fn fold_function_bodies(
18319 &mut self,
18320 _: &actions::FoldFunctionBodies,
18321 window: &mut Window,
18322 cx: &mut Context<Self>,
18323 ) {
18324 let snapshot = self.buffer.read(cx).snapshot(cx);
18325
18326 let ranges = snapshot
18327 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18328 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18329 .collect::<Vec<_>>();
18330
18331 let creases = ranges
18332 .into_iter()
18333 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18334 .collect();
18335
18336 self.fold_creases(creases, true, window, cx);
18337 }
18338
18339 pub fn fold_recursive(
18340 &mut self,
18341 _: &actions::FoldRecursive,
18342 window: &mut Window,
18343 cx: &mut Context<Self>,
18344 ) {
18345 let mut to_fold = Vec::new();
18346 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18347 let selections = self.selections.all_adjusted(cx);
18348
18349 for selection in selections {
18350 let range = selection.range().sorted();
18351 let buffer_start_row = range.start.row;
18352
18353 if range.start.row != range.end.row {
18354 let mut found = false;
18355 for row in range.start.row..=range.end.row {
18356 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18357 found = true;
18358 to_fold.push(crease);
18359 }
18360 }
18361 if found {
18362 continue;
18363 }
18364 }
18365
18366 for row in (0..=range.start.row).rev() {
18367 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18368 if crease.range().end.row >= buffer_start_row {
18369 to_fold.push(crease);
18370 } else {
18371 break;
18372 }
18373 }
18374 }
18375 }
18376
18377 self.fold_creases(to_fold, true, window, cx);
18378 }
18379
18380 pub fn fold_at(
18381 &mut self,
18382 buffer_row: MultiBufferRow,
18383 window: &mut Window,
18384 cx: &mut Context<Self>,
18385 ) {
18386 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18387
18388 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18389 let autoscroll = self
18390 .selections
18391 .all::<Point>(cx)
18392 .iter()
18393 .any(|selection| crease.range().overlaps(&selection.range()));
18394
18395 self.fold_creases(vec![crease], autoscroll, window, cx);
18396 }
18397 }
18398
18399 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18400 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18401 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18402 let buffer = display_map.buffer_snapshot();
18403 let selections = self.selections.all::<Point>(cx);
18404 let ranges = selections
18405 .iter()
18406 .map(|s| {
18407 let range = s.display_range(&display_map).sorted();
18408 let mut start = range.start.to_point(&display_map);
18409 let mut end = range.end.to_point(&display_map);
18410 start.column = 0;
18411 end.column = buffer.line_len(MultiBufferRow(end.row));
18412 start..end
18413 })
18414 .collect::<Vec<_>>();
18415
18416 self.unfold_ranges(&ranges, true, true, cx);
18417 } else {
18418 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18419 let buffer_ids = self
18420 .selections
18421 .disjoint_anchor_ranges()
18422 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18423 .collect::<HashSet<_>>();
18424 for buffer_id in buffer_ids {
18425 self.unfold_buffer(buffer_id, cx);
18426 }
18427 }
18428 }
18429
18430 pub fn unfold_recursive(
18431 &mut self,
18432 _: &UnfoldRecursive,
18433 _window: &mut Window,
18434 cx: &mut Context<Self>,
18435 ) {
18436 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18437 let selections = self.selections.all::<Point>(cx);
18438 let ranges = selections
18439 .iter()
18440 .map(|s| {
18441 let mut range = s.display_range(&display_map).sorted();
18442 *range.start.column_mut() = 0;
18443 *range.end.column_mut() = display_map.line_len(range.end.row());
18444 let start = range.start.to_point(&display_map);
18445 let end = range.end.to_point(&display_map);
18446 start..end
18447 })
18448 .collect::<Vec<_>>();
18449
18450 self.unfold_ranges(&ranges, true, true, cx);
18451 }
18452
18453 pub fn unfold_at(
18454 &mut self,
18455 buffer_row: MultiBufferRow,
18456 _window: &mut Window,
18457 cx: &mut Context<Self>,
18458 ) {
18459 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18460
18461 let intersection_range = Point::new(buffer_row.0, 0)
18462 ..Point::new(
18463 buffer_row.0,
18464 display_map.buffer_snapshot().line_len(buffer_row),
18465 );
18466
18467 let autoscroll = self
18468 .selections
18469 .all::<Point>(cx)
18470 .iter()
18471 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18472
18473 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18474 }
18475
18476 pub fn unfold_all(
18477 &mut self,
18478 _: &actions::UnfoldAll,
18479 _window: &mut Window,
18480 cx: &mut Context<Self>,
18481 ) {
18482 if self.buffer.read(cx).is_singleton() {
18483 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18484 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18485 } else {
18486 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18487 editor
18488 .update(cx, |editor, cx| {
18489 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18490 editor.unfold_buffer(buffer_id, cx);
18491 }
18492 })
18493 .ok();
18494 });
18495 }
18496 }
18497
18498 pub fn fold_selected_ranges(
18499 &mut self,
18500 _: &FoldSelectedRanges,
18501 window: &mut Window,
18502 cx: &mut Context<Self>,
18503 ) {
18504 let selections = self.selections.all_adjusted(cx);
18505 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18506 let ranges = selections
18507 .into_iter()
18508 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18509 .collect::<Vec<_>>();
18510 self.fold_creases(ranges, true, window, cx);
18511 }
18512
18513 pub fn fold_ranges<T: ToOffset + Clone>(
18514 &mut self,
18515 ranges: Vec<Range<T>>,
18516 auto_scroll: bool,
18517 window: &mut Window,
18518 cx: &mut Context<Self>,
18519 ) {
18520 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18521 let ranges = ranges
18522 .into_iter()
18523 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18524 .collect::<Vec<_>>();
18525 self.fold_creases(ranges, auto_scroll, window, cx);
18526 }
18527
18528 pub fn fold_creases<T: ToOffset + Clone>(
18529 &mut self,
18530 creases: Vec<Crease<T>>,
18531 auto_scroll: bool,
18532 _window: &mut Window,
18533 cx: &mut Context<Self>,
18534 ) {
18535 if creases.is_empty() {
18536 return;
18537 }
18538
18539 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18540
18541 if auto_scroll {
18542 self.request_autoscroll(Autoscroll::fit(), cx);
18543 }
18544
18545 cx.notify();
18546
18547 self.scrollbar_marker_state.dirty = true;
18548 self.folds_did_change(cx);
18549 }
18550
18551 /// Removes any folds whose ranges intersect any of the given ranges.
18552 pub fn unfold_ranges<T: ToOffset + Clone>(
18553 &mut self,
18554 ranges: &[Range<T>],
18555 inclusive: bool,
18556 auto_scroll: bool,
18557 cx: &mut Context<Self>,
18558 ) {
18559 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18560 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18561 });
18562 self.folds_did_change(cx);
18563 }
18564
18565 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18566 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18567 return;
18568 }
18569 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18570 self.display_map.update(cx, |display_map, cx| {
18571 display_map.fold_buffers([buffer_id], cx)
18572 });
18573 cx.emit(EditorEvent::BufferFoldToggled {
18574 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18575 folded: true,
18576 });
18577 cx.notify();
18578 }
18579
18580 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18581 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18582 return;
18583 }
18584 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18585 self.display_map.update(cx, |display_map, cx| {
18586 display_map.unfold_buffers([buffer_id], cx);
18587 });
18588 cx.emit(EditorEvent::BufferFoldToggled {
18589 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18590 folded: false,
18591 });
18592 cx.notify();
18593 }
18594
18595 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18596 self.display_map.read(cx).is_buffer_folded(buffer)
18597 }
18598
18599 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18600 self.display_map.read(cx).folded_buffers()
18601 }
18602
18603 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18604 self.display_map.update(cx, |display_map, cx| {
18605 display_map.disable_header_for_buffer(buffer_id, cx);
18606 });
18607 cx.notify();
18608 }
18609
18610 /// Removes any folds with the given ranges.
18611 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18612 &mut self,
18613 ranges: &[Range<T>],
18614 type_id: TypeId,
18615 auto_scroll: bool,
18616 cx: &mut Context<Self>,
18617 ) {
18618 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18619 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18620 });
18621 self.folds_did_change(cx);
18622 }
18623
18624 fn remove_folds_with<T: ToOffset + Clone>(
18625 &mut self,
18626 ranges: &[Range<T>],
18627 auto_scroll: bool,
18628 cx: &mut Context<Self>,
18629 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18630 ) {
18631 if ranges.is_empty() {
18632 return;
18633 }
18634
18635 let mut buffers_affected = HashSet::default();
18636 let multi_buffer = self.buffer().read(cx);
18637 for range in ranges {
18638 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18639 buffers_affected.insert(buffer.read(cx).remote_id());
18640 };
18641 }
18642
18643 self.display_map.update(cx, update);
18644
18645 if auto_scroll {
18646 self.request_autoscroll(Autoscroll::fit(), cx);
18647 }
18648
18649 cx.notify();
18650 self.scrollbar_marker_state.dirty = true;
18651 self.active_indent_guides_state.dirty = true;
18652 }
18653
18654 pub fn update_renderer_widths(
18655 &mut self,
18656 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18657 cx: &mut Context<Self>,
18658 ) -> bool {
18659 self.display_map
18660 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18661 }
18662
18663 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18664 self.display_map.read(cx).fold_placeholder.clone()
18665 }
18666
18667 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18668 self.buffer.update(cx, |buffer, cx| {
18669 buffer.set_all_diff_hunks_expanded(cx);
18670 });
18671 }
18672
18673 pub fn expand_all_diff_hunks(
18674 &mut self,
18675 _: &ExpandAllDiffHunks,
18676 _window: &mut Window,
18677 cx: &mut Context<Self>,
18678 ) {
18679 self.buffer.update(cx, |buffer, cx| {
18680 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18681 });
18682 }
18683
18684 pub fn toggle_selected_diff_hunks(
18685 &mut self,
18686 _: &ToggleSelectedDiffHunks,
18687 _window: &mut Window,
18688 cx: &mut Context<Self>,
18689 ) {
18690 let ranges: Vec<_> = self
18691 .selections
18692 .disjoint_anchors()
18693 .iter()
18694 .map(|s| s.range())
18695 .collect();
18696 self.toggle_diff_hunks_in_ranges(ranges, cx);
18697 }
18698
18699 pub fn diff_hunks_in_ranges<'a>(
18700 &'a self,
18701 ranges: &'a [Range<Anchor>],
18702 buffer: &'a MultiBufferSnapshot,
18703 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18704 ranges.iter().flat_map(move |range| {
18705 let end_excerpt_id = range.end.excerpt_id;
18706 let range = range.to_point(buffer);
18707 let mut peek_end = range.end;
18708 if range.end.row < buffer.max_row().0 {
18709 peek_end = Point::new(range.end.row + 1, 0);
18710 }
18711 buffer
18712 .diff_hunks_in_range(range.start..peek_end)
18713 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18714 })
18715 }
18716
18717 pub fn has_stageable_diff_hunks_in_ranges(
18718 &self,
18719 ranges: &[Range<Anchor>],
18720 snapshot: &MultiBufferSnapshot,
18721 ) -> bool {
18722 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18723 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18724 }
18725
18726 pub fn toggle_staged_selected_diff_hunks(
18727 &mut self,
18728 _: &::git::ToggleStaged,
18729 _: &mut Window,
18730 cx: &mut Context<Self>,
18731 ) {
18732 let snapshot = self.buffer.read(cx).snapshot(cx);
18733 let ranges: Vec<_> = self
18734 .selections
18735 .disjoint_anchors()
18736 .iter()
18737 .map(|s| s.range())
18738 .collect();
18739 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18740 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18741 }
18742
18743 pub fn set_render_diff_hunk_controls(
18744 &mut self,
18745 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18746 cx: &mut Context<Self>,
18747 ) {
18748 self.render_diff_hunk_controls = render_diff_hunk_controls;
18749 cx.notify();
18750 }
18751
18752 pub fn stage_and_next(
18753 &mut self,
18754 _: &::git::StageAndNext,
18755 window: &mut Window,
18756 cx: &mut Context<Self>,
18757 ) {
18758 self.do_stage_or_unstage_and_next(true, window, cx);
18759 }
18760
18761 pub fn unstage_and_next(
18762 &mut self,
18763 _: &::git::UnstageAndNext,
18764 window: &mut Window,
18765 cx: &mut Context<Self>,
18766 ) {
18767 self.do_stage_or_unstage_and_next(false, window, cx);
18768 }
18769
18770 pub fn stage_or_unstage_diff_hunks(
18771 &mut self,
18772 stage: bool,
18773 ranges: Vec<Range<Anchor>>,
18774 cx: &mut Context<Self>,
18775 ) {
18776 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18777 cx.spawn(async move |this, cx| {
18778 task.await?;
18779 this.update(cx, |this, cx| {
18780 let snapshot = this.buffer.read(cx).snapshot(cx);
18781 let chunk_by = this
18782 .diff_hunks_in_ranges(&ranges, &snapshot)
18783 .chunk_by(|hunk| hunk.buffer_id);
18784 for (buffer_id, hunks) in &chunk_by {
18785 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18786 }
18787 })
18788 })
18789 .detach_and_log_err(cx);
18790 }
18791
18792 fn save_buffers_for_ranges_if_needed(
18793 &mut self,
18794 ranges: &[Range<Anchor>],
18795 cx: &mut Context<Editor>,
18796 ) -> Task<Result<()>> {
18797 let multibuffer = self.buffer.read(cx);
18798 let snapshot = multibuffer.read(cx);
18799 let buffer_ids: HashSet<_> = ranges
18800 .iter()
18801 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18802 .collect();
18803 drop(snapshot);
18804
18805 let mut buffers = HashSet::default();
18806 for buffer_id in buffer_ids {
18807 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18808 let buffer = buffer_entity.read(cx);
18809 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18810 {
18811 buffers.insert(buffer_entity);
18812 }
18813 }
18814 }
18815
18816 if let Some(project) = &self.project {
18817 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18818 } else {
18819 Task::ready(Ok(()))
18820 }
18821 }
18822
18823 fn do_stage_or_unstage_and_next(
18824 &mut self,
18825 stage: bool,
18826 window: &mut Window,
18827 cx: &mut Context<Self>,
18828 ) {
18829 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18830
18831 if ranges.iter().any(|range| range.start != range.end) {
18832 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18833 return;
18834 }
18835
18836 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18837 let snapshot = self.snapshot(window, cx);
18838 let position = self.selections.newest::<Point>(cx).head();
18839 let mut row = snapshot
18840 .buffer_snapshot()
18841 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18842 .find(|hunk| hunk.row_range.start.0 > position.row)
18843 .map(|hunk| hunk.row_range.start);
18844
18845 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18846 // Outside of the project diff editor, wrap around to the beginning.
18847 if !all_diff_hunks_expanded {
18848 row = row.or_else(|| {
18849 snapshot
18850 .buffer_snapshot()
18851 .diff_hunks_in_range(Point::zero()..position)
18852 .find(|hunk| hunk.row_range.end.0 < position.row)
18853 .map(|hunk| hunk.row_range.start)
18854 });
18855 }
18856
18857 if let Some(row) = row {
18858 let destination = Point::new(row.0, 0);
18859 let autoscroll = Autoscroll::center();
18860
18861 self.unfold_ranges(&[destination..destination], false, false, cx);
18862 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18863 s.select_ranges([destination..destination]);
18864 });
18865 }
18866 }
18867
18868 fn do_stage_or_unstage(
18869 &self,
18870 stage: bool,
18871 buffer_id: BufferId,
18872 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18873 cx: &mut App,
18874 ) -> Option<()> {
18875 let project = self.project()?;
18876 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18877 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18878 let buffer_snapshot = buffer.read(cx).snapshot();
18879 let file_exists = buffer_snapshot
18880 .file()
18881 .is_some_and(|file| file.disk_state().exists());
18882 diff.update(cx, |diff, cx| {
18883 diff.stage_or_unstage_hunks(
18884 stage,
18885 &hunks
18886 .map(|hunk| buffer_diff::DiffHunk {
18887 buffer_range: hunk.buffer_range,
18888 diff_base_byte_range: hunk.diff_base_byte_range,
18889 secondary_status: hunk.secondary_status,
18890 range: Point::zero()..Point::zero(), // unused
18891 })
18892 .collect::<Vec<_>>(),
18893 &buffer_snapshot,
18894 file_exists,
18895 cx,
18896 )
18897 });
18898 None
18899 }
18900
18901 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18902 let ranges: Vec<_> = self
18903 .selections
18904 .disjoint_anchors()
18905 .iter()
18906 .map(|s| s.range())
18907 .collect();
18908 self.buffer
18909 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18910 }
18911
18912 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18913 self.buffer.update(cx, |buffer, cx| {
18914 let ranges = vec![Anchor::min()..Anchor::max()];
18915 if !buffer.all_diff_hunks_expanded()
18916 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18917 {
18918 buffer.collapse_diff_hunks(ranges, cx);
18919 true
18920 } else {
18921 false
18922 }
18923 })
18924 }
18925
18926 fn toggle_diff_hunks_in_ranges(
18927 &mut self,
18928 ranges: Vec<Range<Anchor>>,
18929 cx: &mut Context<Editor>,
18930 ) {
18931 self.buffer.update(cx, |buffer, cx| {
18932 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18933 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18934 })
18935 }
18936
18937 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18938 self.buffer.update(cx, |buffer, cx| {
18939 let snapshot = buffer.snapshot(cx);
18940 let excerpt_id = range.end.excerpt_id;
18941 let point_range = range.to_point(&snapshot);
18942 let expand = !buffer.single_hunk_is_expanded(range, cx);
18943 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18944 })
18945 }
18946
18947 pub(crate) fn apply_all_diff_hunks(
18948 &mut self,
18949 _: &ApplyAllDiffHunks,
18950 window: &mut Window,
18951 cx: &mut Context<Self>,
18952 ) {
18953 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18954
18955 let buffers = self.buffer.read(cx).all_buffers();
18956 for branch_buffer in buffers {
18957 branch_buffer.update(cx, |branch_buffer, cx| {
18958 branch_buffer.merge_into_base(Vec::new(), cx);
18959 });
18960 }
18961
18962 if let Some(project) = self.project.clone() {
18963 self.save(
18964 SaveOptions {
18965 format: true,
18966 autosave: false,
18967 },
18968 project,
18969 window,
18970 cx,
18971 )
18972 .detach_and_log_err(cx);
18973 }
18974 }
18975
18976 pub(crate) fn apply_selected_diff_hunks(
18977 &mut self,
18978 _: &ApplyDiffHunk,
18979 window: &mut Window,
18980 cx: &mut Context<Self>,
18981 ) {
18982 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18983 let snapshot = self.snapshot(window, cx);
18984 let hunks = snapshot.hunks_for_ranges(
18985 self.selections
18986 .all(cx)
18987 .into_iter()
18988 .map(|selection| selection.range()),
18989 );
18990 let mut ranges_by_buffer = HashMap::default();
18991 self.transact(window, cx, |editor, _window, cx| {
18992 for hunk in hunks {
18993 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18994 ranges_by_buffer
18995 .entry(buffer.clone())
18996 .or_insert_with(Vec::new)
18997 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18998 }
18999 }
19000
19001 for (buffer, ranges) in ranges_by_buffer {
19002 buffer.update(cx, |buffer, cx| {
19003 buffer.merge_into_base(ranges, cx);
19004 });
19005 }
19006 });
19007
19008 if let Some(project) = self.project.clone() {
19009 self.save(
19010 SaveOptions {
19011 format: true,
19012 autosave: false,
19013 },
19014 project,
19015 window,
19016 cx,
19017 )
19018 .detach_and_log_err(cx);
19019 }
19020 }
19021
19022 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19023 if hovered != self.gutter_hovered {
19024 self.gutter_hovered = hovered;
19025 cx.notify();
19026 }
19027 }
19028
19029 pub fn insert_blocks(
19030 &mut self,
19031 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19032 autoscroll: Option<Autoscroll>,
19033 cx: &mut Context<Self>,
19034 ) -> Vec<CustomBlockId> {
19035 let blocks = self
19036 .display_map
19037 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19038 if let Some(autoscroll) = autoscroll {
19039 self.request_autoscroll(autoscroll, cx);
19040 }
19041 cx.notify();
19042 blocks
19043 }
19044
19045 pub fn resize_blocks(
19046 &mut self,
19047 heights: HashMap<CustomBlockId, u32>,
19048 autoscroll: Option<Autoscroll>,
19049 cx: &mut Context<Self>,
19050 ) {
19051 self.display_map
19052 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19053 if let Some(autoscroll) = autoscroll {
19054 self.request_autoscroll(autoscroll, cx);
19055 }
19056 cx.notify();
19057 }
19058
19059 pub fn replace_blocks(
19060 &mut self,
19061 renderers: HashMap<CustomBlockId, RenderBlock>,
19062 autoscroll: Option<Autoscroll>,
19063 cx: &mut Context<Self>,
19064 ) {
19065 self.display_map
19066 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19067 if let Some(autoscroll) = autoscroll {
19068 self.request_autoscroll(autoscroll, cx);
19069 }
19070 cx.notify();
19071 }
19072
19073 pub fn remove_blocks(
19074 &mut self,
19075 block_ids: HashSet<CustomBlockId>,
19076 autoscroll: Option<Autoscroll>,
19077 cx: &mut Context<Self>,
19078 ) {
19079 self.display_map.update(cx, |display_map, cx| {
19080 display_map.remove_blocks(block_ids, cx)
19081 });
19082 if let Some(autoscroll) = autoscroll {
19083 self.request_autoscroll(autoscroll, cx);
19084 }
19085 cx.notify();
19086 }
19087
19088 pub fn row_for_block(
19089 &self,
19090 block_id: CustomBlockId,
19091 cx: &mut Context<Self>,
19092 ) -> Option<DisplayRow> {
19093 self.display_map
19094 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19095 }
19096
19097 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19098 self.focused_block = Some(focused_block);
19099 }
19100
19101 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19102 self.focused_block.take()
19103 }
19104
19105 pub fn insert_creases(
19106 &mut self,
19107 creases: impl IntoIterator<Item = Crease<Anchor>>,
19108 cx: &mut Context<Self>,
19109 ) -> Vec<CreaseId> {
19110 self.display_map
19111 .update(cx, |map, cx| map.insert_creases(creases, cx))
19112 }
19113
19114 pub fn remove_creases(
19115 &mut self,
19116 ids: impl IntoIterator<Item = CreaseId>,
19117 cx: &mut Context<Self>,
19118 ) -> Vec<(CreaseId, Range<Anchor>)> {
19119 self.display_map
19120 .update(cx, |map, cx| map.remove_creases(ids, cx))
19121 }
19122
19123 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19124 self.display_map
19125 .update(cx, |map, cx| map.snapshot(cx))
19126 .longest_row()
19127 }
19128
19129 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19130 self.display_map
19131 .update(cx, |map, cx| map.snapshot(cx))
19132 .max_point()
19133 }
19134
19135 pub fn text(&self, cx: &App) -> String {
19136 self.buffer.read(cx).read(cx).text()
19137 }
19138
19139 pub fn is_empty(&self, cx: &App) -> bool {
19140 self.buffer.read(cx).read(cx).is_empty()
19141 }
19142
19143 pub fn text_option(&self, cx: &App) -> Option<String> {
19144 let text = self.text(cx);
19145 let text = text.trim();
19146
19147 if text.is_empty() {
19148 return None;
19149 }
19150
19151 Some(text.to_string())
19152 }
19153
19154 pub fn set_text(
19155 &mut self,
19156 text: impl Into<Arc<str>>,
19157 window: &mut Window,
19158 cx: &mut Context<Self>,
19159 ) {
19160 self.transact(window, cx, |this, _, cx| {
19161 this.buffer
19162 .read(cx)
19163 .as_singleton()
19164 .expect("you can only call set_text on editors for singleton buffers")
19165 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19166 });
19167 }
19168
19169 pub fn display_text(&self, cx: &mut App) -> String {
19170 self.display_map
19171 .update(cx, |map, cx| map.snapshot(cx))
19172 .text()
19173 }
19174
19175 fn create_minimap(
19176 &self,
19177 minimap_settings: MinimapSettings,
19178 window: &mut Window,
19179 cx: &mut Context<Self>,
19180 ) -> Option<Entity<Self>> {
19181 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19182 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19183 }
19184
19185 fn initialize_new_minimap(
19186 &self,
19187 minimap_settings: MinimapSettings,
19188 window: &mut Window,
19189 cx: &mut Context<Self>,
19190 ) -> Entity<Self> {
19191 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19192
19193 let mut minimap = Editor::new_internal(
19194 EditorMode::Minimap {
19195 parent: cx.weak_entity(),
19196 },
19197 self.buffer.clone(),
19198 None,
19199 Some(self.display_map.clone()),
19200 window,
19201 cx,
19202 );
19203 minimap.scroll_manager.clone_state(&self.scroll_manager);
19204 minimap.set_text_style_refinement(TextStyleRefinement {
19205 font_size: Some(MINIMAP_FONT_SIZE),
19206 font_weight: Some(MINIMAP_FONT_WEIGHT),
19207 ..Default::default()
19208 });
19209 minimap.update_minimap_configuration(minimap_settings, cx);
19210 cx.new(|_| minimap)
19211 }
19212
19213 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19214 let current_line_highlight = minimap_settings
19215 .current_line_highlight
19216 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19217 self.set_current_line_highlight(Some(current_line_highlight));
19218 }
19219
19220 pub fn minimap(&self) -> Option<&Entity<Self>> {
19221 self.minimap
19222 .as_ref()
19223 .filter(|_| self.minimap_visibility.visible())
19224 }
19225
19226 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19227 let mut wrap_guides = smallvec![];
19228
19229 if self.show_wrap_guides == Some(false) {
19230 return wrap_guides;
19231 }
19232
19233 let settings = self.buffer.read(cx).language_settings(cx);
19234 if settings.show_wrap_guides {
19235 match self.soft_wrap_mode(cx) {
19236 SoftWrap::Column(soft_wrap) => {
19237 wrap_guides.push((soft_wrap as usize, true));
19238 }
19239 SoftWrap::Bounded(soft_wrap) => {
19240 wrap_guides.push((soft_wrap as usize, true));
19241 }
19242 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19243 }
19244 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19245 }
19246
19247 wrap_guides
19248 }
19249
19250 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19251 let settings = self.buffer.read(cx).language_settings(cx);
19252 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19253 match mode {
19254 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19255 SoftWrap::None
19256 }
19257 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19258 language_settings::SoftWrap::PreferredLineLength => {
19259 SoftWrap::Column(settings.preferred_line_length)
19260 }
19261 language_settings::SoftWrap::Bounded => {
19262 SoftWrap::Bounded(settings.preferred_line_length)
19263 }
19264 }
19265 }
19266
19267 pub fn set_soft_wrap_mode(
19268 &mut self,
19269 mode: language_settings::SoftWrap,
19270
19271 cx: &mut Context<Self>,
19272 ) {
19273 self.soft_wrap_mode_override = Some(mode);
19274 cx.notify();
19275 }
19276
19277 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19278 self.hard_wrap = hard_wrap;
19279 cx.notify();
19280 }
19281
19282 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19283 self.text_style_refinement = Some(style);
19284 }
19285
19286 /// called by the Element so we know what style we were most recently rendered with.
19287 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19288 // We intentionally do not inform the display map about the minimap style
19289 // so that wrapping is not recalculated and stays consistent for the editor
19290 // and its linked minimap.
19291 if !self.mode.is_minimap() {
19292 let font = style.text.font();
19293 let font_size = style.text.font_size.to_pixels(window.rem_size());
19294 let display_map = self
19295 .placeholder_display_map
19296 .as_ref()
19297 .filter(|_| self.is_empty(cx))
19298 .unwrap_or(&self.display_map);
19299
19300 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19301 }
19302 self.style = Some(style);
19303 }
19304
19305 pub fn style(&self) -> Option<&EditorStyle> {
19306 self.style.as_ref()
19307 }
19308
19309 // Called by the element. This method is not designed to be called outside of the editor
19310 // element's layout code because it does not notify when rewrapping is computed synchronously.
19311 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19312 if self.is_empty(cx) {
19313 self.placeholder_display_map
19314 .as_ref()
19315 .map_or(false, |display_map| {
19316 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19317 })
19318 } else {
19319 self.display_map
19320 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19321 }
19322 }
19323
19324 pub fn set_soft_wrap(&mut self) {
19325 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19326 }
19327
19328 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19329 if self.soft_wrap_mode_override.is_some() {
19330 self.soft_wrap_mode_override.take();
19331 } else {
19332 let soft_wrap = match self.soft_wrap_mode(cx) {
19333 SoftWrap::GitDiff => return,
19334 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19335 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19336 language_settings::SoftWrap::None
19337 }
19338 };
19339 self.soft_wrap_mode_override = Some(soft_wrap);
19340 }
19341 cx.notify();
19342 }
19343
19344 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19345 let Some(workspace) = self.workspace() else {
19346 return;
19347 };
19348 let fs = workspace.read(cx).app_state().fs.clone();
19349 let current_show = TabBarSettings::get_global(cx).show;
19350 update_settings_file(fs, cx, move |setting, _| {
19351 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19352 });
19353 }
19354
19355 pub fn toggle_indent_guides(
19356 &mut self,
19357 _: &ToggleIndentGuides,
19358 _: &mut Window,
19359 cx: &mut Context<Self>,
19360 ) {
19361 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19362 self.buffer
19363 .read(cx)
19364 .language_settings(cx)
19365 .indent_guides
19366 .enabled
19367 });
19368 self.show_indent_guides = Some(!currently_enabled);
19369 cx.notify();
19370 }
19371
19372 fn should_show_indent_guides(&self) -> Option<bool> {
19373 self.show_indent_guides
19374 }
19375
19376 pub fn toggle_line_numbers(
19377 &mut self,
19378 _: &ToggleLineNumbers,
19379 _: &mut Window,
19380 cx: &mut Context<Self>,
19381 ) {
19382 let mut editor_settings = EditorSettings::get_global(cx).clone();
19383 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19384 EditorSettings::override_global(editor_settings, cx);
19385 }
19386
19387 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19388 if let Some(show_line_numbers) = self.show_line_numbers {
19389 return show_line_numbers;
19390 }
19391 EditorSettings::get_global(cx).gutter.line_numbers
19392 }
19393
19394 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19395 self.use_relative_line_numbers
19396 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19397 }
19398
19399 pub fn toggle_relative_line_numbers(
19400 &mut self,
19401 _: &ToggleRelativeLineNumbers,
19402 _: &mut Window,
19403 cx: &mut Context<Self>,
19404 ) {
19405 let is_relative = self.should_use_relative_line_numbers(cx);
19406 self.set_relative_line_number(Some(!is_relative), cx)
19407 }
19408
19409 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19410 self.use_relative_line_numbers = is_relative;
19411 cx.notify();
19412 }
19413
19414 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19415 self.show_gutter = show_gutter;
19416 cx.notify();
19417 }
19418
19419 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19420 self.show_scrollbars = ScrollbarAxes {
19421 horizontal: show,
19422 vertical: show,
19423 };
19424 cx.notify();
19425 }
19426
19427 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19428 self.show_scrollbars.vertical = show;
19429 cx.notify();
19430 }
19431
19432 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19433 self.show_scrollbars.horizontal = show;
19434 cx.notify();
19435 }
19436
19437 pub fn set_minimap_visibility(
19438 &mut self,
19439 minimap_visibility: MinimapVisibility,
19440 window: &mut Window,
19441 cx: &mut Context<Self>,
19442 ) {
19443 if self.minimap_visibility != minimap_visibility {
19444 if minimap_visibility.visible() && self.minimap.is_none() {
19445 let minimap_settings = EditorSettings::get_global(cx).minimap;
19446 self.minimap =
19447 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19448 }
19449 self.minimap_visibility = minimap_visibility;
19450 cx.notify();
19451 }
19452 }
19453
19454 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19455 self.set_show_scrollbars(false, cx);
19456 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19457 }
19458
19459 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19460 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19461 }
19462
19463 /// Normally the text in full mode and auto height editors is padded on the
19464 /// left side by roughly half a character width for improved hit testing.
19465 ///
19466 /// Use this method to disable this for cases where this is not wanted (e.g.
19467 /// if you want to align the editor text with some other text above or below)
19468 /// or if you want to add this padding to single-line editors.
19469 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19470 self.offset_content = offset_content;
19471 cx.notify();
19472 }
19473
19474 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19475 self.show_line_numbers = Some(show_line_numbers);
19476 cx.notify();
19477 }
19478
19479 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19480 self.disable_expand_excerpt_buttons = true;
19481 cx.notify();
19482 }
19483
19484 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19485 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19486 cx.notify();
19487 }
19488
19489 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19490 self.show_code_actions = Some(show_code_actions);
19491 cx.notify();
19492 }
19493
19494 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19495 self.show_runnables = Some(show_runnables);
19496 cx.notify();
19497 }
19498
19499 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19500 self.show_breakpoints = Some(show_breakpoints);
19501 cx.notify();
19502 }
19503
19504 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19505 if self.display_map.read(cx).masked != masked {
19506 self.display_map.update(cx, |map, _| map.masked = masked);
19507 }
19508 cx.notify()
19509 }
19510
19511 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19512 self.show_wrap_guides = Some(show_wrap_guides);
19513 cx.notify();
19514 }
19515
19516 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19517 self.show_indent_guides = Some(show_indent_guides);
19518 cx.notify();
19519 }
19520
19521 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19522 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19523 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19524 && let Some(dir) = file.abs_path(cx).parent()
19525 {
19526 return Some(dir.to_owned());
19527 }
19528 }
19529
19530 None
19531 }
19532
19533 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19534 self.active_excerpt(cx)?
19535 .1
19536 .read(cx)
19537 .file()
19538 .and_then(|f| f.as_local())
19539 }
19540
19541 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19542 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19543 let buffer = buffer.read(cx);
19544 if let Some(project_path) = buffer.project_path(cx) {
19545 let project = self.project()?.read(cx);
19546 project.absolute_path(&project_path, cx)
19547 } else {
19548 buffer
19549 .file()
19550 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19551 }
19552 })
19553 }
19554
19555 pub fn reveal_in_finder(
19556 &mut self,
19557 _: &RevealInFileManager,
19558 _window: &mut Window,
19559 cx: &mut Context<Self>,
19560 ) {
19561 if let Some(target) = self.target_file(cx) {
19562 cx.reveal_path(&target.abs_path(cx));
19563 }
19564 }
19565
19566 pub fn copy_path(
19567 &mut self,
19568 _: &zed_actions::workspace::CopyPath,
19569 _window: &mut Window,
19570 cx: &mut Context<Self>,
19571 ) {
19572 if let Some(path) = self.target_file_abs_path(cx)
19573 && let Some(path) = path.to_str()
19574 {
19575 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19576 } else {
19577 cx.propagate();
19578 }
19579 }
19580
19581 pub fn copy_relative_path(
19582 &mut self,
19583 _: &zed_actions::workspace::CopyRelativePath,
19584 _window: &mut Window,
19585 cx: &mut Context<Self>,
19586 ) {
19587 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19588 let project = self.project()?.read(cx);
19589 let path = buffer.read(cx).file()?.path();
19590 let path = path.display(project.path_style(cx));
19591 Some(path)
19592 }) {
19593 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19594 } else {
19595 cx.propagate();
19596 }
19597 }
19598
19599 /// Returns the project path for the editor's buffer, if any buffer is
19600 /// opened in the editor.
19601 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19602 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19603 buffer.read(cx).project_path(cx)
19604 } else {
19605 None
19606 }
19607 }
19608
19609 // Returns true if the editor handled a go-to-line request
19610 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19611 maybe!({
19612 let breakpoint_store = self.breakpoint_store.as_ref()?;
19613
19614 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19615 else {
19616 self.clear_row_highlights::<ActiveDebugLine>();
19617 return None;
19618 };
19619
19620 let position = active_stack_frame.position;
19621 let buffer_id = position.buffer_id?;
19622 let snapshot = self
19623 .project
19624 .as_ref()?
19625 .read(cx)
19626 .buffer_for_id(buffer_id, cx)?
19627 .read(cx)
19628 .snapshot();
19629
19630 let mut handled = false;
19631 for (id, ExcerptRange { context, .. }) in
19632 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19633 {
19634 if context.start.cmp(&position, &snapshot).is_ge()
19635 || context.end.cmp(&position, &snapshot).is_lt()
19636 {
19637 continue;
19638 }
19639 let snapshot = self.buffer.read(cx).snapshot(cx);
19640 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19641
19642 handled = true;
19643 self.clear_row_highlights::<ActiveDebugLine>();
19644
19645 self.go_to_line::<ActiveDebugLine>(
19646 multibuffer_anchor,
19647 Some(cx.theme().colors().editor_debugger_active_line_background),
19648 window,
19649 cx,
19650 );
19651
19652 cx.notify();
19653 }
19654
19655 handled.then_some(())
19656 })
19657 .is_some()
19658 }
19659
19660 pub fn copy_file_name_without_extension(
19661 &mut self,
19662 _: &CopyFileNameWithoutExtension,
19663 _: &mut Window,
19664 cx: &mut Context<Self>,
19665 ) {
19666 if let Some(file) = self.target_file(cx)
19667 && let Some(file_stem) = file.path().file_stem()
19668 {
19669 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19670 }
19671 }
19672
19673 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19674 if let Some(file) = self.target_file(cx)
19675 && let Some(name) = file.path().file_name()
19676 {
19677 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19678 }
19679 }
19680
19681 pub fn toggle_git_blame(
19682 &mut self,
19683 _: &::git::Blame,
19684 window: &mut Window,
19685 cx: &mut Context<Self>,
19686 ) {
19687 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19688
19689 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19690 self.start_git_blame(true, window, cx);
19691 }
19692
19693 cx.notify();
19694 }
19695
19696 pub fn toggle_git_blame_inline(
19697 &mut self,
19698 _: &ToggleGitBlameInline,
19699 window: &mut Window,
19700 cx: &mut Context<Self>,
19701 ) {
19702 self.toggle_git_blame_inline_internal(true, window, cx);
19703 cx.notify();
19704 }
19705
19706 pub fn open_git_blame_commit(
19707 &mut self,
19708 _: &OpenGitBlameCommit,
19709 window: &mut Window,
19710 cx: &mut Context<Self>,
19711 ) {
19712 self.open_git_blame_commit_internal(window, cx);
19713 }
19714
19715 fn open_git_blame_commit_internal(
19716 &mut self,
19717 window: &mut Window,
19718 cx: &mut Context<Self>,
19719 ) -> Option<()> {
19720 let blame = self.blame.as_ref()?;
19721 let snapshot = self.snapshot(window, cx);
19722 let cursor = self.selections.newest::<Point>(cx).head();
19723 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19724 let (_, blame_entry) = blame
19725 .update(cx, |blame, cx| {
19726 blame
19727 .blame_for_rows(
19728 &[RowInfo {
19729 buffer_id: Some(buffer.remote_id()),
19730 buffer_row: Some(point.row),
19731 ..Default::default()
19732 }],
19733 cx,
19734 )
19735 .next()
19736 })
19737 .flatten()?;
19738 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19739 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19740 let workspace = self.workspace()?.downgrade();
19741 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19742 None
19743 }
19744
19745 pub fn git_blame_inline_enabled(&self) -> bool {
19746 self.git_blame_inline_enabled
19747 }
19748
19749 pub fn toggle_selection_menu(
19750 &mut self,
19751 _: &ToggleSelectionMenu,
19752 _: &mut Window,
19753 cx: &mut Context<Self>,
19754 ) {
19755 self.show_selection_menu = self
19756 .show_selection_menu
19757 .map(|show_selections_menu| !show_selections_menu)
19758 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19759
19760 cx.notify();
19761 }
19762
19763 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19764 self.show_selection_menu
19765 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19766 }
19767
19768 fn start_git_blame(
19769 &mut self,
19770 user_triggered: bool,
19771 window: &mut Window,
19772 cx: &mut Context<Self>,
19773 ) {
19774 if let Some(project) = self.project() {
19775 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19776 && buffer.read(cx).file().is_none()
19777 {
19778 return;
19779 }
19780
19781 let focused = self.focus_handle(cx).contains_focused(window, cx);
19782
19783 let project = project.clone();
19784 let blame = cx
19785 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19786 self.blame_subscription =
19787 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19788 self.blame = Some(blame);
19789 }
19790 }
19791
19792 fn toggle_git_blame_inline_internal(
19793 &mut self,
19794 user_triggered: bool,
19795 window: &mut Window,
19796 cx: &mut Context<Self>,
19797 ) {
19798 if self.git_blame_inline_enabled {
19799 self.git_blame_inline_enabled = false;
19800 self.show_git_blame_inline = false;
19801 self.show_git_blame_inline_delay_task.take();
19802 } else {
19803 self.git_blame_inline_enabled = true;
19804 self.start_git_blame_inline(user_triggered, window, cx);
19805 }
19806
19807 cx.notify();
19808 }
19809
19810 fn start_git_blame_inline(
19811 &mut self,
19812 user_triggered: bool,
19813 window: &mut Window,
19814 cx: &mut Context<Self>,
19815 ) {
19816 self.start_git_blame(user_triggered, window, cx);
19817
19818 if ProjectSettings::get_global(cx)
19819 .git
19820 .inline_blame_delay()
19821 .is_some()
19822 {
19823 self.start_inline_blame_timer(window, cx);
19824 } else {
19825 self.show_git_blame_inline = true
19826 }
19827 }
19828
19829 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19830 self.blame.as_ref()
19831 }
19832
19833 pub fn show_git_blame_gutter(&self) -> bool {
19834 self.show_git_blame_gutter
19835 }
19836
19837 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19838 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19839 }
19840
19841 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19842 self.show_git_blame_inline
19843 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19844 && !self.newest_selection_head_on_empty_line(cx)
19845 && self.has_blame_entries(cx)
19846 }
19847
19848 fn has_blame_entries(&self, cx: &App) -> bool {
19849 self.blame()
19850 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19851 }
19852
19853 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19854 let cursor_anchor = self.selections.newest_anchor().head();
19855
19856 let snapshot = self.buffer.read(cx).snapshot(cx);
19857 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19858
19859 snapshot.line_len(buffer_row) == 0
19860 }
19861
19862 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19863 let buffer_and_selection = maybe!({
19864 let selection = self.selections.newest::<Point>(cx);
19865 let selection_range = selection.range();
19866
19867 let multi_buffer = self.buffer().read(cx);
19868 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19869 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19870
19871 let (buffer, range, _) = if selection.reversed {
19872 buffer_ranges.first()
19873 } else {
19874 buffer_ranges.last()
19875 }?;
19876
19877 let selection = text::ToPoint::to_point(&range.start, buffer).row
19878 ..text::ToPoint::to_point(&range.end, buffer).row;
19879 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19880 });
19881
19882 let Some((buffer, selection)) = buffer_and_selection else {
19883 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19884 };
19885
19886 let Some(project) = self.project() else {
19887 return Task::ready(Err(anyhow!("editor does not have project")));
19888 };
19889
19890 project.update(cx, |project, cx| {
19891 project.get_permalink_to_line(&buffer, selection, cx)
19892 })
19893 }
19894
19895 pub fn copy_permalink_to_line(
19896 &mut self,
19897 _: &CopyPermalinkToLine,
19898 window: &mut Window,
19899 cx: &mut Context<Self>,
19900 ) {
19901 let permalink_task = self.get_permalink_to_line(cx);
19902 let workspace = self.workspace();
19903
19904 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19905 Ok(permalink) => {
19906 cx.update(|_, cx| {
19907 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19908 })
19909 .ok();
19910 }
19911 Err(err) => {
19912 let message = format!("Failed to copy permalink: {err}");
19913
19914 anyhow::Result::<()>::Err(err).log_err();
19915
19916 if let Some(workspace) = workspace {
19917 workspace
19918 .update_in(cx, |workspace, _, cx| {
19919 struct CopyPermalinkToLine;
19920
19921 workspace.show_toast(
19922 Toast::new(
19923 NotificationId::unique::<CopyPermalinkToLine>(),
19924 message,
19925 ),
19926 cx,
19927 )
19928 })
19929 .ok();
19930 }
19931 }
19932 })
19933 .detach();
19934 }
19935
19936 pub fn copy_file_location(
19937 &mut self,
19938 _: &CopyFileLocation,
19939 _: &mut Window,
19940 cx: &mut Context<Self>,
19941 ) {
19942 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19943 if let Some(file) = self.target_file(cx) {
19944 let path = file.path().display(file.path_style(cx));
19945 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19946 }
19947 }
19948
19949 pub fn open_permalink_to_line(
19950 &mut self,
19951 _: &OpenPermalinkToLine,
19952 window: &mut Window,
19953 cx: &mut Context<Self>,
19954 ) {
19955 let permalink_task = self.get_permalink_to_line(cx);
19956 let workspace = self.workspace();
19957
19958 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19959 Ok(permalink) => {
19960 cx.update(|_, cx| {
19961 cx.open_url(permalink.as_ref());
19962 })
19963 .ok();
19964 }
19965 Err(err) => {
19966 let message = format!("Failed to open permalink: {err}");
19967
19968 anyhow::Result::<()>::Err(err).log_err();
19969
19970 if let Some(workspace) = workspace {
19971 workspace
19972 .update(cx, |workspace, cx| {
19973 struct OpenPermalinkToLine;
19974
19975 workspace.show_toast(
19976 Toast::new(
19977 NotificationId::unique::<OpenPermalinkToLine>(),
19978 message,
19979 ),
19980 cx,
19981 )
19982 })
19983 .ok();
19984 }
19985 }
19986 })
19987 .detach();
19988 }
19989
19990 pub fn insert_uuid_v4(
19991 &mut self,
19992 _: &InsertUuidV4,
19993 window: &mut Window,
19994 cx: &mut Context<Self>,
19995 ) {
19996 self.insert_uuid(UuidVersion::V4, window, cx);
19997 }
19998
19999 pub fn insert_uuid_v7(
20000 &mut self,
20001 _: &InsertUuidV7,
20002 window: &mut Window,
20003 cx: &mut Context<Self>,
20004 ) {
20005 self.insert_uuid(UuidVersion::V7, window, cx);
20006 }
20007
20008 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20009 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20010 self.transact(window, cx, |this, window, cx| {
20011 let edits = this
20012 .selections
20013 .all::<Point>(cx)
20014 .into_iter()
20015 .map(|selection| {
20016 let uuid = match version {
20017 UuidVersion::V4 => uuid::Uuid::new_v4(),
20018 UuidVersion::V7 => uuid::Uuid::now_v7(),
20019 };
20020
20021 (selection.range(), uuid.to_string())
20022 });
20023 this.edit(edits, cx);
20024 this.refresh_edit_prediction(true, false, window, cx);
20025 });
20026 }
20027
20028 pub fn open_selections_in_multibuffer(
20029 &mut self,
20030 _: &OpenSelectionsInMultibuffer,
20031 window: &mut Window,
20032 cx: &mut Context<Self>,
20033 ) {
20034 let multibuffer = self.buffer.read(cx);
20035
20036 let Some(buffer) = multibuffer.as_singleton() else {
20037 return;
20038 };
20039
20040 let Some(workspace) = self.workspace() else {
20041 return;
20042 };
20043
20044 let title = multibuffer.title(cx).to_string();
20045
20046 let locations = self
20047 .selections
20048 .all_anchors(cx)
20049 .iter()
20050 .map(|selection| {
20051 (
20052 buffer.clone(),
20053 (selection.start.text_anchor..selection.end.text_anchor)
20054 .to_point(buffer.read(cx)),
20055 )
20056 })
20057 .into_group_map();
20058
20059 cx.spawn_in(window, async move |_, cx| {
20060 workspace.update_in(cx, |workspace, window, cx| {
20061 Self::open_locations_in_multibuffer(
20062 workspace,
20063 locations,
20064 format!("Selections for '{title}'"),
20065 false,
20066 MultibufferSelectionMode::All,
20067 window,
20068 cx,
20069 );
20070 })
20071 })
20072 .detach();
20073 }
20074
20075 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20076 /// last highlight added will be used.
20077 ///
20078 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20079 pub fn highlight_rows<T: 'static>(
20080 &mut self,
20081 range: Range<Anchor>,
20082 color: Hsla,
20083 options: RowHighlightOptions,
20084 cx: &mut Context<Self>,
20085 ) {
20086 let snapshot = self.buffer().read(cx).snapshot(cx);
20087 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20088 let ix = row_highlights.binary_search_by(|highlight| {
20089 Ordering::Equal
20090 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20091 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20092 });
20093
20094 if let Err(mut ix) = ix {
20095 let index = post_inc(&mut self.highlight_order);
20096
20097 // If this range intersects with the preceding highlight, then merge it with
20098 // the preceding highlight. Otherwise insert a new highlight.
20099 let mut merged = false;
20100 if ix > 0 {
20101 let prev_highlight = &mut row_highlights[ix - 1];
20102 if prev_highlight
20103 .range
20104 .end
20105 .cmp(&range.start, &snapshot)
20106 .is_ge()
20107 {
20108 ix -= 1;
20109 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20110 prev_highlight.range.end = range.end;
20111 }
20112 merged = true;
20113 prev_highlight.index = index;
20114 prev_highlight.color = color;
20115 prev_highlight.options = options;
20116 }
20117 }
20118
20119 if !merged {
20120 row_highlights.insert(
20121 ix,
20122 RowHighlight {
20123 range,
20124 index,
20125 color,
20126 options,
20127 type_id: TypeId::of::<T>(),
20128 },
20129 );
20130 }
20131
20132 // If any of the following highlights intersect with this one, merge them.
20133 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20134 let highlight = &row_highlights[ix];
20135 if next_highlight
20136 .range
20137 .start
20138 .cmp(&highlight.range.end, &snapshot)
20139 .is_le()
20140 {
20141 if next_highlight
20142 .range
20143 .end
20144 .cmp(&highlight.range.end, &snapshot)
20145 .is_gt()
20146 {
20147 row_highlights[ix].range.end = next_highlight.range.end;
20148 }
20149 row_highlights.remove(ix + 1);
20150 } else {
20151 break;
20152 }
20153 }
20154 }
20155 }
20156
20157 /// Remove any highlighted row ranges of the given type that intersect the
20158 /// given ranges.
20159 pub fn remove_highlighted_rows<T: 'static>(
20160 &mut self,
20161 ranges_to_remove: Vec<Range<Anchor>>,
20162 cx: &mut Context<Self>,
20163 ) {
20164 let snapshot = self.buffer().read(cx).snapshot(cx);
20165 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20166 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20167 row_highlights.retain(|highlight| {
20168 while let Some(range_to_remove) = ranges_to_remove.peek() {
20169 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20170 Ordering::Less | Ordering::Equal => {
20171 ranges_to_remove.next();
20172 }
20173 Ordering::Greater => {
20174 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20175 Ordering::Less | Ordering::Equal => {
20176 return false;
20177 }
20178 Ordering::Greater => break,
20179 }
20180 }
20181 }
20182 }
20183
20184 true
20185 })
20186 }
20187
20188 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20189 pub fn clear_row_highlights<T: 'static>(&mut self) {
20190 self.highlighted_rows.remove(&TypeId::of::<T>());
20191 }
20192
20193 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20194 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20195 self.highlighted_rows
20196 .get(&TypeId::of::<T>())
20197 .map_or(&[] as &[_], |vec| vec.as_slice())
20198 .iter()
20199 .map(|highlight| (highlight.range.clone(), highlight.color))
20200 }
20201
20202 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20203 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20204 /// Allows to ignore certain kinds of highlights.
20205 pub fn highlighted_display_rows(
20206 &self,
20207 window: &mut Window,
20208 cx: &mut App,
20209 ) -> BTreeMap<DisplayRow, LineHighlight> {
20210 let snapshot = self.snapshot(window, cx);
20211 let mut used_highlight_orders = HashMap::default();
20212 self.highlighted_rows
20213 .iter()
20214 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20215 .fold(
20216 BTreeMap::<DisplayRow, LineHighlight>::new(),
20217 |mut unique_rows, highlight| {
20218 let start = highlight.range.start.to_display_point(&snapshot);
20219 let end = highlight.range.end.to_display_point(&snapshot);
20220 let start_row = start.row().0;
20221 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20222 && end.column() == 0
20223 {
20224 end.row().0.saturating_sub(1)
20225 } else {
20226 end.row().0
20227 };
20228 for row in start_row..=end_row {
20229 let used_index =
20230 used_highlight_orders.entry(row).or_insert(highlight.index);
20231 if highlight.index >= *used_index {
20232 *used_index = highlight.index;
20233 unique_rows.insert(
20234 DisplayRow(row),
20235 LineHighlight {
20236 include_gutter: highlight.options.include_gutter,
20237 border: None,
20238 background: highlight.color.into(),
20239 type_id: Some(highlight.type_id),
20240 },
20241 );
20242 }
20243 }
20244 unique_rows
20245 },
20246 )
20247 }
20248
20249 pub fn highlighted_display_row_for_autoscroll(
20250 &self,
20251 snapshot: &DisplaySnapshot,
20252 ) -> Option<DisplayRow> {
20253 self.highlighted_rows
20254 .values()
20255 .flat_map(|highlighted_rows| highlighted_rows.iter())
20256 .filter_map(|highlight| {
20257 if highlight.options.autoscroll {
20258 Some(highlight.range.start.to_display_point(snapshot).row())
20259 } else {
20260 None
20261 }
20262 })
20263 .min()
20264 }
20265
20266 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20267 self.highlight_background::<SearchWithinRange>(
20268 ranges,
20269 |colors| colors.colors().editor_document_highlight_read_background,
20270 cx,
20271 )
20272 }
20273
20274 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20275 self.breadcrumb_header = Some(new_header);
20276 }
20277
20278 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20279 self.clear_background_highlights::<SearchWithinRange>(cx);
20280 }
20281
20282 pub fn highlight_background<T: 'static>(
20283 &mut self,
20284 ranges: &[Range<Anchor>],
20285 color_fetcher: fn(&Theme) -> Hsla,
20286 cx: &mut Context<Self>,
20287 ) {
20288 self.background_highlights.insert(
20289 HighlightKey::Type(TypeId::of::<T>()),
20290 (color_fetcher, Arc::from(ranges)),
20291 );
20292 self.scrollbar_marker_state.dirty = true;
20293 cx.notify();
20294 }
20295
20296 pub fn highlight_background_key<T: 'static>(
20297 &mut self,
20298 key: usize,
20299 ranges: &[Range<Anchor>],
20300 color_fetcher: fn(&Theme) -> Hsla,
20301 cx: &mut Context<Self>,
20302 ) {
20303 self.background_highlights.insert(
20304 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20305 (color_fetcher, Arc::from(ranges)),
20306 );
20307 self.scrollbar_marker_state.dirty = true;
20308 cx.notify();
20309 }
20310
20311 pub fn clear_background_highlights<T: 'static>(
20312 &mut self,
20313 cx: &mut Context<Self>,
20314 ) -> Option<BackgroundHighlight> {
20315 let text_highlights = self
20316 .background_highlights
20317 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20318 if !text_highlights.1.is_empty() {
20319 self.scrollbar_marker_state.dirty = true;
20320 cx.notify();
20321 }
20322 Some(text_highlights)
20323 }
20324
20325 pub fn highlight_gutter<T: 'static>(
20326 &mut self,
20327 ranges: impl Into<Vec<Range<Anchor>>>,
20328 color_fetcher: fn(&App) -> Hsla,
20329 cx: &mut Context<Self>,
20330 ) {
20331 self.gutter_highlights
20332 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20333 cx.notify();
20334 }
20335
20336 pub fn clear_gutter_highlights<T: 'static>(
20337 &mut self,
20338 cx: &mut Context<Self>,
20339 ) -> Option<GutterHighlight> {
20340 cx.notify();
20341 self.gutter_highlights.remove(&TypeId::of::<T>())
20342 }
20343
20344 pub fn insert_gutter_highlight<T: 'static>(
20345 &mut self,
20346 range: Range<Anchor>,
20347 color_fetcher: fn(&App) -> Hsla,
20348 cx: &mut Context<Self>,
20349 ) {
20350 let snapshot = self.buffer().read(cx).snapshot(cx);
20351 let mut highlights = self
20352 .gutter_highlights
20353 .remove(&TypeId::of::<T>())
20354 .map(|(_, highlights)| highlights)
20355 .unwrap_or_default();
20356 let ix = highlights.binary_search_by(|highlight| {
20357 Ordering::Equal
20358 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20359 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20360 });
20361 if let Err(ix) = ix {
20362 highlights.insert(ix, range);
20363 }
20364 self.gutter_highlights
20365 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20366 }
20367
20368 pub fn remove_gutter_highlights<T: 'static>(
20369 &mut self,
20370 ranges_to_remove: Vec<Range<Anchor>>,
20371 cx: &mut Context<Self>,
20372 ) {
20373 let snapshot = self.buffer().read(cx).snapshot(cx);
20374 let Some((color_fetcher, mut gutter_highlights)) =
20375 self.gutter_highlights.remove(&TypeId::of::<T>())
20376 else {
20377 return;
20378 };
20379 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20380 gutter_highlights.retain(|highlight| {
20381 while let Some(range_to_remove) = ranges_to_remove.peek() {
20382 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20383 Ordering::Less | Ordering::Equal => {
20384 ranges_to_remove.next();
20385 }
20386 Ordering::Greater => {
20387 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20388 Ordering::Less | Ordering::Equal => {
20389 return false;
20390 }
20391 Ordering::Greater => break,
20392 }
20393 }
20394 }
20395 }
20396
20397 true
20398 });
20399 self.gutter_highlights
20400 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20401 }
20402
20403 #[cfg(feature = "test-support")]
20404 pub fn all_text_highlights(
20405 &self,
20406 window: &mut Window,
20407 cx: &mut Context<Self>,
20408 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20409 let snapshot = self.snapshot(window, cx);
20410 self.display_map.update(cx, |display_map, _| {
20411 display_map
20412 .all_text_highlights()
20413 .map(|highlight| {
20414 let (style, ranges) = highlight.as_ref();
20415 (
20416 *style,
20417 ranges
20418 .iter()
20419 .map(|range| range.clone().to_display_points(&snapshot))
20420 .collect(),
20421 )
20422 })
20423 .collect()
20424 })
20425 }
20426
20427 #[cfg(feature = "test-support")]
20428 pub fn all_text_background_highlights(
20429 &self,
20430 window: &mut Window,
20431 cx: &mut Context<Self>,
20432 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20433 let snapshot = self.snapshot(window, cx);
20434 let buffer = &snapshot.buffer_snapshot();
20435 let start = buffer.anchor_before(0);
20436 let end = buffer.anchor_after(buffer.len());
20437 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20438 }
20439
20440 #[cfg(any(test, feature = "test-support"))]
20441 pub fn sorted_background_highlights_in_range(
20442 &self,
20443 search_range: Range<Anchor>,
20444 display_snapshot: &DisplaySnapshot,
20445 theme: &Theme,
20446 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20447 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20448 res.sort_by(|a, b| {
20449 a.0.start
20450 .cmp(&b.0.start)
20451 .then_with(|| a.0.end.cmp(&b.0.end))
20452 .then_with(|| a.1.cmp(&b.1))
20453 });
20454 res
20455 }
20456
20457 #[cfg(feature = "test-support")]
20458 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20459 let snapshot = self.buffer().read(cx).snapshot(cx);
20460
20461 let highlights = self
20462 .background_highlights
20463 .get(&HighlightKey::Type(TypeId::of::<
20464 items::BufferSearchHighlights,
20465 >()));
20466
20467 if let Some((_color, ranges)) = highlights {
20468 ranges
20469 .iter()
20470 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20471 .collect_vec()
20472 } else {
20473 vec![]
20474 }
20475 }
20476
20477 fn document_highlights_for_position<'a>(
20478 &'a self,
20479 position: Anchor,
20480 buffer: &'a MultiBufferSnapshot,
20481 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20482 let read_highlights = self
20483 .background_highlights
20484 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20485 .map(|h| &h.1);
20486 let write_highlights = self
20487 .background_highlights
20488 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20489 .map(|h| &h.1);
20490 let left_position = position.bias_left(buffer);
20491 let right_position = position.bias_right(buffer);
20492 read_highlights
20493 .into_iter()
20494 .chain(write_highlights)
20495 .flat_map(move |ranges| {
20496 let start_ix = match ranges.binary_search_by(|probe| {
20497 let cmp = probe.end.cmp(&left_position, buffer);
20498 if cmp.is_ge() {
20499 Ordering::Greater
20500 } else {
20501 Ordering::Less
20502 }
20503 }) {
20504 Ok(i) | Err(i) => i,
20505 };
20506
20507 ranges[start_ix..]
20508 .iter()
20509 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20510 })
20511 }
20512
20513 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20514 self.background_highlights
20515 .get(&HighlightKey::Type(TypeId::of::<T>()))
20516 .is_some_and(|(_, highlights)| !highlights.is_empty())
20517 }
20518
20519 /// Returns all background highlights for a given range.
20520 ///
20521 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20522 pub fn background_highlights_in_range(
20523 &self,
20524 search_range: Range<Anchor>,
20525 display_snapshot: &DisplaySnapshot,
20526 theme: &Theme,
20527 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20528 let mut results = Vec::new();
20529 for (color_fetcher, ranges) in self.background_highlights.values() {
20530 let color = color_fetcher(theme);
20531 let start_ix = match ranges.binary_search_by(|probe| {
20532 let cmp = probe
20533 .end
20534 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20535 if cmp.is_gt() {
20536 Ordering::Greater
20537 } else {
20538 Ordering::Less
20539 }
20540 }) {
20541 Ok(i) | Err(i) => i,
20542 };
20543 for range in &ranges[start_ix..] {
20544 if range
20545 .start
20546 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20547 .is_ge()
20548 {
20549 break;
20550 }
20551
20552 let start = range.start.to_display_point(display_snapshot);
20553 let end = range.end.to_display_point(display_snapshot);
20554 results.push((start..end, color))
20555 }
20556 }
20557 results
20558 }
20559
20560 pub fn gutter_highlights_in_range(
20561 &self,
20562 search_range: Range<Anchor>,
20563 display_snapshot: &DisplaySnapshot,
20564 cx: &App,
20565 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20566 let mut results = Vec::new();
20567 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20568 let color = color_fetcher(cx);
20569 let start_ix = match ranges.binary_search_by(|probe| {
20570 let cmp = probe
20571 .end
20572 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20573 if cmp.is_gt() {
20574 Ordering::Greater
20575 } else {
20576 Ordering::Less
20577 }
20578 }) {
20579 Ok(i) | Err(i) => i,
20580 };
20581 for range in &ranges[start_ix..] {
20582 if range
20583 .start
20584 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20585 .is_ge()
20586 {
20587 break;
20588 }
20589
20590 let start = range.start.to_display_point(display_snapshot);
20591 let end = range.end.to_display_point(display_snapshot);
20592 results.push((start..end, color))
20593 }
20594 }
20595 results
20596 }
20597
20598 /// Get the text ranges corresponding to the redaction query
20599 pub fn redacted_ranges(
20600 &self,
20601 search_range: Range<Anchor>,
20602 display_snapshot: &DisplaySnapshot,
20603 cx: &App,
20604 ) -> Vec<Range<DisplayPoint>> {
20605 display_snapshot
20606 .buffer_snapshot()
20607 .redacted_ranges(search_range, |file| {
20608 if let Some(file) = file {
20609 file.is_private()
20610 && EditorSettings::get(
20611 Some(SettingsLocation {
20612 worktree_id: file.worktree_id(cx),
20613 path: file.path().as_ref(),
20614 }),
20615 cx,
20616 )
20617 .redact_private_values
20618 } else {
20619 false
20620 }
20621 })
20622 .map(|range| {
20623 range.start.to_display_point(display_snapshot)
20624 ..range.end.to_display_point(display_snapshot)
20625 })
20626 .collect()
20627 }
20628
20629 pub fn highlight_text_key<T: 'static>(
20630 &mut self,
20631 key: usize,
20632 ranges: Vec<Range<Anchor>>,
20633 style: HighlightStyle,
20634 cx: &mut Context<Self>,
20635 ) {
20636 self.display_map.update(cx, |map, _| {
20637 map.highlight_text(
20638 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20639 ranges,
20640 style,
20641 );
20642 });
20643 cx.notify();
20644 }
20645
20646 pub fn highlight_text<T: 'static>(
20647 &mut self,
20648 ranges: Vec<Range<Anchor>>,
20649 style: HighlightStyle,
20650 cx: &mut Context<Self>,
20651 ) {
20652 self.display_map.update(cx, |map, _| {
20653 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20654 });
20655 cx.notify();
20656 }
20657
20658 pub(crate) fn highlight_inlays<T: 'static>(
20659 &mut self,
20660 highlights: Vec<InlayHighlight>,
20661 style: HighlightStyle,
20662 cx: &mut Context<Self>,
20663 ) {
20664 self.display_map.update(cx, |map, _| {
20665 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20666 });
20667 cx.notify();
20668 }
20669
20670 pub fn text_highlights<'a, T: 'static>(
20671 &'a self,
20672 cx: &'a App,
20673 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20674 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20675 }
20676
20677 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20678 let cleared = self
20679 .display_map
20680 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20681 if cleared {
20682 cx.notify();
20683 }
20684 }
20685
20686 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20687 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20688 && self.focus_handle.is_focused(window)
20689 }
20690
20691 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20692 self.show_cursor_when_unfocused = is_enabled;
20693 cx.notify();
20694 }
20695
20696 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20697 cx.notify();
20698 }
20699
20700 fn on_debug_session_event(
20701 &mut self,
20702 _session: Entity<Session>,
20703 event: &SessionEvent,
20704 cx: &mut Context<Self>,
20705 ) {
20706 if let SessionEvent::InvalidateInlineValue = event {
20707 self.refresh_inline_values(cx);
20708 }
20709 }
20710
20711 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20712 let Some(project) = self.project.clone() else {
20713 return;
20714 };
20715
20716 if !self.inline_value_cache.enabled {
20717 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20718 self.splice_inlays(&inlays, Vec::new(), cx);
20719 return;
20720 }
20721
20722 let current_execution_position = self
20723 .highlighted_rows
20724 .get(&TypeId::of::<ActiveDebugLine>())
20725 .and_then(|lines| lines.last().map(|line| line.range.end));
20726
20727 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20728 let inline_values = editor
20729 .update(cx, |editor, cx| {
20730 let Some(current_execution_position) = current_execution_position else {
20731 return Some(Task::ready(Ok(Vec::new())));
20732 };
20733
20734 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20735 let snapshot = buffer.snapshot(cx);
20736
20737 let excerpt = snapshot.excerpt_containing(
20738 current_execution_position..current_execution_position,
20739 )?;
20740
20741 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20742 })?;
20743
20744 let range =
20745 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20746
20747 project.inline_values(buffer, range, cx)
20748 })
20749 .ok()
20750 .flatten()?
20751 .await
20752 .context("refreshing debugger inlays")
20753 .log_err()?;
20754
20755 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20756
20757 for (buffer_id, inline_value) in inline_values
20758 .into_iter()
20759 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20760 {
20761 buffer_inline_values
20762 .entry(buffer_id)
20763 .or_default()
20764 .push(inline_value);
20765 }
20766
20767 editor
20768 .update(cx, |editor, cx| {
20769 let snapshot = editor.buffer.read(cx).snapshot(cx);
20770 let mut new_inlays = Vec::default();
20771
20772 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20773 let buffer_id = buffer_snapshot.remote_id();
20774 buffer_inline_values
20775 .get(&buffer_id)
20776 .into_iter()
20777 .flatten()
20778 .for_each(|hint| {
20779 let inlay = Inlay::debugger(
20780 post_inc(&mut editor.next_inlay_id),
20781 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20782 hint.text(),
20783 );
20784 if !inlay.text().chars().contains(&'\n') {
20785 new_inlays.push(inlay);
20786 }
20787 });
20788 }
20789
20790 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20791 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20792
20793 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20794 })
20795 .ok()?;
20796 Some(())
20797 });
20798 }
20799
20800 fn on_buffer_event(
20801 &mut self,
20802 multibuffer: &Entity<MultiBuffer>,
20803 event: &multi_buffer::Event,
20804 window: &mut Window,
20805 cx: &mut Context<Self>,
20806 ) {
20807 match event {
20808 multi_buffer::Event::Edited {
20809 singleton_buffer_edited,
20810 edited_buffer,
20811 } => {
20812 self.scrollbar_marker_state.dirty = true;
20813 self.active_indent_guides_state.dirty = true;
20814 self.refresh_active_diagnostics(cx);
20815 self.refresh_code_actions(window, cx);
20816 self.refresh_selected_text_highlights(true, window, cx);
20817 self.refresh_single_line_folds(window, cx);
20818 refresh_matching_bracket_highlights(self, cx);
20819 if self.has_active_edit_prediction() {
20820 self.update_visible_edit_prediction(window, cx);
20821 }
20822 if let Some(project) = self.project.as_ref()
20823 && let Some(edited_buffer) = edited_buffer
20824 {
20825 project.update(cx, |project, cx| {
20826 self.registered_buffers
20827 .entry(edited_buffer.read(cx).remote_id())
20828 .or_insert_with(|| {
20829 project.register_buffer_with_language_servers(edited_buffer, cx)
20830 });
20831 });
20832 }
20833 cx.emit(EditorEvent::BufferEdited);
20834 cx.emit(SearchEvent::MatchesInvalidated);
20835
20836 if let Some(buffer) = edited_buffer {
20837 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20838 }
20839
20840 if *singleton_buffer_edited {
20841 if let Some(buffer) = edited_buffer
20842 && buffer.read(cx).file().is_none()
20843 {
20844 cx.emit(EditorEvent::TitleChanged);
20845 }
20846 if let Some(project) = &self.project {
20847 #[allow(clippy::mutable_key_type)]
20848 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20849 multibuffer
20850 .all_buffers()
20851 .into_iter()
20852 .filter_map(|buffer| {
20853 buffer.update(cx, |buffer, cx| {
20854 let language = buffer.language()?;
20855 let should_discard = project.update(cx, |project, cx| {
20856 project.is_local()
20857 && !project.has_language_servers_for(buffer, cx)
20858 });
20859 should_discard.not().then_some(language.clone())
20860 })
20861 })
20862 .collect::<HashSet<_>>()
20863 });
20864 if !languages_affected.is_empty() {
20865 self.refresh_inlay_hints(
20866 InlayHintRefreshReason::BufferEdited(languages_affected),
20867 cx,
20868 );
20869 }
20870 }
20871 }
20872
20873 let Some(project) = &self.project else { return };
20874 let (telemetry, is_via_ssh) = {
20875 let project = project.read(cx);
20876 let telemetry = project.client().telemetry().clone();
20877 let is_via_ssh = project.is_via_remote_server();
20878 (telemetry, is_via_ssh)
20879 };
20880 refresh_linked_ranges(self, window, cx);
20881 telemetry.log_edit_event("editor", is_via_ssh);
20882 }
20883 multi_buffer::Event::ExcerptsAdded {
20884 buffer,
20885 predecessor,
20886 excerpts,
20887 } => {
20888 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20889 let buffer_id = buffer.read(cx).remote_id();
20890 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20891 && let Some(project) = &self.project
20892 {
20893 update_uncommitted_diff_for_buffer(
20894 cx.entity(),
20895 project,
20896 [buffer.clone()],
20897 self.buffer.clone(),
20898 cx,
20899 )
20900 .detach();
20901 }
20902 if self.active_diagnostics != ActiveDiagnostic::All {
20903 self.update_lsp_data(false, Some(buffer_id), window, cx);
20904 }
20905 cx.emit(EditorEvent::ExcerptsAdded {
20906 buffer: buffer.clone(),
20907 predecessor: *predecessor,
20908 excerpts: excerpts.clone(),
20909 });
20910 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20911 }
20912 multi_buffer::Event::ExcerptsRemoved {
20913 ids,
20914 removed_buffer_ids,
20915 } => {
20916 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20917 let buffer = self.buffer.read(cx);
20918 self.registered_buffers
20919 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20920 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20921 cx.emit(EditorEvent::ExcerptsRemoved {
20922 ids: ids.clone(),
20923 removed_buffer_ids: removed_buffer_ids.clone(),
20924 });
20925 }
20926 multi_buffer::Event::ExcerptsEdited {
20927 excerpt_ids,
20928 buffer_ids,
20929 } => {
20930 self.display_map.update(cx, |map, cx| {
20931 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20932 });
20933 cx.emit(EditorEvent::ExcerptsEdited {
20934 ids: excerpt_ids.clone(),
20935 });
20936 }
20937 multi_buffer::Event::ExcerptsExpanded { ids } => {
20938 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20939 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20940 }
20941 multi_buffer::Event::Reparsed(buffer_id) => {
20942 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20943 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20944
20945 cx.emit(EditorEvent::Reparsed(*buffer_id));
20946 }
20947 multi_buffer::Event::DiffHunksToggled => {
20948 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20949 }
20950 multi_buffer::Event::LanguageChanged(buffer_id) => {
20951 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20952 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20953 cx.emit(EditorEvent::Reparsed(*buffer_id));
20954 cx.notify();
20955 }
20956 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20957 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20958 multi_buffer::Event::FileHandleChanged
20959 | multi_buffer::Event::Reloaded
20960 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20961 multi_buffer::Event::DiagnosticsUpdated => {
20962 self.update_diagnostics_state(window, cx);
20963 }
20964 _ => {}
20965 };
20966 }
20967
20968 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20969 if !self.diagnostics_enabled() {
20970 return;
20971 }
20972 self.refresh_active_diagnostics(cx);
20973 self.refresh_inline_diagnostics(true, window, cx);
20974 self.scrollbar_marker_state.dirty = true;
20975 cx.notify();
20976 }
20977
20978 pub fn start_temporary_diff_override(&mut self) {
20979 self.load_diff_task.take();
20980 self.temporary_diff_override = true;
20981 }
20982
20983 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20984 self.temporary_diff_override = false;
20985 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20986 self.buffer.update(cx, |buffer, cx| {
20987 buffer.set_all_diff_hunks_collapsed(cx);
20988 });
20989
20990 if let Some(project) = self.project.clone() {
20991 self.load_diff_task = Some(
20992 update_uncommitted_diff_for_buffer(
20993 cx.entity(),
20994 &project,
20995 self.buffer.read(cx).all_buffers(),
20996 self.buffer.clone(),
20997 cx,
20998 )
20999 .shared(),
21000 );
21001 }
21002 }
21003
21004 fn on_display_map_changed(
21005 &mut self,
21006 _: Entity<DisplayMap>,
21007 _: &mut Window,
21008 cx: &mut Context<Self>,
21009 ) {
21010 cx.notify();
21011 }
21012
21013 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21014 if self.diagnostics_enabled() {
21015 let new_severity = EditorSettings::get_global(cx)
21016 .diagnostics_max_severity
21017 .unwrap_or(DiagnosticSeverity::Hint);
21018 self.set_max_diagnostics_severity(new_severity, cx);
21019 }
21020 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21021 self.update_edit_prediction_settings(cx);
21022 self.refresh_edit_prediction(true, false, window, cx);
21023 self.refresh_inline_values(cx);
21024 self.refresh_inlay_hints(
21025 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21026 self.selections.newest_anchor().head(),
21027 &self.buffer.read(cx).snapshot(cx),
21028 cx,
21029 )),
21030 cx,
21031 );
21032
21033 let old_cursor_shape = self.cursor_shape;
21034 let old_show_breadcrumbs = self.show_breadcrumbs;
21035
21036 {
21037 let editor_settings = EditorSettings::get_global(cx);
21038 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21039 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21040 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21041 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21042 }
21043
21044 if old_cursor_shape != self.cursor_shape {
21045 cx.emit(EditorEvent::CursorShapeChanged);
21046 }
21047
21048 if old_show_breadcrumbs != self.show_breadcrumbs {
21049 cx.emit(EditorEvent::BreadcrumbsChanged);
21050 }
21051
21052 let project_settings = ProjectSettings::get_global(cx);
21053 self.serialize_dirty_buffers =
21054 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21055
21056 if self.mode.is_full() {
21057 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21058 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21059 if self.show_inline_diagnostics != show_inline_diagnostics {
21060 self.show_inline_diagnostics = show_inline_diagnostics;
21061 self.refresh_inline_diagnostics(false, window, cx);
21062 }
21063
21064 if self.git_blame_inline_enabled != inline_blame_enabled {
21065 self.toggle_git_blame_inline_internal(false, window, cx);
21066 }
21067
21068 let minimap_settings = EditorSettings::get_global(cx).minimap;
21069 if self.minimap_visibility != MinimapVisibility::Disabled {
21070 if self.minimap_visibility.settings_visibility()
21071 != minimap_settings.minimap_enabled()
21072 {
21073 self.set_minimap_visibility(
21074 MinimapVisibility::for_mode(self.mode(), cx),
21075 window,
21076 cx,
21077 );
21078 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21079 minimap_entity.update(cx, |minimap_editor, cx| {
21080 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21081 })
21082 }
21083 }
21084 }
21085
21086 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21087 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21088 }) {
21089 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
21090 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21091 }
21092 self.refresh_colors(false, None, window, cx);
21093 }
21094
21095 cx.notify();
21096 }
21097
21098 pub fn set_searchable(&mut self, searchable: bool) {
21099 self.searchable = searchable;
21100 }
21101
21102 pub fn searchable(&self) -> bool {
21103 self.searchable
21104 }
21105
21106 fn open_proposed_changes_editor(
21107 &mut self,
21108 _: &OpenProposedChangesEditor,
21109 window: &mut Window,
21110 cx: &mut Context<Self>,
21111 ) {
21112 let Some(workspace) = self.workspace() else {
21113 cx.propagate();
21114 return;
21115 };
21116
21117 let selections = self.selections.all::<usize>(cx);
21118 let multi_buffer = self.buffer.read(cx);
21119 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21120 let mut new_selections_by_buffer = HashMap::default();
21121 for selection in selections {
21122 for (buffer, range, _) in
21123 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21124 {
21125 let mut range = range.to_point(buffer);
21126 range.start.column = 0;
21127 range.end.column = buffer.line_len(range.end.row);
21128 new_selections_by_buffer
21129 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21130 .or_insert(Vec::new())
21131 .push(range)
21132 }
21133 }
21134
21135 let proposed_changes_buffers = new_selections_by_buffer
21136 .into_iter()
21137 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21138 .collect::<Vec<_>>();
21139 let proposed_changes_editor = cx.new(|cx| {
21140 ProposedChangesEditor::new(
21141 "Proposed changes",
21142 proposed_changes_buffers,
21143 self.project.clone(),
21144 window,
21145 cx,
21146 )
21147 });
21148
21149 window.defer(cx, move |window, cx| {
21150 workspace.update(cx, |workspace, cx| {
21151 workspace.active_pane().update(cx, |pane, cx| {
21152 pane.add_item(
21153 Box::new(proposed_changes_editor),
21154 true,
21155 true,
21156 None,
21157 window,
21158 cx,
21159 );
21160 });
21161 });
21162 });
21163 }
21164
21165 pub fn open_excerpts_in_split(
21166 &mut self,
21167 _: &OpenExcerptsSplit,
21168 window: &mut Window,
21169 cx: &mut Context<Self>,
21170 ) {
21171 self.open_excerpts_common(None, true, window, cx)
21172 }
21173
21174 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21175 self.open_excerpts_common(None, false, window, cx)
21176 }
21177
21178 fn open_excerpts_common(
21179 &mut self,
21180 jump_data: Option<JumpData>,
21181 split: bool,
21182 window: &mut Window,
21183 cx: &mut Context<Self>,
21184 ) {
21185 let Some(workspace) = self.workspace() else {
21186 cx.propagate();
21187 return;
21188 };
21189
21190 if self.buffer.read(cx).is_singleton() {
21191 cx.propagate();
21192 return;
21193 }
21194
21195 let mut new_selections_by_buffer = HashMap::default();
21196 match &jump_data {
21197 Some(JumpData::MultiBufferPoint {
21198 excerpt_id,
21199 position,
21200 anchor,
21201 line_offset_from_top,
21202 }) => {
21203 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21204 if let Some(buffer) = multi_buffer_snapshot
21205 .buffer_id_for_excerpt(*excerpt_id)
21206 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21207 {
21208 let buffer_snapshot = buffer.read(cx).snapshot();
21209 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21210 language::ToPoint::to_point(anchor, &buffer_snapshot)
21211 } else {
21212 buffer_snapshot.clip_point(*position, Bias::Left)
21213 };
21214 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21215 new_selections_by_buffer.insert(
21216 buffer,
21217 (
21218 vec![jump_to_offset..jump_to_offset],
21219 Some(*line_offset_from_top),
21220 ),
21221 );
21222 }
21223 }
21224 Some(JumpData::MultiBufferRow {
21225 row,
21226 line_offset_from_top,
21227 }) => {
21228 let point = MultiBufferPoint::new(row.0, 0);
21229 if let Some((buffer, buffer_point, _)) =
21230 self.buffer.read(cx).point_to_buffer_point(point, cx)
21231 {
21232 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21233 new_selections_by_buffer
21234 .entry(buffer)
21235 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21236 .0
21237 .push(buffer_offset..buffer_offset)
21238 }
21239 }
21240 None => {
21241 let selections = self.selections.all::<usize>(cx);
21242 let multi_buffer = self.buffer.read(cx);
21243 for selection in selections {
21244 for (snapshot, range, _, anchor) in multi_buffer
21245 .snapshot(cx)
21246 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21247 {
21248 if let Some(anchor) = anchor {
21249 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21250 else {
21251 continue;
21252 };
21253 let offset = text::ToOffset::to_offset(
21254 &anchor.text_anchor,
21255 &buffer_handle.read(cx).snapshot(),
21256 );
21257 let range = offset..offset;
21258 new_selections_by_buffer
21259 .entry(buffer_handle)
21260 .or_insert((Vec::new(), None))
21261 .0
21262 .push(range)
21263 } else {
21264 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21265 else {
21266 continue;
21267 };
21268 new_selections_by_buffer
21269 .entry(buffer_handle)
21270 .or_insert((Vec::new(), None))
21271 .0
21272 .push(range)
21273 }
21274 }
21275 }
21276 }
21277 }
21278
21279 new_selections_by_buffer
21280 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21281
21282 if new_selections_by_buffer.is_empty() {
21283 return;
21284 }
21285
21286 // We defer the pane interaction because we ourselves are a workspace item
21287 // and activating a new item causes the pane to call a method on us reentrantly,
21288 // which panics if we're on the stack.
21289 window.defer(cx, move |window, cx| {
21290 workspace.update(cx, |workspace, cx| {
21291 let pane = if split {
21292 workspace.adjacent_pane(window, cx)
21293 } else {
21294 workspace.active_pane().clone()
21295 };
21296
21297 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21298 let editor = buffer
21299 .read(cx)
21300 .file()
21301 .is_none()
21302 .then(|| {
21303 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21304 // so `workspace.open_project_item` will never find them, always opening a new editor.
21305 // Instead, we try to activate the existing editor in the pane first.
21306 let (editor, pane_item_index) =
21307 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21308 let editor = item.downcast::<Editor>()?;
21309 let singleton_buffer =
21310 editor.read(cx).buffer().read(cx).as_singleton()?;
21311 if singleton_buffer == buffer {
21312 Some((editor, i))
21313 } else {
21314 None
21315 }
21316 })?;
21317 pane.update(cx, |pane, cx| {
21318 pane.activate_item(pane_item_index, true, true, window, cx)
21319 });
21320 Some(editor)
21321 })
21322 .flatten()
21323 .unwrap_or_else(|| {
21324 workspace.open_project_item::<Self>(
21325 pane.clone(),
21326 buffer,
21327 true,
21328 true,
21329 window,
21330 cx,
21331 )
21332 });
21333
21334 editor.update(cx, |editor, cx| {
21335 let autoscroll = match scroll_offset {
21336 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21337 None => Autoscroll::newest(),
21338 };
21339 let nav_history = editor.nav_history.take();
21340 editor.change_selections(
21341 SelectionEffects::scroll(autoscroll),
21342 window,
21343 cx,
21344 |s| {
21345 s.select_ranges(ranges);
21346 },
21347 );
21348 editor.nav_history = nav_history;
21349 });
21350 }
21351 })
21352 });
21353 }
21354
21355 // For now, don't allow opening excerpts in buffers that aren't backed by
21356 // regular project files.
21357 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21358 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21359 }
21360
21361 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21362 let snapshot = self.buffer.read(cx).read(cx);
21363 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21364 Some(
21365 ranges
21366 .iter()
21367 .map(move |range| {
21368 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21369 })
21370 .collect(),
21371 )
21372 }
21373
21374 fn selection_replacement_ranges(
21375 &self,
21376 range: Range<OffsetUtf16>,
21377 cx: &mut App,
21378 ) -> Vec<Range<OffsetUtf16>> {
21379 let selections = self.selections.all::<OffsetUtf16>(cx);
21380 let newest_selection = selections
21381 .iter()
21382 .max_by_key(|selection| selection.id)
21383 .unwrap();
21384 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21385 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21386 let snapshot = self.buffer.read(cx).read(cx);
21387 selections
21388 .into_iter()
21389 .map(|mut selection| {
21390 selection.start.0 =
21391 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21392 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21393 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21394 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21395 })
21396 .collect()
21397 }
21398
21399 fn report_editor_event(
21400 &self,
21401 reported_event: ReportEditorEvent,
21402 file_extension: Option<String>,
21403 cx: &App,
21404 ) {
21405 if cfg!(any(test, feature = "test-support")) {
21406 return;
21407 }
21408
21409 let Some(project) = &self.project else { return };
21410
21411 // If None, we are in a file without an extension
21412 let file = self
21413 .buffer
21414 .read(cx)
21415 .as_singleton()
21416 .and_then(|b| b.read(cx).file());
21417 let file_extension = file_extension.or(file
21418 .as_ref()
21419 .and_then(|file| Path::new(file.file_name(cx)).extension())
21420 .and_then(|e| e.to_str())
21421 .map(|a| a.to_string()));
21422
21423 let vim_mode = vim_enabled(cx);
21424
21425 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21426 let copilot_enabled = edit_predictions_provider
21427 == language::language_settings::EditPredictionProvider::Copilot;
21428 let copilot_enabled_for_language = self
21429 .buffer
21430 .read(cx)
21431 .language_settings(cx)
21432 .show_edit_predictions;
21433
21434 let project = project.read(cx);
21435 let event_type = reported_event.event_type();
21436
21437 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21438 telemetry::event!(
21439 event_type,
21440 type = if auto_saved {"autosave"} else {"manual"},
21441 file_extension,
21442 vim_mode,
21443 copilot_enabled,
21444 copilot_enabled_for_language,
21445 edit_predictions_provider,
21446 is_via_ssh = project.is_via_remote_server(),
21447 );
21448 } else {
21449 telemetry::event!(
21450 event_type,
21451 file_extension,
21452 vim_mode,
21453 copilot_enabled,
21454 copilot_enabled_for_language,
21455 edit_predictions_provider,
21456 is_via_ssh = project.is_via_remote_server(),
21457 );
21458 };
21459 }
21460
21461 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21462 /// with each line being an array of {text, highlight} objects.
21463 fn copy_highlight_json(
21464 &mut self,
21465 _: &CopyHighlightJson,
21466 window: &mut Window,
21467 cx: &mut Context<Self>,
21468 ) {
21469 #[derive(Serialize)]
21470 struct Chunk<'a> {
21471 text: String,
21472 highlight: Option<&'a str>,
21473 }
21474
21475 let snapshot = self.buffer.read(cx).snapshot(cx);
21476 let range = self
21477 .selected_text_range(false, window, cx)
21478 .and_then(|selection| {
21479 if selection.range.is_empty() {
21480 None
21481 } else {
21482 Some(selection.range)
21483 }
21484 })
21485 .unwrap_or_else(|| 0..snapshot.len());
21486
21487 let chunks = snapshot.chunks(range, true);
21488 let mut lines = Vec::new();
21489 let mut line: VecDeque<Chunk> = VecDeque::new();
21490
21491 let Some(style) = self.style.as_ref() else {
21492 return;
21493 };
21494
21495 for chunk in chunks {
21496 let highlight = chunk
21497 .syntax_highlight_id
21498 .and_then(|id| id.name(&style.syntax));
21499 let mut chunk_lines = chunk.text.split('\n').peekable();
21500 while let Some(text) = chunk_lines.next() {
21501 let mut merged_with_last_token = false;
21502 if let Some(last_token) = line.back_mut()
21503 && last_token.highlight == highlight
21504 {
21505 last_token.text.push_str(text);
21506 merged_with_last_token = true;
21507 }
21508
21509 if !merged_with_last_token {
21510 line.push_back(Chunk {
21511 text: text.into(),
21512 highlight,
21513 });
21514 }
21515
21516 if chunk_lines.peek().is_some() {
21517 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21518 line.pop_front();
21519 }
21520 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21521 line.pop_back();
21522 }
21523
21524 lines.push(mem::take(&mut line));
21525 }
21526 }
21527 }
21528
21529 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21530 return;
21531 };
21532 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21533 }
21534
21535 pub fn open_context_menu(
21536 &mut self,
21537 _: &OpenContextMenu,
21538 window: &mut Window,
21539 cx: &mut Context<Self>,
21540 ) {
21541 self.request_autoscroll(Autoscroll::newest(), cx);
21542 let position = self.selections.newest_display(cx).start;
21543 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21544 }
21545
21546 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21547 &self.inlay_hint_cache
21548 }
21549
21550 pub fn replay_insert_event(
21551 &mut self,
21552 text: &str,
21553 relative_utf16_range: Option<Range<isize>>,
21554 window: &mut Window,
21555 cx: &mut Context<Self>,
21556 ) {
21557 if !self.input_enabled {
21558 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21559 return;
21560 }
21561 if let Some(relative_utf16_range) = relative_utf16_range {
21562 let selections = self.selections.all::<OffsetUtf16>(cx);
21563 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21564 let new_ranges = selections.into_iter().map(|range| {
21565 let start = OffsetUtf16(
21566 range
21567 .head()
21568 .0
21569 .saturating_add_signed(relative_utf16_range.start),
21570 );
21571 let end = OffsetUtf16(
21572 range
21573 .head()
21574 .0
21575 .saturating_add_signed(relative_utf16_range.end),
21576 );
21577 start..end
21578 });
21579 s.select_ranges(new_ranges);
21580 });
21581 }
21582
21583 self.handle_input(text, window, cx);
21584 }
21585
21586 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21587 let Some(provider) = self.semantics_provider.as_ref() else {
21588 return false;
21589 };
21590
21591 let mut supports = false;
21592 self.buffer().update(cx, |this, cx| {
21593 this.for_each_buffer(|buffer| {
21594 supports |= provider.supports_inlay_hints(buffer, cx);
21595 });
21596 });
21597
21598 supports
21599 }
21600
21601 pub fn is_focused(&self, window: &Window) -> bool {
21602 self.focus_handle.is_focused(window)
21603 }
21604
21605 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21606 cx.emit(EditorEvent::Focused);
21607
21608 if let Some(descendant) = self
21609 .last_focused_descendant
21610 .take()
21611 .and_then(|descendant| descendant.upgrade())
21612 {
21613 window.focus(&descendant);
21614 } else {
21615 if let Some(blame) = self.blame.as_ref() {
21616 blame.update(cx, GitBlame::focus)
21617 }
21618
21619 self.blink_manager.update(cx, BlinkManager::enable);
21620 self.show_cursor_names(window, cx);
21621 self.buffer.update(cx, |buffer, cx| {
21622 buffer.finalize_last_transaction(cx);
21623 if self.leader_id.is_none() {
21624 buffer.set_active_selections(
21625 &self.selections.disjoint_anchors_arc(),
21626 self.selections.line_mode(),
21627 self.cursor_shape,
21628 cx,
21629 );
21630 }
21631 });
21632 }
21633 }
21634
21635 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21636 cx.emit(EditorEvent::FocusedIn)
21637 }
21638
21639 fn handle_focus_out(
21640 &mut self,
21641 event: FocusOutEvent,
21642 _window: &mut Window,
21643 cx: &mut Context<Self>,
21644 ) {
21645 if event.blurred != self.focus_handle {
21646 self.last_focused_descendant = Some(event.blurred);
21647 }
21648 self.selection_drag_state = SelectionDragState::None;
21649 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21650 }
21651
21652 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21653 self.blink_manager.update(cx, BlinkManager::disable);
21654 self.buffer
21655 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21656
21657 if let Some(blame) = self.blame.as_ref() {
21658 blame.update(cx, GitBlame::blur)
21659 }
21660 if !self.hover_state.focused(window, cx) {
21661 hide_hover(self, cx);
21662 }
21663 if !self
21664 .context_menu
21665 .borrow()
21666 .as_ref()
21667 .is_some_and(|context_menu| context_menu.focused(window, cx))
21668 {
21669 self.hide_context_menu(window, cx);
21670 }
21671 self.take_active_edit_prediction(cx);
21672 cx.emit(EditorEvent::Blurred);
21673 cx.notify();
21674 }
21675
21676 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21677 let mut pending: String = window
21678 .pending_input_keystrokes()
21679 .into_iter()
21680 .flatten()
21681 .filter_map(|keystroke| {
21682 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21683 keystroke.key_char.clone()
21684 } else {
21685 None
21686 }
21687 })
21688 .collect();
21689
21690 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21691 pending = "".to_string();
21692 }
21693
21694 let existing_pending = self
21695 .text_highlights::<PendingInput>(cx)
21696 .map(|(_, ranges)| ranges.to_vec());
21697 if existing_pending.is_none() && pending.is_empty() {
21698 return;
21699 }
21700 let transaction =
21701 self.transact(window, cx, |this, window, cx| {
21702 let selections = this.selections.all::<usize>(cx);
21703 let edits = selections
21704 .iter()
21705 .map(|selection| (selection.end..selection.end, pending.clone()));
21706 this.edit(edits, cx);
21707 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21708 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21709 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21710 }));
21711 });
21712 if let Some(existing_ranges) = existing_pending {
21713 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21714 this.edit(edits, cx);
21715 }
21716 });
21717
21718 let snapshot = self.snapshot(window, cx);
21719 let ranges = self
21720 .selections
21721 .all::<usize>(cx)
21722 .into_iter()
21723 .map(|selection| {
21724 snapshot.buffer_snapshot().anchor_after(selection.end)
21725 ..snapshot
21726 .buffer_snapshot()
21727 .anchor_before(selection.end + pending.len())
21728 })
21729 .collect();
21730
21731 if pending.is_empty() {
21732 self.clear_highlights::<PendingInput>(cx);
21733 } else {
21734 self.highlight_text::<PendingInput>(
21735 ranges,
21736 HighlightStyle {
21737 underline: Some(UnderlineStyle {
21738 thickness: px(1.),
21739 color: None,
21740 wavy: false,
21741 }),
21742 ..Default::default()
21743 },
21744 cx,
21745 );
21746 }
21747
21748 self.ime_transaction = self.ime_transaction.or(transaction);
21749 if let Some(transaction) = self.ime_transaction {
21750 self.buffer.update(cx, |buffer, cx| {
21751 buffer.group_until_transaction(transaction, cx);
21752 });
21753 }
21754
21755 if self.text_highlights::<PendingInput>(cx).is_none() {
21756 self.ime_transaction.take();
21757 }
21758 }
21759
21760 pub fn register_action_renderer(
21761 &mut self,
21762 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21763 ) -> Subscription {
21764 let id = self.next_editor_action_id.post_inc();
21765 self.editor_actions
21766 .borrow_mut()
21767 .insert(id, Box::new(listener));
21768
21769 let editor_actions = self.editor_actions.clone();
21770 Subscription::new(move || {
21771 editor_actions.borrow_mut().remove(&id);
21772 })
21773 }
21774
21775 pub fn register_action<A: Action>(
21776 &mut self,
21777 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21778 ) -> Subscription {
21779 let id = self.next_editor_action_id.post_inc();
21780 let listener = Arc::new(listener);
21781 self.editor_actions.borrow_mut().insert(
21782 id,
21783 Box::new(move |_, window, _| {
21784 let listener = listener.clone();
21785 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21786 let action = action.downcast_ref().unwrap();
21787 if phase == DispatchPhase::Bubble {
21788 listener(action, window, cx)
21789 }
21790 })
21791 }),
21792 );
21793
21794 let editor_actions = self.editor_actions.clone();
21795 Subscription::new(move || {
21796 editor_actions.borrow_mut().remove(&id);
21797 })
21798 }
21799
21800 pub fn file_header_size(&self) -> u32 {
21801 FILE_HEADER_HEIGHT
21802 }
21803
21804 pub fn restore(
21805 &mut self,
21806 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21807 window: &mut Window,
21808 cx: &mut Context<Self>,
21809 ) {
21810 let workspace = self.workspace();
21811 let project = self.project();
21812 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21813 let mut tasks = Vec::new();
21814 for (buffer_id, changes) in revert_changes {
21815 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21816 buffer.update(cx, |buffer, cx| {
21817 buffer.edit(
21818 changes
21819 .into_iter()
21820 .map(|(range, text)| (range, text.to_string())),
21821 None,
21822 cx,
21823 );
21824 });
21825
21826 if let Some(project) =
21827 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21828 {
21829 project.update(cx, |project, cx| {
21830 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21831 })
21832 }
21833 }
21834 }
21835 tasks
21836 });
21837 cx.spawn_in(window, async move |_, cx| {
21838 for (buffer, task) in save_tasks {
21839 let result = task.await;
21840 if result.is_err() {
21841 let Some(path) = buffer
21842 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21843 .ok()
21844 else {
21845 continue;
21846 };
21847 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21848 let Some(task) = cx
21849 .update_window_entity(workspace, |workspace, window, cx| {
21850 workspace
21851 .open_path_preview(path, None, false, false, false, window, cx)
21852 })
21853 .ok()
21854 else {
21855 continue;
21856 };
21857 task.await.log_err();
21858 }
21859 }
21860 }
21861 })
21862 .detach();
21863 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21864 selections.refresh()
21865 });
21866 }
21867
21868 pub fn to_pixel_point(
21869 &self,
21870 source: multi_buffer::Anchor,
21871 editor_snapshot: &EditorSnapshot,
21872 window: &mut Window,
21873 ) -> Option<gpui::Point<Pixels>> {
21874 let source_point = source.to_display_point(editor_snapshot);
21875 self.display_to_pixel_point(source_point, editor_snapshot, window)
21876 }
21877
21878 pub fn display_to_pixel_point(
21879 &self,
21880 source: DisplayPoint,
21881 editor_snapshot: &EditorSnapshot,
21882 window: &mut Window,
21883 ) -> Option<gpui::Point<Pixels>> {
21884 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21885 let text_layout_details = self.text_layout_details(window);
21886 let scroll_top = text_layout_details
21887 .scroll_anchor
21888 .scroll_position(editor_snapshot)
21889 .y;
21890
21891 if source.row().as_f64() < scroll_top.floor() {
21892 return None;
21893 }
21894 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21895 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21896 Some(gpui::Point::new(source_x, source_y))
21897 }
21898
21899 pub fn has_visible_completions_menu(&self) -> bool {
21900 !self.edit_prediction_preview_is_active()
21901 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21902 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21903 })
21904 }
21905
21906 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21907 if self.mode.is_minimap() {
21908 return;
21909 }
21910 self.addons
21911 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21912 }
21913
21914 pub fn unregister_addon<T: Addon>(&mut self) {
21915 self.addons.remove(&std::any::TypeId::of::<T>());
21916 }
21917
21918 pub fn addon<T: Addon>(&self) -> Option<&T> {
21919 let type_id = std::any::TypeId::of::<T>();
21920 self.addons
21921 .get(&type_id)
21922 .and_then(|item| item.to_any().downcast_ref::<T>())
21923 }
21924
21925 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21926 let type_id = std::any::TypeId::of::<T>();
21927 self.addons
21928 .get_mut(&type_id)
21929 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21930 }
21931
21932 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21933 let text_layout_details = self.text_layout_details(window);
21934 let style = &text_layout_details.editor_style;
21935 let font_id = window.text_system().resolve_font(&style.text.font());
21936 let font_size = style.text.font_size.to_pixels(window.rem_size());
21937 let line_height = style.text.line_height_in_pixels(window.rem_size());
21938 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21939 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21940
21941 CharacterDimensions {
21942 em_width,
21943 em_advance,
21944 line_height,
21945 }
21946 }
21947
21948 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21949 self.load_diff_task.clone()
21950 }
21951
21952 fn read_metadata_from_db(
21953 &mut self,
21954 item_id: u64,
21955 workspace_id: WorkspaceId,
21956 window: &mut Window,
21957 cx: &mut Context<Editor>,
21958 ) {
21959 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21960 && !self.mode.is_minimap()
21961 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21962 {
21963 let buffer_snapshot = OnceCell::new();
21964
21965 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21966 && !folds.is_empty()
21967 {
21968 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21969 self.fold_ranges(
21970 folds
21971 .into_iter()
21972 .map(|(start, end)| {
21973 snapshot.clip_offset(start, Bias::Left)
21974 ..snapshot.clip_offset(end, Bias::Right)
21975 })
21976 .collect(),
21977 false,
21978 window,
21979 cx,
21980 );
21981 }
21982
21983 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21984 && !selections.is_empty()
21985 {
21986 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21987 // skip adding the initial selection to selection history
21988 self.selection_history.mode = SelectionHistoryMode::Skipping;
21989 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21990 s.select_ranges(selections.into_iter().map(|(start, end)| {
21991 snapshot.clip_offset(start, Bias::Left)
21992 ..snapshot.clip_offset(end, Bias::Right)
21993 }));
21994 });
21995 self.selection_history.mode = SelectionHistoryMode::Normal;
21996 };
21997 }
21998
21999 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22000 }
22001
22002 fn update_lsp_data(
22003 &mut self,
22004 ignore_cache: bool,
22005 for_buffer: Option<BufferId>,
22006 window: &mut Window,
22007 cx: &mut Context<'_, Self>,
22008 ) {
22009 self.pull_diagnostics(for_buffer, window, cx);
22010 self.refresh_colors(ignore_cache, for_buffer, window, cx);
22011 }
22012}
22013
22014fn edit_for_markdown_paste<'a>(
22015 buffer: &MultiBufferSnapshot,
22016 range: Range<usize>,
22017 to_insert: &'a str,
22018 url: Option<url::Url>,
22019) -> (Range<usize>, Cow<'a, str>) {
22020 if url.is_none() {
22021 return (range, Cow::Borrowed(to_insert));
22022 };
22023
22024 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22025
22026 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22027 Cow::Borrowed(to_insert)
22028 } else {
22029 Cow::Owned(format!("[{old_text}]({to_insert})"))
22030 };
22031 (range, new_text)
22032}
22033
22034fn vim_enabled(cx: &App) -> bool {
22035 vim_mode_setting::VimModeSetting::try_get(cx)
22036 .map(|vim_mode| vim_mode.0)
22037 .unwrap_or(false)
22038}
22039
22040fn process_completion_for_edit(
22041 completion: &Completion,
22042 intent: CompletionIntent,
22043 buffer: &Entity<Buffer>,
22044 cursor_position: &text::Anchor,
22045 cx: &mut Context<Editor>,
22046) -> CompletionEdit {
22047 let buffer = buffer.read(cx);
22048 let buffer_snapshot = buffer.snapshot();
22049 let (snippet, new_text) = if completion.is_snippet() {
22050 let mut snippet_source = completion.new_text.clone();
22051 // Workaround for typescript language server issues so that methods don't expand within
22052 // strings and functions with type expressions. The previous point is used because the query
22053 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22054 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22055 let previous_point = if previous_point.column > 0 {
22056 cursor_position.to_previous_offset(&buffer_snapshot)
22057 } else {
22058 cursor_position.to_offset(&buffer_snapshot)
22059 };
22060 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22061 && scope.prefers_label_for_snippet_in_completion()
22062 && let Some(label) = completion.label()
22063 && matches!(
22064 completion.kind(),
22065 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22066 )
22067 {
22068 snippet_source = label;
22069 }
22070 match Snippet::parse(&snippet_source).log_err() {
22071 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22072 None => (None, completion.new_text.clone()),
22073 }
22074 } else {
22075 (None, completion.new_text.clone())
22076 };
22077
22078 let mut range_to_replace = {
22079 let replace_range = &completion.replace_range;
22080 if let CompletionSource::Lsp {
22081 insert_range: Some(insert_range),
22082 ..
22083 } = &completion.source
22084 {
22085 debug_assert_eq!(
22086 insert_range.start, replace_range.start,
22087 "insert_range and replace_range should start at the same position"
22088 );
22089 debug_assert!(
22090 insert_range
22091 .start
22092 .cmp(cursor_position, &buffer_snapshot)
22093 .is_le(),
22094 "insert_range should start before or at cursor position"
22095 );
22096 debug_assert!(
22097 replace_range
22098 .start
22099 .cmp(cursor_position, &buffer_snapshot)
22100 .is_le(),
22101 "replace_range should start before or at cursor position"
22102 );
22103
22104 let should_replace = match intent {
22105 CompletionIntent::CompleteWithInsert => false,
22106 CompletionIntent::CompleteWithReplace => true,
22107 CompletionIntent::Complete | CompletionIntent::Compose => {
22108 let insert_mode =
22109 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22110 .completions
22111 .lsp_insert_mode;
22112 match insert_mode {
22113 LspInsertMode::Insert => false,
22114 LspInsertMode::Replace => true,
22115 LspInsertMode::ReplaceSubsequence => {
22116 let mut text_to_replace = buffer.chars_for_range(
22117 buffer.anchor_before(replace_range.start)
22118 ..buffer.anchor_after(replace_range.end),
22119 );
22120 let mut current_needle = text_to_replace.next();
22121 for haystack_ch in completion.label.text.chars() {
22122 if let Some(needle_ch) = current_needle
22123 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22124 {
22125 current_needle = text_to_replace.next();
22126 }
22127 }
22128 current_needle.is_none()
22129 }
22130 LspInsertMode::ReplaceSuffix => {
22131 if replace_range
22132 .end
22133 .cmp(cursor_position, &buffer_snapshot)
22134 .is_gt()
22135 {
22136 let range_after_cursor = *cursor_position..replace_range.end;
22137 let text_after_cursor = buffer
22138 .text_for_range(
22139 buffer.anchor_before(range_after_cursor.start)
22140 ..buffer.anchor_after(range_after_cursor.end),
22141 )
22142 .collect::<String>()
22143 .to_ascii_lowercase();
22144 completion
22145 .label
22146 .text
22147 .to_ascii_lowercase()
22148 .ends_with(&text_after_cursor)
22149 } else {
22150 true
22151 }
22152 }
22153 }
22154 }
22155 };
22156
22157 if should_replace {
22158 replace_range.clone()
22159 } else {
22160 insert_range.clone()
22161 }
22162 } else {
22163 replace_range.clone()
22164 }
22165 };
22166
22167 if range_to_replace
22168 .end
22169 .cmp(cursor_position, &buffer_snapshot)
22170 .is_lt()
22171 {
22172 range_to_replace.end = *cursor_position;
22173 }
22174
22175 CompletionEdit {
22176 new_text,
22177 replace_range: range_to_replace.to_offset(buffer),
22178 snippet,
22179 }
22180}
22181
22182struct CompletionEdit {
22183 new_text: String,
22184 replace_range: Range<usize>,
22185 snippet: Option<Snippet>,
22186}
22187
22188fn insert_extra_newline_brackets(
22189 buffer: &MultiBufferSnapshot,
22190 range: Range<usize>,
22191 language: &language::LanguageScope,
22192) -> bool {
22193 let leading_whitespace_len = buffer
22194 .reversed_chars_at(range.start)
22195 .take_while(|c| c.is_whitespace() && *c != '\n')
22196 .map(|c| c.len_utf8())
22197 .sum::<usize>();
22198 let trailing_whitespace_len = buffer
22199 .chars_at(range.end)
22200 .take_while(|c| c.is_whitespace() && *c != '\n')
22201 .map(|c| c.len_utf8())
22202 .sum::<usize>();
22203 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22204
22205 language.brackets().any(|(pair, enabled)| {
22206 let pair_start = pair.start.trim_end();
22207 let pair_end = pair.end.trim_start();
22208
22209 enabled
22210 && pair.newline
22211 && buffer.contains_str_at(range.end, pair_end)
22212 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22213 })
22214}
22215
22216fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22217 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22218 [(buffer, range, _)] => (*buffer, range.clone()),
22219 _ => return false,
22220 };
22221 let pair = {
22222 let mut result: Option<BracketMatch> = None;
22223
22224 for pair in buffer
22225 .all_bracket_ranges(range.clone())
22226 .filter(move |pair| {
22227 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22228 })
22229 {
22230 let len = pair.close_range.end - pair.open_range.start;
22231
22232 if let Some(existing) = &result {
22233 let existing_len = existing.close_range.end - existing.open_range.start;
22234 if len > existing_len {
22235 continue;
22236 }
22237 }
22238
22239 result = Some(pair);
22240 }
22241
22242 result
22243 };
22244 let Some(pair) = pair else {
22245 return false;
22246 };
22247 pair.newline_only
22248 && buffer
22249 .chars_for_range(pair.open_range.end..range.start)
22250 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22251 .all(|c| c.is_whitespace() && c != '\n')
22252}
22253
22254fn update_uncommitted_diff_for_buffer(
22255 editor: Entity<Editor>,
22256 project: &Entity<Project>,
22257 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22258 buffer: Entity<MultiBuffer>,
22259 cx: &mut App,
22260) -> Task<()> {
22261 let mut tasks = Vec::new();
22262 project.update(cx, |project, cx| {
22263 for buffer in buffers {
22264 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22265 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22266 }
22267 }
22268 });
22269 cx.spawn(async move |cx| {
22270 let diffs = future::join_all(tasks).await;
22271 if editor
22272 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22273 .unwrap_or(false)
22274 {
22275 return;
22276 }
22277
22278 buffer
22279 .update(cx, |buffer, cx| {
22280 for diff in diffs.into_iter().flatten() {
22281 buffer.add_diff(diff, cx);
22282 }
22283 })
22284 .ok();
22285 })
22286}
22287
22288fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22289 let tab_size = tab_size.get() as usize;
22290 let mut width = offset;
22291
22292 for ch in text.chars() {
22293 width += if ch == '\t' {
22294 tab_size - (width % tab_size)
22295 } else {
22296 1
22297 };
22298 }
22299
22300 width - offset
22301}
22302
22303#[cfg(test)]
22304mod tests {
22305 use super::*;
22306
22307 #[test]
22308 fn test_string_size_with_expanded_tabs() {
22309 let nz = |val| NonZeroU32::new(val).unwrap();
22310 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22311 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22312 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22313 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22314 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22315 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22316 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22317 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22318 }
22319}
22320
22321/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22322struct WordBreakingTokenizer<'a> {
22323 input: &'a str,
22324}
22325
22326impl<'a> WordBreakingTokenizer<'a> {
22327 fn new(input: &'a str) -> Self {
22328 Self { input }
22329 }
22330}
22331
22332fn is_char_ideographic(ch: char) -> bool {
22333 use unicode_script::Script::*;
22334 use unicode_script::UnicodeScript;
22335 matches!(ch.script(), Han | Tangut | Yi)
22336}
22337
22338fn is_grapheme_ideographic(text: &str) -> bool {
22339 text.chars().any(is_char_ideographic)
22340}
22341
22342fn is_grapheme_whitespace(text: &str) -> bool {
22343 text.chars().any(|x| x.is_whitespace())
22344}
22345
22346fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22347 text.chars()
22348 .next()
22349 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22350}
22351
22352#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22353enum WordBreakToken<'a> {
22354 Word { token: &'a str, grapheme_len: usize },
22355 InlineWhitespace { token: &'a str, grapheme_len: usize },
22356 Newline,
22357}
22358
22359impl<'a> Iterator for WordBreakingTokenizer<'a> {
22360 /// Yields a span, the count of graphemes in the token, and whether it was
22361 /// whitespace. Note that it also breaks at word boundaries.
22362 type Item = WordBreakToken<'a>;
22363
22364 fn next(&mut self) -> Option<Self::Item> {
22365 use unicode_segmentation::UnicodeSegmentation;
22366 if self.input.is_empty() {
22367 return None;
22368 }
22369
22370 let mut iter = self.input.graphemes(true).peekable();
22371 let mut offset = 0;
22372 let mut grapheme_len = 0;
22373 if let Some(first_grapheme) = iter.next() {
22374 let is_newline = first_grapheme == "\n";
22375 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22376 offset += first_grapheme.len();
22377 grapheme_len += 1;
22378 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22379 if let Some(grapheme) = iter.peek().copied()
22380 && should_stay_with_preceding_ideograph(grapheme)
22381 {
22382 offset += grapheme.len();
22383 grapheme_len += 1;
22384 }
22385 } else {
22386 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22387 let mut next_word_bound = words.peek().copied();
22388 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22389 next_word_bound = words.next();
22390 }
22391 while let Some(grapheme) = iter.peek().copied() {
22392 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22393 break;
22394 };
22395 if is_grapheme_whitespace(grapheme) != is_whitespace
22396 || (grapheme == "\n") != is_newline
22397 {
22398 break;
22399 };
22400 offset += grapheme.len();
22401 grapheme_len += 1;
22402 iter.next();
22403 }
22404 }
22405 let token = &self.input[..offset];
22406 self.input = &self.input[offset..];
22407 if token == "\n" {
22408 Some(WordBreakToken::Newline)
22409 } else if is_whitespace {
22410 Some(WordBreakToken::InlineWhitespace {
22411 token,
22412 grapheme_len,
22413 })
22414 } else {
22415 Some(WordBreakToken::Word {
22416 token,
22417 grapheme_len,
22418 })
22419 }
22420 } else {
22421 None
22422 }
22423 }
22424}
22425
22426#[test]
22427fn test_word_breaking_tokenizer() {
22428 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22429 ("", &[]),
22430 (" ", &[whitespace(" ", 2)]),
22431 ("Ʒ", &[word("Ʒ", 1)]),
22432 ("Ǽ", &[word("Ǽ", 1)]),
22433 ("⋑", &[word("⋑", 1)]),
22434 ("⋑⋑", &[word("⋑⋑", 2)]),
22435 (
22436 "原理,进而",
22437 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22438 ),
22439 (
22440 "hello world",
22441 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22442 ),
22443 (
22444 "hello, world",
22445 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22446 ),
22447 (
22448 " hello world",
22449 &[
22450 whitespace(" ", 2),
22451 word("hello", 5),
22452 whitespace(" ", 1),
22453 word("world", 5),
22454 ],
22455 ),
22456 (
22457 "这是什么 \n 钢笔",
22458 &[
22459 word("这", 1),
22460 word("是", 1),
22461 word("什", 1),
22462 word("么", 1),
22463 whitespace(" ", 1),
22464 newline(),
22465 whitespace(" ", 1),
22466 word("钢", 1),
22467 word("笔", 1),
22468 ],
22469 ),
22470 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22471 ];
22472
22473 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22474 WordBreakToken::Word {
22475 token,
22476 grapheme_len,
22477 }
22478 }
22479
22480 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22481 WordBreakToken::InlineWhitespace {
22482 token,
22483 grapheme_len,
22484 }
22485 }
22486
22487 fn newline() -> WordBreakToken<'static> {
22488 WordBreakToken::Newline
22489 }
22490
22491 for (input, result) in tests {
22492 assert_eq!(
22493 WordBreakingTokenizer::new(input)
22494 .collect::<Vec<_>>()
22495 .as_slice(),
22496 *result,
22497 );
22498 }
22499}
22500
22501fn wrap_with_prefix(
22502 first_line_prefix: String,
22503 subsequent_lines_prefix: String,
22504 unwrapped_text: String,
22505 wrap_column: usize,
22506 tab_size: NonZeroU32,
22507 preserve_existing_whitespace: bool,
22508) -> String {
22509 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22510 let subsequent_lines_prefix_len =
22511 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22512 let mut wrapped_text = String::new();
22513 let mut current_line = first_line_prefix;
22514 let mut is_first_line = true;
22515
22516 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22517 let mut current_line_len = first_line_prefix_len;
22518 let mut in_whitespace = false;
22519 for token in tokenizer {
22520 let have_preceding_whitespace = in_whitespace;
22521 match token {
22522 WordBreakToken::Word {
22523 token,
22524 grapheme_len,
22525 } => {
22526 in_whitespace = false;
22527 let current_prefix_len = if is_first_line {
22528 first_line_prefix_len
22529 } else {
22530 subsequent_lines_prefix_len
22531 };
22532 if current_line_len + grapheme_len > wrap_column
22533 && current_line_len != current_prefix_len
22534 {
22535 wrapped_text.push_str(current_line.trim_end());
22536 wrapped_text.push('\n');
22537 is_first_line = false;
22538 current_line = subsequent_lines_prefix.clone();
22539 current_line_len = subsequent_lines_prefix_len;
22540 }
22541 current_line.push_str(token);
22542 current_line_len += grapheme_len;
22543 }
22544 WordBreakToken::InlineWhitespace {
22545 mut token,
22546 mut grapheme_len,
22547 } => {
22548 in_whitespace = true;
22549 if have_preceding_whitespace && !preserve_existing_whitespace {
22550 continue;
22551 }
22552 if !preserve_existing_whitespace {
22553 // Keep a single whitespace grapheme as-is
22554 if let Some(first) =
22555 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22556 {
22557 token = first;
22558 } else {
22559 token = " ";
22560 }
22561 grapheme_len = 1;
22562 }
22563 let current_prefix_len = if is_first_line {
22564 first_line_prefix_len
22565 } else {
22566 subsequent_lines_prefix_len
22567 };
22568 if current_line_len + grapheme_len > wrap_column {
22569 wrapped_text.push_str(current_line.trim_end());
22570 wrapped_text.push('\n');
22571 is_first_line = false;
22572 current_line = subsequent_lines_prefix.clone();
22573 current_line_len = subsequent_lines_prefix_len;
22574 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22575 current_line.push_str(token);
22576 current_line_len += grapheme_len;
22577 }
22578 }
22579 WordBreakToken::Newline => {
22580 in_whitespace = true;
22581 let current_prefix_len = if is_first_line {
22582 first_line_prefix_len
22583 } else {
22584 subsequent_lines_prefix_len
22585 };
22586 if preserve_existing_whitespace {
22587 wrapped_text.push_str(current_line.trim_end());
22588 wrapped_text.push('\n');
22589 is_first_line = false;
22590 current_line = subsequent_lines_prefix.clone();
22591 current_line_len = subsequent_lines_prefix_len;
22592 } else if have_preceding_whitespace {
22593 continue;
22594 } else if current_line_len + 1 > wrap_column
22595 && current_line_len != current_prefix_len
22596 {
22597 wrapped_text.push_str(current_line.trim_end());
22598 wrapped_text.push('\n');
22599 is_first_line = false;
22600 current_line = subsequent_lines_prefix.clone();
22601 current_line_len = subsequent_lines_prefix_len;
22602 } else if current_line_len != current_prefix_len {
22603 current_line.push(' ');
22604 current_line_len += 1;
22605 }
22606 }
22607 }
22608 }
22609
22610 if !current_line.is_empty() {
22611 wrapped_text.push_str(¤t_line);
22612 }
22613 wrapped_text
22614}
22615
22616#[test]
22617fn test_wrap_with_prefix() {
22618 assert_eq!(
22619 wrap_with_prefix(
22620 "# ".to_string(),
22621 "# ".to_string(),
22622 "abcdefg".to_string(),
22623 4,
22624 NonZeroU32::new(4).unwrap(),
22625 false,
22626 ),
22627 "# abcdefg"
22628 );
22629 assert_eq!(
22630 wrap_with_prefix(
22631 "".to_string(),
22632 "".to_string(),
22633 "\thello world".to_string(),
22634 8,
22635 NonZeroU32::new(4).unwrap(),
22636 false,
22637 ),
22638 "hello\nworld"
22639 );
22640 assert_eq!(
22641 wrap_with_prefix(
22642 "// ".to_string(),
22643 "// ".to_string(),
22644 "xx \nyy zz aa bb cc".to_string(),
22645 12,
22646 NonZeroU32::new(4).unwrap(),
22647 false,
22648 ),
22649 "// xx yy zz\n// aa bb cc"
22650 );
22651 assert_eq!(
22652 wrap_with_prefix(
22653 String::new(),
22654 String::new(),
22655 "这是什么 \n 钢笔".to_string(),
22656 3,
22657 NonZeroU32::new(4).unwrap(),
22658 false,
22659 ),
22660 "这是什\n么 钢\n笔"
22661 );
22662 assert_eq!(
22663 wrap_with_prefix(
22664 String::new(),
22665 String::new(),
22666 format!("foo{}bar", '\u{2009}'), // thin space
22667 80,
22668 NonZeroU32::new(4).unwrap(),
22669 false,
22670 ),
22671 format!("foo{}bar", '\u{2009}')
22672 );
22673}
22674
22675pub trait CollaborationHub {
22676 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22677 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22678 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22679}
22680
22681impl CollaborationHub for Entity<Project> {
22682 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22683 self.read(cx).collaborators()
22684 }
22685
22686 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22687 self.read(cx).user_store().read(cx).participant_indices()
22688 }
22689
22690 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22691 let this = self.read(cx);
22692 let user_ids = this.collaborators().values().map(|c| c.user_id);
22693 this.user_store().read(cx).participant_names(user_ids, cx)
22694 }
22695}
22696
22697pub trait SemanticsProvider {
22698 fn hover(
22699 &self,
22700 buffer: &Entity<Buffer>,
22701 position: text::Anchor,
22702 cx: &mut App,
22703 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22704
22705 fn inline_values(
22706 &self,
22707 buffer_handle: Entity<Buffer>,
22708 range: Range<text::Anchor>,
22709 cx: &mut App,
22710 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22711
22712 fn inlay_hints(
22713 &self,
22714 buffer_handle: Entity<Buffer>,
22715 range: Range<text::Anchor>,
22716 cx: &mut App,
22717 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22718
22719 fn resolve_inlay_hint(
22720 &self,
22721 hint: InlayHint,
22722 buffer_handle: Entity<Buffer>,
22723 server_id: LanguageServerId,
22724 cx: &mut App,
22725 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22726
22727 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22728
22729 fn document_highlights(
22730 &self,
22731 buffer: &Entity<Buffer>,
22732 position: text::Anchor,
22733 cx: &mut App,
22734 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22735
22736 fn definitions(
22737 &self,
22738 buffer: &Entity<Buffer>,
22739 position: text::Anchor,
22740 kind: GotoDefinitionKind,
22741 cx: &mut App,
22742 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22743
22744 fn range_for_rename(
22745 &self,
22746 buffer: &Entity<Buffer>,
22747 position: text::Anchor,
22748 cx: &mut App,
22749 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22750
22751 fn perform_rename(
22752 &self,
22753 buffer: &Entity<Buffer>,
22754 position: text::Anchor,
22755 new_name: String,
22756 cx: &mut App,
22757 ) -> Option<Task<Result<ProjectTransaction>>>;
22758}
22759
22760pub trait CompletionProvider {
22761 fn completions(
22762 &self,
22763 excerpt_id: ExcerptId,
22764 buffer: &Entity<Buffer>,
22765 buffer_position: text::Anchor,
22766 trigger: CompletionContext,
22767 window: &mut Window,
22768 cx: &mut Context<Editor>,
22769 ) -> Task<Result<Vec<CompletionResponse>>>;
22770
22771 fn resolve_completions(
22772 &self,
22773 _buffer: Entity<Buffer>,
22774 _completion_indices: Vec<usize>,
22775 _completions: Rc<RefCell<Box<[Completion]>>>,
22776 _cx: &mut Context<Editor>,
22777 ) -> Task<Result<bool>> {
22778 Task::ready(Ok(false))
22779 }
22780
22781 fn apply_additional_edits_for_completion(
22782 &self,
22783 _buffer: Entity<Buffer>,
22784 _completions: Rc<RefCell<Box<[Completion]>>>,
22785 _completion_index: usize,
22786 _push_to_history: bool,
22787 _cx: &mut Context<Editor>,
22788 ) -> Task<Result<Option<language::Transaction>>> {
22789 Task::ready(Ok(None))
22790 }
22791
22792 fn is_completion_trigger(
22793 &self,
22794 buffer: &Entity<Buffer>,
22795 position: language::Anchor,
22796 text: &str,
22797 trigger_in_words: bool,
22798 menu_is_open: bool,
22799 cx: &mut Context<Editor>,
22800 ) -> bool;
22801
22802 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22803
22804 fn sort_completions(&self) -> bool {
22805 true
22806 }
22807
22808 fn filter_completions(&self) -> bool {
22809 true
22810 }
22811}
22812
22813pub trait CodeActionProvider {
22814 fn id(&self) -> Arc<str>;
22815
22816 fn code_actions(
22817 &self,
22818 buffer: &Entity<Buffer>,
22819 range: Range<text::Anchor>,
22820 window: &mut Window,
22821 cx: &mut App,
22822 ) -> Task<Result<Vec<CodeAction>>>;
22823
22824 fn apply_code_action(
22825 &self,
22826 buffer_handle: Entity<Buffer>,
22827 action: CodeAction,
22828 excerpt_id: ExcerptId,
22829 push_to_history: bool,
22830 window: &mut Window,
22831 cx: &mut App,
22832 ) -> Task<Result<ProjectTransaction>>;
22833}
22834
22835impl CodeActionProvider for Entity<Project> {
22836 fn id(&self) -> Arc<str> {
22837 "project".into()
22838 }
22839
22840 fn code_actions(
22841 &self,
22842 buffer: &Entity<Buffer>,
22843 range: Range<text::Anchor>,
22844 _window: &mut Window,
22845 cx: &mut App,
22846 ) -> Task<Result<Vec<CodeAction>>> {
22847 self.update(cx, |project, cx| {
22848 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22849 let code_actions = project.code_actions(buffer, range, None, cx);
22850 cx.background_spawn(async move {
22851 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22852 Ok(code_lens_actions
22853 .context("code lens fetch")?
22854 .into_iter()
22855 .flatten()
22856 .chain(
22857 code_actions
22858 .context("code action fetch")?
22859 .into_iter()
22860 .flatten(),
22861 )
22862 .collect())
22863 })
22864 })
22865 }
22866
22867 fn apply_code_action(
22868 &self,
22869 buffer_handle: Entity<Buffer>,
22870 action: CodeAction,
22871 _excerpt_id: ExcerptId,
22872 push_to_history: bool,
22873 _window: &mut Window,
22874 cx: &mut App,
22875 ) -> Task<Result<ProjectTransaction>> {
22876 self.update(cx, |project, cx| {
22877 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22878 })
22879 }
22880}
22881
22882fn snippet_completions(
22883 project: &Project,
22884 buffer: &Entity<Buffer>,
22885 buffer_position: text::Anchor,
22886 cx: &mut App,
22887) -> Task<Result<CompletionResponse>> {
22888 let languages = buffer.read(cx).languages_at(buffer_position);
22889 let snippet_store = project.snippets().read(cx);
22890
22891 let scopes: Vec<_> = languages
22892 .iter()
22893 .filter_map(|language| {
22894 let language_name = language.lsp_id();
22895 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22896
22897 if snippets.is_empty() {
22898 None
22899 } else {
22900 Some((language.default_scope(), snippets))
22901 }
22902 })
22903 .collect();
22904
22905 if scopes.is_empty() {
22906 return Task::ready(Ok(CompletionResponse {
22907 completions: vec![],
22908 display_options: CompletionDisplayOptions::default(),
22909 is_incomplete: false,
22910 }));
22911 }
22912
22913 let snapshot = buffer.read(cx).text_snapshot();
22914 let executor = cx.background_executor().clone();
22915
22916 cx.background_spawn(async move {
22917 let mut is_incomplete = false;
22918 let mut completions: Vec<Completion> = Vec::new();
22919 for (scope, snippets) in scopes.into_iter() {
22920 let classifier =
22921 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22922
22923 const MAX_WORD_PREFIX_LEN: usize = 128;
22924 let last_word: String = snapshot
22925 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22926 .take(MAX_WORD_PREFIX_LEN)
22927 .take_while(|c| classifier.is_word(*c))
22928 .collect::<String>()
22929 .chars()
22930 .rev()
22931 .collect();
22932
22933 if last_word.is_empty() {
22934 return Ok(CompletionResponse {
22935 completions: vec![],
22936 display_options: CompletionDisplayOptions::default(),
22937 is_incomplete: true,
22938 });
22939 }
22940
22941 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22942 let to_lsp = |point: &text::Anchor| {
22943 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22944 point_to_lsp(end)
22945 };
22946 let lsp_end = to_lsp(&buffer_position);
22947
22948 let candidates = snippets
22949 .iter()
22950 .enumerate()
22951 .flat_map(|(ix, snippet)| {
22952 snippet
22953 .prefix
22954 .iter()
22955 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22956 })
22957 .collect::<Vec<StringMatchCandidate>>();
22958
22959 const MAX_RESULTS: usize = 100;
22960 let mut matches = fuzzy::match_strings(
22961 &candidates,
22962 &last_word,
22963 last_word.chars().any(|c| c.is_uppercase()),
22964 true,
22965 MAX_RESULTS,
22966 &Default::default(),
22967 executor.clone(),
22968 )
22969 .await;
22970
22971 if matches.len() >= MAX_RESULTS {
22972 is_incomplete = true;
22973 }
22974
22975 // Remove all candidates where the query's start does not match the start of any word in the candidate
22976 if let Some(query_start) = last_word.chars().next() {
22977 matches.retain(|string_match| {
22978 split_words(&string_match.string).any(|word| {
22979 // Check that the first codepoint of the word as lowercase matches the first
22980 // codepoint of the query as lowercase
22981 word.chars()
22982 .flat_map(|codepoint| codepoint.to_lowercase())
22983 .zip(query_start.to_lowercase())
22984 .all(|(word_cp, query_cp)| word_cp == query_cp)
22985 })
22986 });
22987 }
22988
22989 let matched_strings = matches
22990 .into_iter()
22991 .map(|m| m.string)
22992 .collect::<HashSet<_>>();
22993
22994 completions.extend(snippets.iter().filter_map(|snippet| {
22995 let matching_prefix = snippet
22996 .prefix
22997 .iter()
22998 .find(|prefix| matched_strings.contains(*prefix))?;
22999 let start = as_offset - last_word.len();
23000 let start = snapshot.anchor_before(start);
23001 let range = start..buffer_position;
23002 let lsp_start = to_lsp(&start);
23003 let lsp_range = lsp::Range {
23004 start: lsp_start,
23005 end: lsp_end,
23006 };
23007 Some(Completion {
23008 replace_range: range,
23009 new_text: snippet.body.clone(),
23010 source: CompletionSource::Lsp {
23011 insert_range: None,
23012 server_id: LanguageServerId(usize::MAX),
23013 resolved: true,
23014 lsp_completion: Box::new(lsp::CompletionItem {
23015 label: snippet.prefix.first().unwrap().clone(),
23016 kind: Some(CompletionItemKind::SNIPPET),
23017 label_details: snippet.description.as_ref().map(|description| {
23018 lsp::CompletionItemLabelDetails {
23019 detail: Some(description.clone()),
23020 description: None,
23021 }
23022 }),
23023 insert_text_format: Some(InsertTextFormat::SNIPPET),
23024 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23025 lsp::InsertReplaceEdit {
23026 new_text: snippet.body.clone(),
23027 insert: lsp_range,
23028 replace: lsp_range,
23029 },
23030 )),
23031 filter_text: Some(snippet.body.clone()),
23032 sort_text: Some(char::MAX.to_string()),
23033 ..lsp::CompletionItem::default()
23034 }),
23035 lsp_defaults: None,
23036 },
23037 label: CodeLabel {
23038 text: matching_prefix.clone(),
23039 runs: Vec::new(),
23040 filter_range: 0..matching_prefix.len(),
23041 },
23042 icon_path: None,
23043 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23044 single_line: snippet.name.clone().into(),
23045 plain_text: snippet
23046 .description
23047 .clone()
23048 .map(|description| description.into()),
23049 }),
23050 insert_text_mode: None,
23051 confirm: None,
23052 })
23053 }))
23054 }
23055
23056 Ok(CompletionResponse {
23057 completions,
23058 display_options: CompletionDisplayOptions::default(),
23059 is_incomplete,
23060 })
23061 })
23062}
23063
23064impl CompletionProvider for Entity<Project> {
23065 fn completions(
23066 &self,
23067 _excerpt_id: ExcerptId,
23068 buffer: &Entity<Buffer>,
23069 buffer_position: text::Anchor,
23070 options: CompletionContext,
23071 _window: &mut Window,
23072 cx: &mut Context<Editor>,
23073 ) -> Task<Result<Vec<CompletionResponse>>> {
23074 self.update(cx, |project, cx| {
23075 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23076 let project_completions = project.completions(buffer, buffer_position, options, cx);
23077 cx.background_spawn(async move {
23078 let mut responses = project_completions.await?;
23079 let snippets = snippets.await?;
23080 if !snippets.completions.is_empty() {
23081 responses.push(snippets);
23082 }
23083 Ok(responses)
23084 })
23085 })
23086 }
23087
23088 fn resolve_completions(
23089 &self,
23090 buffer: Entity<Buffer>,
23091 completion_indices: Vec<usize>,
23092 completions: Rc<RefCell<Box<[Completion]>>>,
23093 cx: &mut Context<Editor>,
23094 ) -> Task<Result<bool>> {
23095 self.update(cx, |project, cx| {
23096 project.lsp_store().update(cx, |lsp_store, cx| {
23097 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23098 })
23099 })
23100 }
23101
23102 fn apply_additional_edits_for_completion(
23103 &self,
23104 buffer: Entity<Buffer>,
23105 completions: Rc<RefCell<Box<[Completion]>>>,
23106 completion_index: usize,
23107 push_to_history: bool,
23108 cx: &mut Context<Editor>,
23109 ) -> Task<Result<Option<language::Transaction>>> {
23110 self.update(cx, |project, cx| {
23111 project.lsp_store().update(cx, |lsp_store, cx| {
23112 lsp_store.apply_additional_edits_for_completion(
23113 buffer,
23114 completions,
23115 completion_index,
23116 push_to_history,
23117 cx,
23118 )
23119 })
23120 })
23121 }
23122
23123 fn is_completion_trigger(
23124 &self,
23125 buffer: &Entity<Buffer>,
23126 position: language::Anchor,
23127 text: &str,
23128 trigger_in_words: bool,
23129 menu_is_open: bool,
23130 cx: &mut Context<Editor>,
23131 ) -> bool {
23132 let mut chars = text.chars();
23133 let char = if let Some(char) = chars.next() {
23134 char
23135 } else {
23136 return false;
23137 };
23138 if chars.next().is_some() {
23139 return false;
23140 }
23141
23142 let buffer = buffer.read(cx);
23143 let snapshot = buffer.snapshot();
23144 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23145 return false;
23146 }
23147 let classifier = snapshot
23148 .char_classifier_at(position)
23149 .scope_context(Some(CharScopeContext::Completion));
23150 if trigger_in_words && classifier.is_word(char) {
23151 return true;
23152 }
23153
23154 buffer.completion_triggers().contains(text)
23155 }
23156}
23157
23158impl SemanticsProvider for Entity<Project> {
23159 fn hover(
23160 &self,
23161 buffer: &Entity<Buffer>,
23162 position: text::Anchor,
23163 cx: &mut App,
23164 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23165 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23166 }
23167
23168 fn document_highlights(
23169 &self,
23170 buffer: &Entity<Buffer>,
23171 position: text::Anchor,
23172 cx: &mut App,
23173 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23174 Some(self.update(cx, |project, cx| {
23175 project.document_highlights(buffer, position, cx)
23176 }))
23177 }
23178
23179 fn definitions(
23180 &self,
23181 buffer: &Entity<Buffer>,
23182 position: text::Anchor,
23183 kind: GotoDefinitionKind,
23184 cx: &mut App,
23185 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23186 Some(self.update(cx, |project, cx| match kind {
23187 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23188 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23189 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23190 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23191 }))
23192 }
23193
23194 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23195 self.update(cx, |project, cx| {
23196 if project
23197 .active_debug_session(cx)
23198 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23199 {
23200 return true;
23201 }
23202
23203 buffer.update(cx, |buffer, cx| {
23204 project.any_language_server_supports_inlay_hints(buffer, cx)
23205 })
23206 })
23207 }
23208
23209 fn inline_values(
23210 &self,
23211 buffer_handle: Entity<Buffer>,
23212 range: Range<text::Anchor>,
23213 cx: &mut App,
23214 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23215 self.update(cx, |project, cx| {
23216 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23217
23218 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23219 })
23220 }
23221
23222 fn inlay_hints(
23223 &self,
23224 buffer_handle: Entity<Buffer>,
23225 range: Range<text::Anchor>,
23226 cx: &mut App,
23227 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23228 Some(self.update(cx, |project, cx| {
23229 project.inlay_hints(buffer_handle, range, cx)
23230 }))
23231 }
23232
23233 fn resolve_inlay_hint(
23234 &self,
23235 hint: InlayHint,
23236 buffer_handle: Entity<Buffer>,
23237 server_id: LanguageServerId,
23238 cx: &mut App,
23239 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23240 Some(self.update(cx, |project, cx| {
23241 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23242 }))
23243 }
23244
23245 fn range_for_rename(
23246 &self,
23247 buffer: &Entity<Buffer>,
23248 position: text::Anchor,
23249 cx: &mut App,
23250 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23251 Some(self.update(cx, |project, cx| {
23252 let buffer = buffer.clone();
23253 let task = project.prepare_rename(buffer.clone(), position, cx);
23254 cx.spawn(async move |_, cx| {
23255 Ok(match task.await? {
23256 PrepareRenameResponse::Success(range) => Some(range),
23257 PrepareRenameResponse::InvalidPosition => None,
23258 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23259 // Fallback on using TreeSitter info to determine identifier range
23260 buffer.read_with(cx, |buffer, _| {
23261 let snapshot = buffer.snapshot();
23262 let (range, kind) = snapshot.surrounding_word(position, None);
23263 if kind != Some(CharKind::Word) {
23264 return None;
23265 }
23266 Some(
23267 snapshot.anchor_before(range.start)
23268 ..snapshot.anchor_after(range.end),
23269 )
23270 })?
23271 }
23272 })
23273 })
23274 }))
23275 }
23276
23277 fn perform_rename(
23278 &self,
23279 buffer: &Entity<Buffer>,
23280 position: text::Anchor,
23281 new_name: String,
23282 cx: &mut App,
23283 ) -> Option<Task<Result<ProjectTransaction>>> {
23284 Some(self.update(cx, |project, cx| {
23285 project.perform_rename(buffer.clone(), position, new_name, cx)
23286 }))
23287 }
23288}
23289
23290fn inlay_hint_settings(
23291 location: Anchor,
23292 snapshot: &MultiBufferSnapshot,
23293 cx: &mut Context<Editor>,
23294) -> InlayHintSettings {
23295 let file = snapshot.file_at(location);
23296 let language = snapshot.language_at(location).map(|l| l.name());
23297 language_settings(language, file, cx).inlay_hints
23298}
23299
23300fn consume_contiguous_rows(
23301 contiguous_row_selections: &mut Vec<Selection<Point>>,
23302 selection: &Selection<Point>,
23303 display_map: &DisplaySnapshot,
23304 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23305) -> (MultiBufferRow, MultiBufferRow) {
23306 contiguous_row_selections.push(selection.clone());
23307 let start_row = starting_row(selection, display_map);
23308 let mut end_row = ending_row(selection, display_map);
23309
23310 while let Some(next_selection) = selections.peek() {
23311 if next_selection.start.row <= end_row.0 {
23312 end_row = ending_row(next_selection, display_map);
23313 contiguous_row_selections.push(selections.next().unwrap().clone());
23314 } else {
23315 break;
23316 }
23317 }
23318 (start_row, end_row)
23319}
23320
23321fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23322 if selection.start.column > 0 {
23323 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23324 } else {
23325 MultiBufferRow(selection.start.row)
23326 }
23327}
23328
23329fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23330 if next_selection.end.column > 0 || next_selection.is_empty() {
23331 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23332 } else {
23333 MultiBufferRow(next_selection.end.row)
23334 }
23335}
23336
23337impl EditorSnapshot {
23338 pub fn remote_selections_in_range<'a>(
23339 &'a self,
23340 range: &'a Range<Anchor>,
23341 collaboration_hub: &dyn CollaborationHub,
23342 cx: &'a App,
23343 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23344 let participant_names = collaboration_hub.user_names(cx);
23345 let participant_indices = collaboration_hub.user_participant_indices(cx);
23346 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23347 let collaborators_by_replica_id = collaborators_by_peer_id
23348 .values()
23349 .map(|collaborator| (collaborator.replica_id, collaborator))
23350 .collect::<HashMap<_, _>>();
23351 self.buffer_snapshot()
23352 .selections_in_range(range, false)
23353 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23354 if replica_id == AGENT_REPLICA_ID {
23355 Some(RemoteSelection {
23356 replica_id,
23357 selection,
23358 cursor_shape,
23359 line_mode,
23360 collaborator_id: CollaboratorId::Agent,
23361 user_name: Some("Agent".into()),
23362 color: cx.theme().players().agent(),
23363 })
23364 } else {
23365 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23366 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23367 let user_name = participant_names.get(&collaborator.user_id).cloned();
23368 Some(RemoteSelection {
23369 replica_id,
23370 selection,
23371 cursor_shape,
23372 line_mode,
23373 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23374 user_name,
23375 color: if let Some(index) = participant_index {
23376 cx.theme().players().color_for_participant(index.0)
23377 } else {
23378 cx.theme().players().absent()
23379 },
23380 })
23381 }
23382 })
23383 }
23384
23385 pub fn hunks_for_ranges(
23386 &self,
23387 ranges: impl IntoIterator<Item = Range<Point>>,
23388 ) -> Vec<MultiBufferDiffHunk> {
23389 let mut hunks = Vec::new();
23390 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23391 HashMap::default();
23392 for query_range in ranges {
23393 let query_rows =
23394 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23395 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23396 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23397 ) {
23398 // Include deleted hunks that are adjacent to the query range, because
23399 // otherwise they would be missed.
23400 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23401 if hunk.status().is_deleted() {
23402 intersects_range |= hunk.row_range.start == query_rows.end;
23403 intersects_range |= hunk.row_range.end == query_rows.start;
23404 }
23405 if intersects_range {
23406 if !processed_buffer_rows
23407 .entry(hunk.buffer_id)
23408 .or_default()
23409 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23410 {
23411 continue;
23412 }
23413 hunks.push(hunk);
23414 }
23415 }
23416 }
23417
23418 hunks
23419 }
23420
23421 fn display_diff_hunks_for_rows<'a>(
23422 &'a self,
23423 display_rows: Range<DisplayRow>,
23424 folded_buffers: &'a HashSet<BufferId>,
23425 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23426 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23427 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23428
23429 self.buffer_snapshot()
23430 .diff_hunks_in_range(buffer_start..buffer_end)
23431 .filter_map(|hunk| {
23432 if folded_buffers.contains(&hunk.buffer_id) {
23433 return None;
23434 }
23435
23436 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23437 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23438
23439 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23440 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23441
23442 let display_hunk = if hunk_display_start.column() != 0 {
23443 DisplayDiffHunk::Folded {
23444 display_row: hunk_display_start.row(),
23445 }
23446 } else {
23447 let mut end_row = hunk_display_end.row();
23448 if hunk_display_end.column() > 0 {
23449 end_row.0 += 1;
23450 }
23451 let is_created_file = hunk.is_created_file();
23452 DisplayDiffHunk::Unfolded {
23453 status: hunk.status(),
23454 diff_base_byte_range: hunk.diff_base_byte_range,
23455 display_row_range: hunk_display_start.row()..end_row,
23456 multi_buffer_range: Anchor::range_in_buffer(
23457 hunk.excerpt_id,
23458 hunk.buffer_id,
23459 hunk.buffer_range,
23460 ),
23461 is_created_file,
23462 }
23463 };
23464
23465 Some(display_hunk)
23466 })
23467 }
23468
23469 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23470 self.display_snapshot
23471 .buffer_snapshot()
23472 .language_at(position)
23473 }
23474
23475 pub fn is_focused(&self) -> bool {
23476 self.is_focused
23477 }
23478
23479 pub fn placeholder_text(&self) -> Option<String> {
23480 self.placeholder_display_snapshot
23481 .as_ref()
23482 .map(|display_map| display_map.text())
23483 }
23484
23485 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23486 self.scroll_anchor.scroll_position(&self.display_snapshot)
23487 }
23488
23489 fn gutter_dimensions(
23490 &self,
23491 font_id: FontId,
23492 font_size: Pixels,
23493 max_line_number_width: Pixels,
23494 cx: &App,
23495 ) -> Option<GutterDimensions> {
23496 if !self.show_gutter {
23497 return None;
23498 }
23499
23500 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23501 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23502
23503 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23504 matches!(
23505 ProjectSettings::get_global(cx).git.git_gutter,
23506 GitGutterSetting::TrackedFiles
23507 )
23508 });
23509 let gutter_settings = EditorSettings::get_global(cx).gutter;
23510 let show_line_numbers = self
23511 .show_line_numbers
23512 .unwrap_or(gutter_settings.line_numbers);
23513 let line_gutter_width = if show_line_numbers {
23514 // Avoid flicker-like gutter resizes when the line number gains another digit by
23515 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23516 let min_width_for_number_on_gutter =
23517 ch_advance * gutter_settings.min_line_number_digits as f32;
23518 max_line_number_width.max(min_width_for_number_on_gutter)
23519 } else {
23520 0.0.into()
23521 };
23522
23523 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23524 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23525
23526 let git_blame_entries_width =
23527 self.git_blame_gutter_max_author_length
23528 .map(|max_author_length| {
23529 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23530 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23531
23532 /// The number of characters to dedicate to gaps and margins.
23533 const SPACING_WIDTH: usize = 4;
23534
23535 let max_char_count = max_author_length.min(renderer.max_author_length())
23536 + ::git::SHORT_SHA_LENGTH
23537 + MAX_RELATIVE_TIMESTAMP.len()
23538 + SPACING_WIDTH;
23539
23540 ch_advance * max_char_count
23541 });
23542
23543 let is_singleton = self.buffer_snapshot().is_singleton();
23544
23545 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23546 left_padding += if !is_singleton {
23547 ch_width * 4.0
23548 } else if show_runnables || show_breakpoints {
23549 ch_width * 3.0
23550 } else if show_git_gutter && show_line_numbers {
23551 ch_width * 2.0
23552 } else if show_git_gutter || show_line_numbers {
23553 ch_width
23554 } else {
23555 px(0.)
23556 };
23557
23558 let shows_folds = is_singleton && gutter_settings.folds;
23559
23560 let right_padding = if shows_folds && show_line_numbers {
23561 ch_width * 4.0
23562 } else if shows_folds || (!is_singleton && show_line_numbers) {
23563 ch_width * 3.0
23564 } else if show_line_numbers {
23565 ch_width
23566 } else {
23567 px(0.)
23568 };
23569
23570 Some(GutterDimensions {
23571 left_padding,
23572 right_padding,
23573 width: line_gutter_width + left_padding + right_padding,
23574 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23575 git_blame_entries_width,
23576 })
23577 }
23578
23579 pub fn render_crease_toggle(
23580 &self,
23581 buffer_row: MultiBufferRow,
23582 row_contains_cursor: bool,
23583 editor: Entity<Editor>,
23584 window: &mut Window,
23585 cx: &mut App,
23586 ) -> Option<AnyElement> {
23587 let folded = self.is_line_folded(buffer_row);
23588 let mut is_foldable = false;
23589
23590 if let Some(crease) = self
23591 .crease_snapshot
23592 .query_row(buffer_row, self.buffer_snapshot())
23593 {
23594 is_foldable = true;
23595 match crease {
23596 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23597 if let Some(render_toggle) = render_toggle {
23598 let toggle_callback =
23599 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23600 if folded {
23601 editor.update(cx, |editor, cx| {
23602 editor.fold_at(buffer_row, window, cx)
23603 });
23604 } else {
23605 editor.update(cx, |editor, cx| {
23606 editor.unfold_at(buffer_row, window, cx)
23607 });
23608 }
23609 });
23610 return Some((render_toggle)(
23611 buffer_row,
23612 folded,
23613 toggle_callback,
23614 window,
23615 cx,
23616 ));
23617 }
23618 }
23619 }
23620 }
23621
23622 is_foldable |= self.starts_indent(buffer_row);
23623
23624 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23625 Some(
23626 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23627 .toggle_state(folded)
23628 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23629 if folded {
23630 this.unfold_at(buffer_row, window, cx);
23631 } else {
23632 this.fold_at(buffer_row, window, cx);
23633 }
23634 }))
23635 .into_any_element(),
23636 )
23637 } else {
23638 None
23639 }
23640 }
23641
23642 pub fn render_crease_trailer(
23643 &self,
23644 buffer_row: MultiBufferRow,
23645 window: &mut Window,
23646 cx: &mut App,
23647 ) -> Option<AnyElement> {
23648 let folded = self.is_line_folded(buffer_row);
23649 if let Crease::Inline { render_trailer, .. } = self
23650 .crease_snapshot
23651 .query_row(buffer_row, self.buffer_snapshot())?
23652 {
23653 let render_trailer = render_trailer.as_ref()?;
23654 Some(render_trailer(buffer_row, folded, window, cx))
23655 } else {
23656 None
23657 }
23658 }
23659}
23660
23661impl Deref for EditorSnapshot {
23662 type Target = DisplaySnapshot;
23663
23664 fn deref(&self) -> &Self::Target {
23665 &self.display_snapshot
23666 }
23667}
23668
23669#[derive(Clone, Debug, PartialEq, Eq)]
23670pub enum EditorEvent {
23671 InputIgnored {
23672 text: Arc<str>,
23673 },
23674 InputHandled {
23675 utf16_range_to_replace: Option<Range<isize>>,
23676 text: Arc<str>,
23677 },
23678 ExcerptsAdded {
23679 buffer: Entity<Buffer>,
23680 predecessor: ExcerptId,
23681 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23682 },
23683 ExcerptsRemoved {
23684 ids: Vec<ExcerptId>,
23685 removed_buffer_ids: Vec<BufferId>,
23686 },
23687 BufferFoldToggled {
23688 ids: Vec<ExcerptId>,
23689 folded: bool,
23690 },
23691 ExcerptsEdited {
23692 ids: Vec<ExcerptId>,
23693 },
23694 ExcerptsExpanded {
23695 ids: Vec<ExcerptId>,
23696 },
23697 BufferEdited,
23698 Edited {
23699 transaction_id: clock::Lamport,
23700 },
23701 Reparsed(BufferId),
23702 Focused,
23703 FocusedIn,
23704 Blurred,
23705 DirtyChanged,
23706 Saved,
23707 TitleChanged,
23708 SelectionsChanged {
23709 local: bool,
23710 },
23711 ScrollPositionChanged {
23712 local: bool,
23713 autoscroll: bool,
23714 },
23715 TransactionUndone {
23716 transaction_id: clock::Lamport,
23717 },
23718 TransactionBegun {
23719 transaction_id: clock::Lamport,
23720 },
23721 CursorShapeChanged,
23722 BreadcrumbsChanged,
23723 PushedToNavHistory {
23724 anchor: Anchor,
23725 is_deactivate: bool,
23726 },
23727}
23728
23729impl EventEmitter<EditorEvent> for Editor {}
23730
23731impl Focusable for Editor {
23732 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23733 self.focus_handle.clone()
23734 }
23735}
23736
23737impl Render for Editor {
23738 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23739 let settings = ThemeSettings::get_global(cx);
23740
23741 let mut text_style = match self.mode {
23742 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23743 color: cx.theme().colors().editor_foreground,
23744 font_family: settings.ui_font.family.clone(),
23745 font_features: settings.ui_font.features.clone(),
23746 font_fallbacks: settings.ui_font.fallbacks.clone(),
23747 font_size: rems(0.875).into(),
23748 font_weight: settings.ui_font.weight,
23749 line_height: relative(settings.buffer_line_height.value()),
23750 ..Default::default()
23751 },
23752 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23753 color: cx.theme().colors().editor_foreground,
23754 font_family: settings.buffer_font.family.clone(),
23755 font_features: settings.buffer_font.features.clone(),
23756 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23757 font_size: settings.buffer_font_size(cx).into(),
23758 font_weight: settings.buffer_font.weight,
23759 line_height: relative(settings.buffer_line_height.value()),
23760 ..Default::default()
23761 },
23762 };
23763 if let Some(text_style_refinement) = &self.text_style_refinement {
23764 text_style.refine(text_style_refinement)
23765 }
23766
23767 let background = match self.mode {
23768 EditorMode::SingleLine => cx.theme().system().transparent,
23769 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23770 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23771 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23772 };
23773
23774 EditorElement::new(
23775 &cx.entity(),
23776 EditorStyle {
23777 background,
23778 border: cx.theme().colors().border,
23779 local_player: cx.theme().players().local(),
23780 text: text_style,
23781 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23782 syntax: cx.theme().syntax().clone(),
23783 status: cx.theme().status().clone(),
23784 inlay_hints_style: make_inlay_hints_style(cx),
23785 edit_prediction_styles: make_suggestion_styles(cx),
23786 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23787 show_underlines: self.diagnostics_enabled(),
23788 },
23789 )
23790 }
23791}
23792
23793impl EntityInputHandler for Editor {
23794 fn text_for_range(
23795 &mut self,
23796 range_utf16: Range<usize>,
23797 adjusted_range: &mut Option<Range<usize>>,
23798 _: &mut Window,
23799 cx: &mut Context<Self>,
23800 ) -> Option<String> {
23801 let snapshot = self.buffer.read(cx).read(cx);
23802 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23803 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23804 if (start.0..end.0) != range_utf16 {
23805 adjusted_range.replace(start.0..end.0);
23806 }
23807 Some(snapshot.text_for_range(start..end).collect())
23808 }
23809
23810 fn selected_text_range(
23811 &mut self,
23812 ignore_disabled_input: bool,
23813 _: &mut Window,
23814 cx: &mut Context<Self>,
23815 ) -> Option<UTF16Selection> {
23816 // Prevent the IME menu from appearing when holding down an alphabetic key
23817 // while input is disabled.
23818 if !ignore_disabled_input && !self.input_enabled {
23819 return None;
23820 }
23821
23822 let selection = self.selections.newest::<OffsetUtf16>(cx);
23823 let range = selection.range();
23824
23825 Some(UTF16Selection {
23826 range: range.start.0..range.end.0,
23827 reversed: selection.reversed,
23828 })
23829 }
23830
23831 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23832 let snapshot = self.buffer.read(cx).read(cx);
23833 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23834 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23835 }
23836
23837 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23838 self.clear_highlights::<InputComposition>(cx);
23839 self.ime_transaction.take();
23840 }
23841
23842 fn replace_text_in_range(
23843 &mut self,
23844 range_utf16: Option<Range<usize>>,
23845 text: &str,
23846 window: &mut Window,
23847 cx: &mut Context<Self>,
23848 ) {
23849 if !self.input_enabled {
23850 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23851 return;
23852 }
23853
23854 self.transact(window, cx, |this, window, cx| {
23855 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23856 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23857 Some(this.selection_replacement_ranges(range_utf16, cx))
23858 } else {
23859 this.marked_text_ranges(cx)
23860 };
23861
23862 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23863 let newest_selection_id = this.selections.newest_anchor().id;
23864 this.selections
23865 .all::<OffsetUtf16>(cx)
23866 .iter()
23867 .zip(ranges_to_replace.iter())
23868 .find_map(|(selection, range)| {
23869 if selection.id == newest_selection_id {
23870 Some(
23871 (range.start.0 as isize - selection.head().0 as isize)
23872 ..(range.end.0 as isize - selection.head().0 as isize),
23873 )
23874 } else {
23875 None
23876 }
23877 })
23878 });
23879
23880 cx.emit(EditorEvent::InputHandled {
23881 utf16_range_to_replace: range_to_replace,
23882 text: text.into(),
23883 });
23884
23885 if let Some(new_selected_ranges) = new_selected_ranges {
23886 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23887 selections.select_ranges(new_selected_ranges)
23888 });
23889 this.backspace(&Default::default(), window, cx);
23890 }
23891
23892 this.handle_input(text, window, cx);
23893 });
23894
23895 if let Some(transaction) = self.ime_transaction {
23896 self.buffer.update(cx, |buffer, cx| {
23897 buffer.group_until_transaction(transaction, cx);
23898 });
23899 }
23900
23901 self.unmark_text(window, cx);
23902 }
23903
23904 fn replace_and_mark_text_in_range(
23905 &mut self,
23906 range_utf16: Option<Range<usize>>,
23907 text: &str,
23908 new_selected_range_utf16: Option<Range<usize>>,
23909 window: &mut Window,
23910 cx: &mut Context<Self>,
23911 ) {
23912 if !self.input_enabled {
23913 return;
23914 }
23915
23916 let transaction = self.transact(window, cx, |this, window, cx| {
23917 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23918 let snapshot = this.buffer.read(cx).read(cx);
23919 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23920 for marked_range in &mut marked_ranges {
23921 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23922 marked_range.start.0 += relative_range_utf16.start;
23923 marked_range.start =
23924 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23925 marked_range.end =
23926 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23927 }
23928 }
23929 Some(marked_ranges)
23930 } else if let Some(range_utf16) = range_utf16 {
23931 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23932 Some(this.selection_replacement_ranges(range_utf16, cx))
23933 } else {
23934 None
23935 };
23936
23937 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23938 let newest_selection_id = this.selections.newest_anchor().id;
23939 this.selections
23940 .all::<OffsetUtf16>(cx)
23941 .iter()
23942 .zip(ranges_to_replace.iter())
23943 .find_map(|(selection, range)| {
23944 if selection.id == newest_selection_id {
23945 Some(
23946 (range.start.0 as isize - selection.head().0 as isize)
23947 ..(range.end.0 as isize - selection.head().0 as isize),
23948 )
23949 } else {
23950 None
23951 }
23952 })
23953 });
23954
23955 cx.emit(EditorEvent::InputHandled {
23956 utf16_range_to_replace: range_to_replace,
23957 text: text.into(),
23958 });
23959
23960 if let Some(ranges) = ranges_to_replace {
23961 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23962 s.select_ranges(ranges)
23963 });
23964 }
23965
23966 let marked_ranges = {
23967 let snapshot = this.buffer.read(cx).read(cx);
23968 this.selections
23969 .disjoint_anchors_arc()
23970 .iter()
23971 .map(|selection| {
23972 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23973 })
23974 .collect::<Vec<_>>()
23975 };
23976
23977 if text.is_empty() {
23978 this.unmark_text(window, cx);
23979 } else {
23980 this.highlight_text::<InputComposition>(
23981 marked_ranges.clone(),
23982 HighlightStyle {
23983 underline: Some(UnderlineStyle {
23984 thickness: px(1.),
23985 color: None,
23986 wavy: false,
23987 }),
23988 ..Default::default()
23989 },
23990 cx,
23991 );
23992 }
23993
23994 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23995 let use_autoclose = this.use_autoclose;
23996 let use_auto_surround = this.use_auto_surround;
23997 this.set_use_autoclose(false);
23998 this.set_use_auto_surround(false);
23999 this.handle_input(text, window, cx);
24000 this.set_use_autoclose(use_autoclose);
24001 this.set_use_auto_surround(use_auto_surround);
24002
24003 if let Some(new_selected_range) = new_selected_range_utf16 {
24004 let snapshot = this.buffer.read(cx).read(cx);
24005 let new_selected_ranges = marked_ranges
24006 .into_iter()
24007 .map(|marked_range| {
24008 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24009 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24010 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24011 snapshot.clip_offset_utf16(new_start, Bias::Left)
24012 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24013 })
24014 .collect::<Vec<_>>();
24015
24016 drop(snapshot);
24017 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24018 selections.select_ranges(new_selected_ranges)
24019 });
24020 }
24021 });
24022
24023 self.ime_transaction = self.ime_transaction.or(transaction);
24024 if let Some(transaction) = self.ime_transaction {
24025 self.buffer.update(cx, |buffer, cx| {
24026 buffer.group_until_transaction(transaction, cx);
24027 });
24028 }
24029
24030 if self.text_highlights::<InputComposition>(cx).is_none() {
24031 self.ime_transaction.take();
24032 }
24033 }
24034
24035 fn bounds_for_range(
24036 &mut self,
24037 range_utf16: Range<usize>,
24038 element_bounds: gpui::Bounds<Pixels>,
24039 window: &mut Window,
24040 cx: &mut Context<Self>,
24041 ) -> Option<gpui::Bounds<Pixels>> {
24042 let text_layout_details = self.text_layout_details(window);
24043 let CharacterDimensions {
24044 em_width,
24045 em_advance,
24046 line_height,
24047 } = self.character_dimensions(window);
24048
24049 let snapshot = self.snapshot(window, cx);
24050 let scroll_position = snapshot.scroll_position();
24051 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24052
24053 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24054 let x = Pixels::from(
24055 ScrollOffset::from(
24056 snapshot.x_for_display_point(start, &text_layout_details)
24057 + self.gutter_dimensions.full_width(),
24058 ) - scroll_left,
24059 );
24060 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24061
24062 Some(Bounds {
24063 origin: element_bounds.origin + point(x, y),
24064 size: size(em_width, line_height),
24065 })
24066 }
24067
24068 fn character_index_for_point(
24069 &mut self,
24070 point: gpui::Point<Pixels>,
24071 _window: &mut Window,
24072 _cx: &mut Context<Self>,
24073 ) -> Option<usize> {
24074 let position_map = self.last_position_map.as_ref()?;
24075 if !position_map.text_hitbox.contains(&point) {
24076 return None;
24077 }
24078 let display_point = position_map.point_for_position(point).previous_valid;
24079 let anchor = position_map
24080 .snapshot
24081 .display_point_to_anchor(display_point, Bias::Left);
24082 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24083 Some(utf16_offset.0)
24084 }
24085}
24086
24087trait SelectionExt {
24088 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24089 fn spanned_rows(
24090 &self,
24091 include_end_if_at_line_start: bool,
24092 map: &DisplaySnapshot,
24093 ) -> Range<MultiBufferRow>;
24094}
24095
24096impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24097 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24098 let start = self
24099 .start
24100 .to_point(map.buffer_snapshot())
24101 .to_display_point(map);
24102 let end = self
24103 .end
24104 .to_point(map.buffer_snapshot())
24105 .to_display_point(map);
24106 if self.reversed {
24107 end..start
24108 } else {
24109 start..end
24110 }
24111 }
24112
24113 fn spanned_rows(
24114 &self,
24115 include_end_if_at_line_start: bool,
24116 map: &DisplaySnapshot,
24117 ) -> Range<MultiBufferRow> {
24118 let start = self.start.to_point(map.buffer_snapshot());
24119 let mut end = self.end.to_point(map.buffer_snapshot());
24120 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24121 end.row -= 1;
24122 }
24123
24124 let buffer_start = map.prev_line_boundary(start).0;
24125 let buffer_end = map.next_line_boundary(end).0;
24126 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24127 }
24128}
24129
24130impl<T: InvalidationRegion> InvalidationStack<T> {
24131 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24132 where
24133 S: Clone + ToOffset,
24134 {
24135 while let Some(region) = self.last() {
24136 let all_selections_inside_invalidation_ranges =
24137 if selections.len() == region.ranges().len() {
24138 selections
24139 .iter()
24140 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24141 .all(|(selection, invalidation_range)| {
24142 let head = selection.head().to_offset(buffer);
24143 invalidation_range.start <= head && invalidation_range.end >= head
24144 })
24145 } else {
24146 false
24147 };
24148
24149 if all_selections_inside_invalidation_ranges {
24150 break;
24151 } else {
24152 self.pop();
24153 }
24154 }
24155 }
24156}
24157
24158impl<T> Default for InvalidationStack<T> {
24159 fn default() -> Self {
24160 Self(Default::default())
24161 }
24162}
24163
24164impl<T> Deref for InvalidationStack<T> {
24165 type Target = Vec<T>;
24166
24167 fn deref(&self) -> &Self::Target {
24168 &self.0
24169 }
24170}
24171
24172impl<T> DerefMut for InvalidationStack<T> {
24173 fn deref_mut(&mut self) -> &mut Self::Target {
24174 &mut self.0
24175 }
24176}
24177
24178impl InvalidationRegion for SnippetState {
24179 fn ranges(&self) -> &[Range<Anchor>] {
24180 &self.ranges[self.active_index]
24181 }
24182}
24183
24184fn edit_prediction_edit_text(
24185 current_snapshot: &BufferSnapshot,
24186 edits: &[(Range<Anchor>, String)],
24187 edit_preview: &EditPreview,
24188 include_deletions: bool,
24189 cx: &App,
24190) -> HighlightedText {
24191 let edits = edits
24192 .iter()
24193 .map(|(anchor, text)| {
24194 (
24195 anchor.start.text_anchor..anchor.end.text_anchor,
24196 text.clone(),
24197 )
24198 })
24199 .collect::<Vec<_>>();
24200
24201 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24202}
24203
24204fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24205 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24206 // Just show the raw edit text with basic styling
24207 let mut text = String::new();
24208 let mut highlights = Vec::new();
24209
24210 let insertion_highlight_style = HighlightStyle {
24211 color: Some(cx.theme().colors().text),
24212 ..Default::default()
24213 };
24214
24215 for (_, edit_text) in edits {
24216 let start_offset = text.len();
24217 text.push_str(edit_text);
24218 let end_offset = text.len();
24219
24220 if start_offset < end_offset {
24221 highlights.push((start_offset..end_offset, insertion_highlight_style));
24222 }
24223 }
24224
24225 HighlightedText {
24226 text: text.into(),
24227 highlights,
24228 }
24229}
24230
24231pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24232 match severity {
24233 lsp::DiagnosticSeverity::ERROR => colors.error,
24234 lsp::DiagnosticSeverity::WARNING => colors.warning,
24235 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24236 lsp::DiagnosticSeverity::HINT => colors.info,
24237 _ => colors.ignored,
24238 }
24239}
24240
24241pub fn styled_runs_for_code_label<'a>(
24242 label: &'a CodeLabel,
24243 syntax_theme: &'a theme::SyntaxTheme,
24244) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24245 let fade_out = HighlightStyle {
24246 fade_out: Some(0.35),
24247 ..Default::default()
24248 };
24249
24250 let mut prev_end = label.filter_range.end;
24251 label
24252 .runs
24253 .iter()
24254 .enumerate()
24255 .flat_map(move |(ix, (range, highlight_id))| {
24256 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24257 style
24258 } else {
24259 return Default::default();
24260 };
24261 let muted_style = style.highlight(fade_out);
24262
24263 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24264 if range.start >= label.filter_range.end {
24265 if range.start > prev_end {
24266 runs.push((prev_end..range.start, fade_out));
24267 }
24268 runs.push((range.clone(), muted_style));
24269 } else if range.end <= label.filter_range.end {
24270 runs.push((range.clone(), style));
24271 } else {
24272 runs.push((range.start..label.filter_range.end, style));
24273 runs.push((label.filter_range.end..range.end, muted_style));
24274 }
24275 prev_end = cmp::max(prev_end, range.end);
24276
24277 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24278 runs.push((prev_end..label.text.len(), fade_out));
24279 }
24280
24281 runs
24282 })
24283}
24284
24285pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24286 let mut prev_index = 0;
24287 let mut prev_codepoint: Option<char> = None;
24288 text.char_indices()
24289 .chain([(text.len(), '\0')])
24290 .filter_map(move |(index, codepoint)| {
24291 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24292 let is_boundary = index == text.len()
24293 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24294 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24295 if is_boundary {
24296 let chunk = &text[prev_index..index];
24297 prev_index = index;
24298 Some(chunk)
24299 } else {
24300 None
24301 }
24302 })
24303}
24304
24305pub trait RangeToAnchorExt: Sized {
24306 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24307
24308 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24309 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24310 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24311 }
24312}
24313
24314impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24315 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24316 let start_offset = self.start.to_offset(snapshot);
24317 let end_offset = self.end.to_offset(snapshot);
24318 if start_offset == end_offset {
24319 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24320 } else {
24321 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24322 }
24323 }
24324}
24325
24326pub trait RowExt {
24327 fn as_f64(&self) -> f64;
24328
24329 fn next_row(&self) -> Self;
24330
24331 fn previous_row(&self) -> Self;
24332
24333 fn minus(&self, other: Self) -> u32;
24334}
24335
24336impl RowExt for DisplayRow {
24337 fn as_f64(&self) -> f64 {
24338 self.0 as _
24339 }
24340
24341 fn next_row(&self) -> Self {
24342 Self(self.0 + 1)
24343 }
24344
24345 fn previous_row(&self) -> Self {
24346 Self(self.0.saturating_sub(1))
24347 }
24348
24349 fn minus(&self, other: Self) -> u32 {
24350 self.0 - other.0
24351 }
24352}
24353
24354impl RowExt for MultiBufferRow {
24355 fn as_f64(&self) -> f64 {
24356 self.0 as _
24357 }
24358
24359 fn next_row(&self) -> Self {
24360 Self(self.0 + 1)
24361 }
24362
24363 fn previous_row(&self) -> Self {
24364 Self(self.0.saturating_sub(1))
24365 }
24366
24367 fn minus(&self, other: Self) -> u32 {
24368 self.0 - other.0
24369 }
24370}
24371
24372trait RowRangeExt {
24373 type Row;
24374
24375 fn len(&self) -> usize;
24376
24377 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24378}
24379
24380impl RowRangeExt for Range<MultiBufferRow> {
24381 type Row = MultiBufferRow;
24382
24383 fn len(&self) -> usize {
24384 (self.end.0 - self.start.0) as usize
24385 }
24386
24387 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24388 (self.start.0..self.end.0).map(MultiBufferRow)
24389 }
24390}
24391
24392impl RowRangeExt for Range<DisplayRow> {
24393 type Row = DisplayRow;
24394
24395 fn len(&self) -> usize {
24396 (self.end.0 - self.start.0) as usize
24397 }
24398
24399 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24400 (self.start.0..self.end.0).map(DisplayRow)
24401 }
24402}
24403
24404/// If select range has more than one line, we
24405/// just point the cursor to range.start.
24406fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24407 if range.start.row == range.end.row {
24408 range
24409 } else {
24410 range.start..range.start
24411 }
24412}
24413pub struct KillRing(ClipboardItem);
24414impl Global for KillRing {}
24415
24416const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24417
24418enum BreakpointPromptEditAction {
24419 Log,
24420 Condition,
24421 HitCondition,
24422}
24423
24424struct BreakpointPromptEditor {
24425 pub(crate) prompt: Entity<Editor>,
24426 editor: WeakEntity<Editor>,
24427 breakpoint_anchor: Anchor,
24428 breakpoint: Breakpoint,
24429 edit_action: BreakpointPromptEditAction,
24430 block_ids: HashSet<CustomBlockId>,
24431 editor_margins: Arc<Mutex<EditorMargins>>,
24432 _subscriptions: Vec<Subscription>,
24433}
24434
24435impl BreakpointPromptEditor {
24436 const MAX_LINES: u8 = 4;
24437
24438 fn new(
24439 editor: WeakEntity<Editor>,
24440 breakpoint_anchor: Anchor,
24441 breakpoint: Breakpoint,
24442 edit_action: BreakpointPromptEditAction,
24443 window: &mut Window,
24444 cx: &mut Context<Self>,
24445 ) -> Self {
24446 let base_text = match edit_action {
24447 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24448 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24449 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24450 }
24451 .map(|msg| msg.to_string())
24452 .unwrap_or_default();
24453
24454 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24455 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24456
24457 let prompt = cx.new(|cx| {
24458 let mut prompt = Editor::new(
24459 EditorMode::AutoHeight {
24460 min_lines: 1,
24461 max_lines: Some(Self::MAX_LINES as usize),
24462 },
24463 buffer,
24464 None,
24465 window,
24466 cx,
24467 );
24468 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24469 prompt.set_show_cursor_when_unfocused(false, cx);
24470 prompt.set_placeholder_text(
24471 match edit_action {
24472 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24473 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24474 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24475 },
24476 window,
24477 cx,
24478 );
24479
24480 prompt
24481 });
24482
24483 Self {
24484 prompt,
24485 editor,
24486 breakpoint_anchor,
24487 breakpoint,
24488 edit_action,
24489 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24490 block_ids: Default::default(),
24491 _subscriptions: vec![],
24492 }
24493 }
24494
24495 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24496 self.block_ids.extend(block_ids)
24497 }
24498
24499 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24500 if let Some(editor) = self.editor.upgrade() {
24501 let message = self
24502 .prompt
24503 .read(cx)
24504 .buffer
24505 .read(cx)
24506 .as_singleton()
24507 .expect("A multi buffer in breakpoint prompt isn't possible")
24508 .read(cx)
24509 .as_rope()
24510 .to_string();
24511
24512 editor.update(cx, |editor, cx| {
24513 editor.edit_breakpoint_at_anchor(
24514 self.breakpoint_anchor,
24515 self.breakpoint.clone(),
24516 match self.edit_action {
24517 BreakpointPromptEditAction::Log => {
24518 BreakpointEditAction::EditLogMessage(message.into())
24519 }
24520 BreakpointPromptEditAction::Condition => {
24521 BreakpointEditAction::EditCondition(message.into())
24522 }
24523 BreakpointPromptEditAction::HitCondition => {
24524 BreakpointEditAction::EditHitCondition(message.into())
24525 }
24526 },
24527 cx,
24528 );
24529
24530 editor.remove_blocks(self.block_ids.clone(), None, cx);
24531 cx.focus_self(window);
24532 });
24533 }
24534 }
24535
24536 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24537 self.editor
24538 .update(cx, |editor, cx| {
24539 editor.remove_blocks(self.block_ids.clone(), None, cx);
24540 window.focus(&editor.focus_handle);
24541 })
24542 .log_err();
24543 }
24544
24545 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24546 let settings = ThemeSettings::get_global(cx);
24547 let text_style = TextStyle {
24548 color: if self.prompt.read(cx).read_only(cx) {
24549 cx.theme().colors().text_disabled
24550 } else {
24551 cx.theme().colors().text
24552 },
24553 font_family: settings.buffer_font.family.clone(),
24554 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24555 font_size: settings.buffer_font_size(cx).into(),
24556 font_weight: settings.buffer_font.weight,
24557 line_height: relative(settings.buffer_line_height.value()),
24558 ..Default::default()
24559 };
24560 EditorElement::new(
24561 &self.prompt,
24562 EditorStyle {
24563 background: cx.theme().colors().editor_background,
24564 local_player: cx.theme().players().local(),
24565 text: text_style,
24566 ..Default::default()
24567 },
24568 )
24569 }
24570}
24571
24572impl Render for BreakpointPromptEditor {
24573 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24574 let editor_margins = *self.editor_margins.lock();
24575 let gutter_dimensions = editor_margins.gutter;
24576 h_flex()
24577 .key_context("Editor")
24578 .bg(cx.theme().colors().editor_background)
24579 .border_y_1()
24580 .border_color(cx.theme().status().info_border)
24581 .size_full()
24582 .py(window.line_height() / 2.5)
24583 .on_action(cx.listener(Self::confirm))
24584 .on_action(cx.listener(Self::cancel))
24585 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24586 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24587 }
24588}
24589
24590impl Focusable for BreakpointPromptEditor {
24591 fn focus_handle(&self, cx: &App) -> FocusHandle {
24592 self.prompt.focus_handle(cx)
24593 }
24594}
24595
24596fn all_edits_insertions_or_deletions(
24597 edits: &Vec<(Range<Anchor>, String)>,
24598 snapshot: &MultiBufferSnapshot,
24599) -> bool {
24600 let mut all_insertions = true;
24601 let mut all_deletions = true;
24602
24603 for (range, new_text) in edits.iter() {
24604 let range_is_empty = range.to_offset(snapshot).is_empty();
24605 let text_is_empty = new_text.is_empty();
24606
24607 if range_is_empty != text_is_empty {
24608 if range_is_empty {
24609 all_deletions = false;
24610 } else {
24611 all_insertions = false;
24612 }
24613 } else {
24614 return false;
24615 }
24616
24617 if !all_insertions && !all_deletions {
24618 return false;
24619 }
24620 }
24621 all_insertions || all_deletions
24622}
24623
24624struct MissingEditPredictionKeybindingTooltip;
24625
24626impl Render for MissingEditPredictionKeybindingTooltip {
24627 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24628 ui::tooltip_container(cx, |container, cx| {
24629 container
24630 .flex_shrink_0()
24631 .max_w_80()
24632 .min_h(rems_from_px(124.))
24633 .justify_between()
24634 .child(
24635 v_flex()
24636 .flex_1()
24637 .text_ui_sm(cx)
24638 .child(Label::new("Conflict with Accept Keybinding"))
24639 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24640 )
24641 .child(
24642 h_flex()
24643 .pb_1()
24644 .gap_1()
24645 .items_end()
24646 .w_full()
24647 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24648 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24649 }))
24650 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24651 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24652 })),
24653 )
24654 })
24655 }
24656}
24657
24658#[derive(Debug, Clone, Copy, PartialEq)]
24659pub struct LineHighlight {
24660 pub background: Background,
24661 pub border: Option<gpui::Hsla>,
24662 pub include_gutter: bool,
24663 pub type_id: Option<TypeId>,
24664}
24665
24666struct LineManipulationResult {
24667 pub new_text: String,
24668 pub line_count_before: usize,
24669 pub line_count_after: usize,
24670}
24671
24672fn render_diff_hunk_controls(
24673 row: u32,
24674 status: &DiffHunkStatus,
24675 hunk_range: Range<Anchor>,
24676 is_created_file: bool,
24677 line_height: Pixels,
24678 editor: &Entity<Editor>,
24679 _window: &mut Window,
24680 cx: &mut App,
24681) -> AnyElement {
24682 h_flex()
24683 .h(line_height)
24684 .mr_1()
24685 .gap_1()
24686 .px_0p5()
24687 .pb_1()
24688 .border_x_1()
24689 .border_b_1()
24690 .border_color(cx.theme().colors().border_variant)
24691 .rounded_b_lg()
24692 .bg(cx.theme().colors().editor_background)
24693 .gap_1()
24694 .block_mouse_except_scroll()
24695 .shadow_md()
24696 .child(if status.has_secondary_hunk() {
24697 Button::new(("stage", row as u64), "Stage")
24698 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24699 .tooltip({
24700 let focus_handle = editor.focus_handle(cx);
24701 move |window, cx| {
24702 Tooltip::for_action_in(
24703 "Stage Hunk",
24704 &::git::ToggleStaged,
24705 &focus_handle,
24706 window,
24707 cx,
24708 )
24709 }
24710 })
24711 .on_click({
24712 let editor = editor.clone();
24713 move |_event, _window, cx| {
24714 editor.update(cx, |editor, cx| {
24715 editor.stage_or_unstage_diff_hunks(
24716 true,
24717 vec![hunk_range.start..hunk_range.start],
24718 cx,
24719 );
24720 });
24721 }
24722 })
24723 } else {
24724 Button::new(("unstage", row as u64), "Unstage")
24725 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24726 .tooltip({
24727 let focus_handle = editor.focus_handle(cx);
24728 move |window, cx| {
24729 Tooltip::for_action_in(
24730 "Unstage Hunk",
24731 &::git::ToggleStaged,
24732 &focus_handle,
24733 window,
24734 cx,
24735 )
24736 }
24737 })
24738 .on_click({
24739 let editor = editor.clone();
24740 move |_event, _window, cx| {
24741 editor.update(cx, |editor, cx| {
24742 editor.stage_or_unstage_diff_hunks(
24743 false,
24744 vec![hunk_range.start..hunk_range.start],
24745 cx,
24746 );
24747 });
24748 }
24749 })
24750 })
24751 .child(
24752 Button::new(("restore", row as u64), "Restore")
24753 .tooltip({
24754 let focus_handle = editor.focus_handle(cx);
24755 move |window, cx| {
24756 Tooltip::for_action_in(
24757 "Restore Hunk",
24758 &::git::Restore,
24759 &focus_handle,
24760 window,
24761 cx,
24762 )
24763 }
24764 })
24765 .on_click({
24766 let editor = editor.clone();
24767 move |_event, window, cx| {
24768 editor.update(cx, |editor, cx| {
24769 let snapshot = editor.snapshot(window, cx);
24770 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24771 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24772 });
24773 }
24774 })
24775 .disabled(is_created_file),
24776 )
24777 .when(
24778 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24779 |el| {
24780 el.child(
24781 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24782 .shape(IconButtonShape::Square)
24783 .icon_size(IconSize::Small)
24784 // .disabled(!has_multiple_hunks)
24785 .tooltip({
24786 let focus_handle = editor.focus_handle(cx);
24787 move |window, cx| {
24788 Tooltip::for_action_in(
24789 "Next Hunk",
24790 &GoToHunk,
24791 &focus_handle,
24792 window,
24793 cx,
24794 )
24795 }
24796 })
24797 .on_click({
24798 let editor = editor.clone();
24799 move |_event, window, cx| {
24800 editor.update(cx, |editor, cx| {
24801 let snapshot = editor.snapshot(window, cx);
24802 let position =
24803 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24804 editor.go_to_hunk_before_or_after_position(
24805 &snapshot,
24806 position,
24807 Direction::Next,
24808 window,
24809 cx,
24810 );
24811 editor.expand_selected_diff_hunks(cx);
24812 });
24813 }
24814 }),
24815 )
24816 .child(
24817 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24818 .shape(IconButtonShape::Square)
24819 .icon_size(IconSize::Small)
24820 // .disabled(!has_multiple_hunks)
24821 .tooltip({
24822 let focus_handle = editor.focus_handle(cx);
24823 move |window, cx| {
24824 Tooltip::for_action_in(
24825 "Previous Hunk",
24826 &GoToPreviousHunk,
24827 &focus_handle,
24828 window,
24829 cx,
24830 )
24831 }
24832 })
24833 .on_click({
24834 let editor = editor.clone();
24835 move |_event, window, cx| {
24836 editor.update(cx, |editor, cx| {
24837 let snapshot = editor.snapshot(window, cx);
24838 let point =
24839 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24840 editor.go_to_hunk_before_or_after_position(
24841 &snapshot,
24842 point,
24843 Direction::Prev,
24844 window,
24845 cx,
24846 );
24847 editor.expand_selected_diff_hunks(cx);
24848 });
24849 }
24850 }),
24851 )
24852 },
24853 )
24854 .into_any_element()
24855}
24856
24857pub fn multibuffer_context_lines(cx: &App) -> u32 {
24858 EditorSettings::try_get(cx)
24859 .map(|settings| settings.excerpt_context_lines)
24860 .unwrap_or(2)
24861 .min(32)
24862}