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, window, 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(cx)?)?;
5347 if worktree_entry.is_ignored {
5348 return None;
5349 }
5350
5351 let language = buffer.language()?;
5352 if let Some(restrict_to_languages) = restrict_to_languages
5353 && !restrict_to_languages.contains(language)
5354 {
5355 return None;
5356 }
5357 Some((
5358 excerpt_id,
5359 (
5360 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5361 buffer.version().clone(),
5362 excerpt_visible_range,
5363 ),
5364 ))
5365 })
5366 .collect()
5367 }
5368
5369 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5370 TextLayoutDetails {
5371 text_system: window.text_system().clone(),
5372 editor_style: self.style.clone().unwrap(),
5373 rem_size: window.rem_size(),
5374 scroll_anchor: self.scroll_manager.anchor(),
5375 visible_rows: self.visible_line_count(),
5376 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5377 }
5378 }
5379
5380 pub fn splice_inlays(
5381 &self,
5382 to_remove: &[InlayId],
5383 to_insert: Vec<Inlay>,
5384 cx: &mut Context<Self>,
5385 ) {
5386 self.display_map.update(cx, |display_map, cx| {
5387 display_map.splice_inlays(to_remove, to_insert, cx)
5388 });
5389 cx.notify();
5390 }
5391
5392 fn trigger_on_type_formatting(
5393 &self,
5394 input: String,
5395 window: &mut Window,
5396 cx: &mut Context<Self>,
5397 ) -> Option<Task<Result<()>>> {
5398 if input.len() != 1 {
5399 return None;
5400 }
5401
5402 let project = self.project()?;
5403 let position = self.selections.newest_anchor().head();
5404 let (buffer, buffer_position) = self
5405 .buffer
5406 .read(cx)
5407 .text_anchor_for_position(position, cx)?;
5408
5409 let settings = language_settings::language_settings(
5410 buffer
5411 .read(cx)
5412 .language_at(buffer_position)
5413 .map(|l| l.name()),
5414 buffer.read(cx).file(),
5415 cx,
5416 );
5417 if !settings.use_on_type_format {
5418 return None;
5419 }
5420
5421 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5422 // hence we do LSP request & edit on host side only — add formats to host's history.
5423 let push_to_lsp_host_history = true;
5424 // If this is not the host, append its history with new edits.
5425 let push_to_client_history = project.read(cx).is_via_collab();
5426
5427 let on_type_formatting = project.update(cx, |project, cx| {
5428 project.on_type_format(
5429 buffer.clone(),
5430 buffer_position,
5431 input,
5432 push_to_lsp_host_history,
5433 cx,
5434 )
5435 });
5436 Some(cx.spawn_in(window, async move |editor, cx| {
5437 if let Some(transaction) = on_type_formatting.await? {
5438 if push_to_client_history {
5439 buffer
5440 .update(cx, |buffer, _| {
5441 buffer.push_transaction(transaction, Instant::now());
5442 buffer.finalize_last_transaction();
5443 })
5444 .ok();
5445 }
5446 editor.update(cx, |editor, cx| {
5447 editor.refresh_document_highlights(cx);
5448 })?;
5449 }
5450 Ok(())
5451 }))
5452 }
5453
5454 pub fn show_word_completions(
5455 &mut self,
5456 _: &ShowWordCompletions,
5457 window: &mut Window,
5458 cx: &mut Context<Self>,
5459 ) {
5460 self.open_or_update_completions_menu(
5461 Some(CompletionsMenuSource::Words {
5462 ignore_threshold: true,
5463 }),
5464 None,
5465 window,
5466 cx,
5467 );
5468 }
5469
5470 pub fn show_completions(
5471 &mut self,
5472 options: &ShowCompletions,
5473 window: &mut Window,
5474 cx: &mut Context<Self>,
5475 ) {
5476 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5477 }
5478
5479 fn open_or_update_completions_menu(
5480 &mut self,
5481 requested_source: Option<CompletionsMenuSource>,
5482 trigger: Option<&str>,
5483 window: &mut Window,
5484 cx: &mut Context<Self>,
5485 ) {
5486 if self.pending_rename.is_some() {
5487 return;
5488 }
5489
5490 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5491
5492 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5493 // inserted and selected. To handle that case, the start of the selection is used so that
5494 // the menu starts with all choices.
5495 let position = self
5496 .selections
5497 .newest_anchor()
5498 .start
5499 .bias_right(&multibuffer_snapshot);
5500 if position.diff_base_anchor.is_some() {
5501 return;
5502 }
5503 let buffer_position = multibuffer_snapshot.anchor_before(position);
5504 let Some(buffer) = buffer_position
5505 .buffer_id
5506 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5507 else {
5508 return;
5509 };
5510 let buffer_snapshot = buffer.read(cx).snapshot();
5511
5512 let query: Option<Arc<String>> =
5513 Self::completion_query(&multibuffer_snapshot, buffer_position)
5514 .map(|query| query.into());
5515
5516 drop(multibuffer_snapshot);
5517
5518 // Hide the current completions menu when query is empty. Without this, cached
5519 // completions from before the trigger char may be reused (#32774).
5520 if query.is_none() {
5521 let menu_is_open = matches!(
5522 self.context_menu.borrow().as_ref(),
5523 Some(CodeContextMenu::Completions(_))
5524 );
5525 if menu_is_open {
5526 self.hide_context_menu(window, cx);
5527 }
5528 }
5529
5530 let mut ignore_word_threshold = false;
5531 let provider = match requested_source {
5532 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5533 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5534 ignore_word_threshold = ignore_threshold;
5535 None
5536 }
5537 Some(CompletionsMenuSource::SnippetChoices) => {
5538 log::error!("bug: SnippetChoices requested_source is not handled");
5539 None
5540 }
5541 };
5542
5543 let sort_completions = provider
5544 .as_ref()
5545 .is_some_and(|provider| provider.sort_completions());
5546
5547 let filter_completions = provider
5548 .as_ref()
5549 .is_none_or(|provider| provider.filter_completions());
5550
5551 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5552 if filter_completions {
5553 menu.filter(query.clone(), provider.clone(), window, cx);
5554 }
5555 // When `is_incomplete` is false, no need to re-query completions when the current query
5556 // is a suffix of the initial query.
5557 if !menu.is_incomplete {
5558 // If the new query is a suffix of the old query (typing more characters) and
5559 // the previous result was complete, the existing completions can be filtered.
5560 //
5561 // Note that this is always true for snippet completions.
5562 let query_matches = match (&menu.initial_query, &query) {
5563 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5564 (None, _) => true,
5565 _ => false,
5566 };
5567 if query_matches {
5568 let position_matches = if menu.initial_position == position {
5569 true
5570 } else {
5571 let snapshot = self.buffer.read(cx).read(cx);
5572 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5573 };
5574 if position_matches {
5575 return;
5576 }
5577 }
5578 }
5579 };
5580
5581 let trigger_kind = match trigger {
5582 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5583 CompletionTriggerKind::TRIGGER_CHARACTER
5584 }
5585 _ => CompletionTriggerKind::INVOKED,
5586 };
5587 let completion_context = CompletionContext {
5588 trigger_character: trigger.and_then(|trigger| {
5589 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5590 Some(String::from(trigger))
5591 } else {
5592 None
5593 }
5594 }),
5595 trigger_kind,
5596 };
5597
5598 let Anchor {
5599 excerpt_id: buffer_excerpt_id,
5600 text_anchor: buffer_position,
5601 ..
5602 } = buffer_position;
5603
5604 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5605 buffer_snapshot.surrounding_word(buffer_position, None)
5606 {
5607 let word_to_exclude = buffer_snapshot
5608 .text_for_range(word_range.clone())
5609 .collect::<String>();
5610 (
5611 buffer_snapshot.anchor_before(word_range.start)
5612 ..buffer_snapshot.anchor_after(buffer_position),
5613 Some(word_to_exclude),
5614 )
5615 } else {
5616 (buffer_position..buffer_position, None)
5617 };
5618
5619 let language = buffer_snapshot
5620 .language_at(buffer_position)
5621 .map(|language| language.name());
5622
5623 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5624 .completions
5625 .clone();
5626
5627 let show_completion_documentation = buffer_snapshot
5628 .settings_at(buffer_position, cx)
5629 .show_completion_documentation;
5630
5631 // The document can be large, so stay in reasonable bounds when searching for words,
5632 // otherwise completion pop-up might be slow to appear.
5633 const WORD_LOOKUP_ROWS: u32 = 5_000;
5634 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5635 let min_word_search = buffer_snapshot.clip_point(
5636 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5637 Bias::Left,
5638 );
5639 let max_word_search = buffer_snapshot.clip_point(
5640 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5641 Bias::Right,
5642 );
5643 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5644 ..buffer_snapshot.point_to_offset(max_word_search);
5645
5646 let skip_digits = query
5647 .as_ref()
5648 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5649
5650 let omit_word_completions = !self.word_completions_enabled
5651 || (!ignore_word_threshold
5652 && match &query {
5653 Some(query) => query.chars().count() < completion_settings.words_min_length,
5654 None => completion_settings.words_min_length != 0,
5655 });
5656
5657 let (mut words, provider_responses) = match &provider {
5658 Some(provider) => {
5659 let provider_responses = provider.completions(
5660 buffer_excerpt_id,
5661 &buffer,
5662 buffer_position,
5663 completion_context,
5664 window,
5665 cx,
5666 );
5667
5668 let words = match (omit_word_completions, completion_settings.words) {
5669 (true, _) | (_, WordsCompletionMode::Disabled) => {
5670 Task::ready(BTreeMap::default())
5671 }
5672 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5673 .background_spawn(async move {
5674 buffer_snapshot.words_in_range(WordsQuery {
5675 fuzzy_contents: None,
5676 range: word_search_range,
5677 skip_digits,
5678 })
5679 }),
5680 };
5681
5682 (words, provider_responses)
5683 }
5684 None => {
5685 let words = if omit_word_completions {
5686 Task::ready(BTreeMap::default())
5687 } else {
5688 cx.background_spawn(async move {
5689 buffer_snapshot.words_in_range(WordsQuery {
5690 fuzzy_contents: None,
5691 range: word_search_range,
5692 skip_digits,
5693 })
5694 })
5695 };
5696 (words, Task::ready(Ok(Vec::new())))
5697 }
5698 };
5699
5700 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5701
5702 let id = post_inc(&mut self.next_completion_id);
5703 let task = cx.spawn_in(window, async move |editor, cx| {
5704 let Ok(()) = editor.update(cx, |this, _| {
5705 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5706 }) else {
5707 return;
5708 };
5709
5710 // TODO: Ideally completions from different sources would be selectively re-queried, so
5711 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5712 let mut completions = Vec::new();
5713 let mut is_incomplete = false;
5714 let mut display_options: Option<CompletionDisplayOptions> = None;
5715 if let Some(provider_responses) = provider_responses.await.log_err()
5716 && !provider_responses.is_empty()
5717 {
5718 for response in provider_responses {
5719 completions.extend(response.completions);
5720 is_incomplete = is_incomplete || response.is_incomplete;
5721 match display_options.as_mut() {
5722 None => {
5723 display_options = Some(response.display_options);
5724 }
5725 Some(options) => options.merge(&response.display_options),
5726 }
5727 }
5728 if completion_settings.words == WordsCompletionMode::Fallback {
5729 words = Task::ready(BTreeMap::default());
5730 }
5731 }
5732 let display_options = display_options.unwrap_or_default();
5733
5734 let mut words = words.await;
5735 if let Some(word_to_exclude) = &word_to_exclude {
5736 words.remove(word_to_exclude);
5737 }
5738 for lsp_completion in &completions {
5739 words.remove(&lsp_completion.new_text);
5740 }
5741 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5742 replace_range: word_replace_range.clone(),
5743 new_text: word.clone(),
5744 label: CodeLabel::plain(word, None),
5745 icon_path: None,
5746 documentation: None,
5747 source: CompletionSource::BufferWord {
5748 word_range,
5749 resolved: false,
5750 },
5751 insert_text_mode: Some(InsertTextMode::AS_IS),
5752 confirm: None,
5753 }));
5754
5755 let menu = if completions.is_empty() {
5756 None
5757 } else {
5758 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5759 let languages = editor
5760 .workspace
5761 .as_ref()
5762 .and_then(|(workspace, _)| workspace.upgrade())
5763 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5764 let menu = CompletionsMenu::new(
5765 id,
5766 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5767 sort_completions,
5768 show_completion_documentation,
5769 position,
5770 query.clone(),
5771 is_incomplete,
5772 buffer.clone(),
5773 completions.into(),
5774 display_options,
5775 snippet_sort_order,
5776 languages,
5777 language,
5778 cx,
5779 );
5780
5781 let query = if filter_completions { query } else { None };
5782 let matches_task = if let Some(query) = query {
5783 menu.do_async_filtering(query, cx)
5784 } else {
5785 Task::ready(menu.unfiltered_matches())
5786 };
5787 (menu, matches_task)
5788 }) else {
5789 return;
5790 };
5791
5792 let matches = matches_task.await;
5793
5794 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5795 // Newer menu already set, so exit.
5796 if let Some(CodeContextMenu::Completions(prev_menu)) =
5797 editor.context_menu.borrow().as_ref()
5798 && prev_menu.id > id
5799 {
5800 return;
5801 };
5802
5803 // Only valid to take prev_menu because it the new menu is immediately set
5804 // below, or the menu is hidden.
5805 if let Some(CodeContextMenu::Completions(prev_menu)) =
5806 editor.context_menu.borrow_mut().take()
5807 {
5808 let position_matches =
5809 if prev_menu.initial_position == menu.initial_position {
5810 true
5811 } else {
5812 let snapshot = editor.buffer.read(cx).read(cx);
5813 prev_menu.initial_position.to_offset(&snapshot)
5814 == menu.initial_position.to_offset(&snapshot)
5815 };
5816 if position_matches {
5817 // Preserve markdown cache before `set_filter_results` because it will
5818 // try to populate the documentation cache.
5819 menu.preserve_markdown_cache(prev_menu);
5820 }
5821 };
5822
5823 menu.set_filter_results(matches, provider, window, cx);
5824 }) else {
5825 return;
5826 };
5827
5828 menu.visible().then_some(menu)
5829 };
5830
5831 editor
5832 .update_in(cx, |editor, window, cx| {
5833 if editor.focus_handle.is_focused(window)
5834 && let Some(menu) = menu
5835 {
5836 *editor.context_menu.borrow_mut() =
5837 Some(CodeContextMenu::Completions(menu));
5838
5839 crate::hover_popover::hide_hover(editor, cx);
5840 if editor.show_edit_predictions_in_menu() {
5841 editor.update_visible_edit_prediction(window, cx);
5842 } else {
5843 editor.discard_edit_prediction(false, cx);
5844 }
5845
5846 cx.notify();
5847 return;
5848 }
5849
5850 if editor.completion_tasks.len() <= 1 {
5851 // If there are no more completion tasks and the last menu was empty, we should hide it.
5852 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5853 // If it was already hidden and we don't show edit predictions in the menu,
5854 // we should also show the edit prediction when available.
5855 if was_hidden && editor.show_edit_predictions_in_menu() {
5856 editor.update_visible_edit_prediction(window, cx);
5857 }
5858 }
5859 })
5860 .ok();
5861 });
5862
5863 self.completion_tasks.push((id, task));
5864 }
5865
5866 #[cfg(feature = "test-support")]
5867 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5868 let menu = self.context_menu.borrow();
5869 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5870 let completions = menu.completions.borrow();
5871 Some(completions.to_vec())
5872 } else {
5873 None
5874 }
5875 }
5876
5877 pub fn with_completions_menu_matching_id<R>(
5878 &self,
5879 id: CompletionId,
5880 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5881 ) -> R {
5882 let mut context_menu = self.context_menu.borrow_mut();
5883 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5884 return f(None);
5885 };
5886 if completions_menu.id != id {
5887 return f(None);
5888 }
5889 f(Some(completions_menu))
5890 }
5891
5892 pub fn confirm_completion(
5893 &mut self,
5894 action: &ConfirmCompletion,
5895 window: &mut Window,
5896 cx: &mut Context<Self>,
5897 ) -> Option<Task<Result<()>>> {
5898 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5899 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5900 }
5901
5902 pub fn confirm_completion_insert(
5903 &mut self,
5904 _: &ConfirmCompletionInsert,
5905 window: &mut Window,
5906 cx: &mut Context<Self>,
5907 ) -> Option<Task<Result<()>>> {
5908 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5909 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5910 }
5911
5912 pub fn confirm_completion_replace(
5913 &mut self,
5914 _: &ConfirmCompletionReplace,
5915 window: &mut Window,
5916 cx: &mut Context<Self>,
5917 ) -> Option<Task<Result<()>>> {
5918 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5919 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5920 }
5921
5922 pub fn compose_completion(
5923 &mut self,
5924 action: &ComposeCompletion,
5925 window: &mut Window,
5926 cx: &mut Context<Self>,
5927 ) -> Option<Task<Result<()>>> {
5928 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5929 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5930 }
5931
5932 fn do_completion(
5933 &mut self,
5934 item_ix: Option<usize>,
5935 intent: CompletionIntent,
5936 window: &mut Window,
5937 cx: &mut Context<Editor>,
5938 ) -> Option<Task<Result<()>>> {
5939 use language::ToOffset as _;
5940
5941 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5942 else {
5943 return None;
5944 };
5945
5946 let candidate_id = {
5947 let entries = completions_menu.entries.borrow();
5948 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5949 if self.show_edit_predictions_in_menu() {
5950 self.discard_edit_prediction(true, cx);
5951 }
5952 mat.candidate_id
5953 };
5954
5955 let completion = completions_menu
5956 .completions
5957 .borrow()
5958 .get(candidate_id)?
5959 .clone();
5960 cx.stop_propagation();
5961
5962 let buffer_handle = completions_menu.buffer.clone();
5963
5964 let CompletionEdit {
5965 new_text,
5966 snippet,
5967 replace_range,
5968 } = process_completion_for_edit(
5969 &completion,
5970 intent,
5971 &buffer_handle,
5972 &completions_menu.initial_position.text_anchor,
5973 cx,
5974 );
5975
5976 let buffer = buffer_handle.read(cx);
5977 let snapshot = self.buffer.read(cx).snapshot(cx);
5978 let newest_anchor = self.selections.newest_anchor();
5979 let replace_range_multibuffer = {
5980 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5981 let multibuffer_anchor = snapshot
5982 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5983 .unwrap()
5984 ..snapshot
5985 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5986 .unwrap();
5987 multibuffer_anchor.start.to_offset(&snapshot)
5988 ..multibuffer_anchor.end.to_offset(&snapshot)
5989 };
5990 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5991 return None;
5992 }
5993
5994 let old_text = buffer
5995 .text_for_range(replace_range.clone())
5996 .collect::<String>();
5997 let lookbehind = newest_anchor
5998 .start
5999 .text_anchor
6000 .to_offset(buffer)
6001 .saturating_sub(replace_range.start);
6002 let lookahead = replace_range
6003 .end
6004 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6005 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6006 let suffix = &old_text[lookbehind.min(old_text.len())..];
6007
6008 let selections = self.selections.all::<usize>(cx);
6009 let mut ranges = Vec::new();
6010 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6011
6012 for selection in &selections {
6013 let range = if selection.id == newest_anchor.id {
6014 replace_range_multibuffer.clone()
6015 } else {
6016 let mut range = selection.range();
6017
6018 // if prefix is present, don't duplicate it
6019 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6020 range.start = range.start.saturating_sub(lookbehind);
6021
6022 // if suffix is also present, mimic the newest cursor and replace it
6023 if selection.id != newest_anchor.id
6024 && snapshot.contains_str_at(range.end, suffix)
6025 {
6026 range.end += lookahead;
6027 }
6028 }
6029 range
6030 };
6031
6032 ranges.push(range.clone());
6033
6034 if !self.linked_edit_ranges.is_empty() {
6035 let start_anchor = snapshot.anchor_before(range.start);
6036 let end_anchor = snapshot.anchor_after(range.end);
6037 if let Some(ranges) = self
6038 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6039 {
6040 for (buffer, edits) in ranges {
6041 linked_edits
6042 .entry(buffer.clone())
6043 .or_default()
6044 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6045 }
6046 }
6047 }
6048 }
6049
6050 let common_prefix_len = old_text
6051 .chars()
6052 .zip(new_text.chars())
6053 .take_while(|(a, b)| a == b)
6054 .map(|(a, _)| a.len_utf8())
6055 .sum::<usize>();
6056
6057 cx.emit(EditorEvent::InputHandled {
6058 utf16_range_to_replace: None,
6059 text: new_text[common_prefix_len..].into(),
6060 });
6061
6062 self.transact(window, cx, |editor, window, cx| {
6063 if let Some(mut snippet) = snippet {
6064 snippet.text = new_text.to_string();
6065 editor
6066 .insert_snippet(&ranges, snippet, window, cx)
6067 .log_err();
6068 } else {
6069 editor.buffer.update(cx, |multi_buffer, cx| {
6070 let auto_indent = match completion.insert_text_mode {
6071 Some(InsertTextMode::AS_IS) => None,
6072 _ => editor.autoindent_mode.clone(),
6073 };
6074 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6075 multi_buffer.edit(edits, auto_indent, cx);
6076 });
6077 }
6078 for (buffer, edits) in linked_edits {
6079 buffer.update(cx, |buffer, cx| {
6080 let snapshot = buffer.snapshot();
6081 let edits = edits
6082 .into_iter()
6083 .map(|(range, text)| {
6084 use text::ToPoint as TP;
6085 let end_point = TP::to_point(&range.end, &snapshot);
6086 let start_point = TP::to_point(&range.start, &snapshot);
6087 (start_point..end_point, text)
6088 })
6089 .sorted_by_key(|(range, _)| range.start);
6090 buffer.edit(edits, None, cx);
6091 })
6092 }
6093
6094 editor.refresh_edit_prediction(true, false, window, cx);
6095 });
6096 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6097
6098 let show_new_completions_on_confirm = completion
6099 .confirm
6100 .as_ref()
6101 .is_some_and(|confirm| confirm(intent, window, cx));
6102 if show_new_completions_on_confirm {
6103 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6104 }
6105
6106 let provider = self.completion_provider.as_ref()?;
6107 drop(completion);
6108 let apply_edits = provider.apply_additional_edits_for_completion(
6109 buffer_handle,
6110 completions_menu.completions.clone(),
6111 candidate_id,
6112 true,
6113 cx,
6114 );
6115
6116 let editor_settings = EditorSettings::get_global(cx);
6117 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6118 // After the code completion is finished, users often want to know what signatures are needed.
6119 // so we should automatically call signature_help
6120 self.show_signature_help(&ShowSignatureHelp, window, cx);
6121 }
6122
6123 Some(cx.foreground_executor().spawn(async move {
6124 apply_edits.await?;
6125 Ok(())
6126 }))
6127 }
6128
6129 pub fn toggle_code_actions(
6130 &mut self,
6131 action: &ToggleCodeActions,
6132 window: &mut Window,
6133 cx: &mut Context<Self>,
6134 ) {
6135 let quick_launch = action.quick_launch;
6136 let mut context_menu = self.context_menu.borrow_mut();
6137 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6138 if code_actions.deployed_from == action.deployed_from {
6139 // Toggle if we're selecting the same one
6140 *context_menu = None;
6141 cx.notify();
6142 return;
6143 } else {
6144 // Otherwise, clear it and start a new one
6145 *context_menu = None;
6146 cx.notify();
6147 }
6148 }
6149 drop(context_menu);
6150 let snapshot = self.snapshot(window, cx);
6151 let deployed_from = action.deployed_from.clone();
6152 let action = action.clone();
6153 self.completion_tasks.clear();
6154 self.discard_edit_prediction(false, cx);
6155
6156 let multibuffer_point = match &action.deployed_from {
6157 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6158 DisplayPoint::new(*row, 0).to_point(&snapshot)
6159 }
6160 _ => self.selections.newest::<Point>(cx).head(),
6161 };
6162 let Some((buffer, buffer_row)) = snapshot
6163 .buffer_snapshot()
6164 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6165 .and_then(|(buffer_snapshot, range)| {
6166 self.buffer()
6167 .read(cx)
6168 .buffer(buffer_snapshot.remote_id())
6169 .map(|buffer| (buffer, range.start.row))
6170 })
6171 else {
6172 return;
6173 };
6174 let buffer_id = buffer.read(cx).remote_id();
6175 let tasks = self
6176 .tasks
6177 .get(&(buffer_id, buffer_row))
6178 .map(|t| Arc::new(t.to_owned()));
6179
6180 if !self.focus_handle.is_focused(window) {
6181 return;
6182 }
6183 let project = self.project.clone();
6184
6185 let code_actions_task = match deployed_from {
6186 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6187 _ => self.code_actions(buffer_row, window, cx),
6188 };
6189
6190 let runnable_task = match deployed_from {
6191 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6192 _ => {
6193 let mut task_context_task = Task::ready(None);
6194 if let Some(tasks) = &tasks
6195 && let Some(project) = project
6196 {
6197 task_context_task =
6198 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6199 }
6200
6201 cx.spawn_in(window, {
6202 let buffer = buffer.clone();
6203 async move |editor, cx| {
6204 let task_context = task_context_task.await;
6205
6206 let resolved_tasks =
6207 tasks
6208 .zip(task_context.clone())
6209 .map(|(tasks, task_context)| ResolvedTasks {
6210 templates: tasks.resolve(&task_context).collect(),
6211 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6212 multibuffer_point.row,
6213 tasks.column,
6214 )),
6215 });
6216 let debug_scenarios = editor
6217 .update(cx, |editor, cx| {
6218 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6219 })?
6220 .await;
6221 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6222 }
6223 })
6224 }
6225 };
6226
6227 cx.spawn_in(window, async move |editor, cx| {
6228 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6229 let code_actions = code_actions_task.await;
6230 let spawn_straight_away = quick_launch
6231 && resolved_tasks
6232 .as_ref()
6233 .is_some_and(|tasks| tasks.templates.len() == 1)
6234 && code_actions
6235 .as_ref()
6236 .is_none_or(|actions| actions.is_empty())
6237 && debug_scenarios.is_empty();
6238
6239 editor.update_in(cx, |editor, window, cx| {
6240 crate::hover_popover::hide_hover(editor, cx);
6241 let actions = CodeActionContents::new(
6242 resolved_tasks,
6243 code_actions,
6244 debug_scenarios,
6245 task_context.unwrap_or_default(),
6246 );
6247
6248 // Don't show the menu if there are no actions available
6249 if actions.is_empty() {
6250 cx.notify();
6251 return Task::ready(Ok(()));
6252 }
6253
6254 *editor.context_menu.borrow_mut() =
6255 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6256 buffer,
6257 actions,
6258 selected_item: Default::default(),
6259 scroll_handle: UniformListScrollHandle::default(),
6260 deployed_from,
6261 }));
6262 cx.notify();
6263 if spawn_straight_away
6264 && let Some(task) = editor.confirm_code_action(
6265 &ConfirmCodeAction { item_ix: Some(0) },
6266 window,
6267 cx,
6268 )
6269 {
6270 return task;
6271 }
6272
6273 Task::ready(Ok(()))
6274 })
6275 })
6276 .detach_and_log_err(cx);
6277 }
6278
6279 fn debug_scenarios(
6280 &mut self,
6281 resolved_tasks: &Option<ResolvedTasks>,
6282 buffer: &Entity<Buffer>,
6283 cx: &mut App,
6284 ) -> Task<Vec<task::DebugScenario>> {
6285 maybe!({
6286 let project = self.project()?;
6287 let dap_store = project.read(cx).dap_store();
6288 let mut scenarios = vec![];
6289 let resolved_tasks = resolved_tasks.as_ref()?;
6290 let buffer = buffer.read(cx);
6291 let language = buffer.language()?;
6292 let file = buffer.file();
6293 let debug_adapter = language_settings(language.name().into(), file, cx)
6294 .debuggers
6295 .first()
6296 .map(SharedString::from)
6297 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6298
6299 dap_store.update(cx, |dap_store, cx| {
6300 for (_, task) in &resolved_tasks.templates {
6301 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6302 task.original_task().clone(),
6303 debug_adapter.clone().into(),
6304 task.display_label().to_owned().into(),
6305 cx,
6306 );
6307 scenarios.push(maybe_scenario);
6308 }
6309 });
6310 Some(cx.background_spawn(async move {
6311 futures::future::join_all(scenarios)
6312 .await
6313 .into_iter()
6314 .flatten()
6315 .collect::<Vec<_>>()
6316 }))
6317 })
6318 .unwrap_or_else(|| Task::ready(vec![]))
6319 }
6320
6321 fn code_actions(
6322 &mut self,
6323 buffer_row: u32,
6324 window: &mut Window,
6325 cx: &mut Context<Self>,
6326 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6327 let mut task = self.code_actions_task.take();
6328 cx.spawn_in(window, async move |editor, cx| {
6329 while let Some(prev_task) = task {
6330 prev_task.await.log_err();
6331 task = editor
6332 .update(cx, |this, _| this.code_actions_task.take())
6333 .ok()?;
6334 }
6335
6336 editor
6337 .update(cx, |editor, cx| {
6338 editor
6339 .available_code_actions
6340 .clone()
6341 .and_then(|(location, code_actions)| {
6342 let snapshot = location.buffer.read(cx).snapshot();
6343 let point_range = location.range.to_point(&snapshot);
6344 let point_range = point_range.start.row..=point_range.end.row;
6345 if point_range.contains(&buffer_row) {
6346 Some(code_actions)
6347 } else {
6348 None
6349 }
6350 })
6351 })
6352 .ok()
6353 .flatten()
6354 })
6355 }
6356
6357 pub fn confirm_code_action(
6358 &mut self,
6359 action: &ConfirmCodeAction,
6360 window: &mut Window,
6361 cx: &mut Context<Self>,
6362 ) -> Option<Task<Result<()>>> {
6363 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6364
6365 let actions_menu =
6366 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6367 menu
6368 } else {
6369 return None;
6370 };
6371
6372 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6373 let action = actions_menu.actions.get(action_ix)?;
6374 let title = action.label();
6375 let buffer = actions_menu.buffer;
6376 let workspace = self.workspace()?;
6377
6378 match action {
6379 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6380 workspace.update(cx, |workspace, cx| {
6381 workspace.schedule_resolved_task(
6382 task_source_kind,
6383 resolved_task,
6384 false,
6385 window,
6386 cx,
6387 );
6388
6389 Some(Task::ready(Ok(())))
6390 })
6391 }
6392 CodeActionsItem::CodeAction {
6393 excerpt_id,
6394 action,
6395 provider,
6396 } => {
6397 let apply_code_action =
6398 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6399 let workspace = workspace.downgrade();
6400 Some(cx.spawn_in(window, async move |editor, cx| {
6401 let project_transaction = apply_code_action.await?;
6402 Self::open_project_transaction(
6403 &editor,
6404 workspace,
6405 project_transaction,
6406 title,
6407 cx,
6408 )
6409 .await
6410 }))
6411 }
6412 CodeActionsItem::DebugScenario(scenario) => {
6413 let context = actions_menu.actions.context;
6414
6415 workspace.update(cx, |workspace, cx| {
6416 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6417 workspace.start_debug_session(
6418 scenario,
6419 context,
6420 Some(buffer),
6421 None,
6422 window,
6423 cx,
6424 );
6425 });
6426 Some(Task::ready(Ok(())))
6427 }
6428 }
6429 }
6430
6431 pub async fn open_project_transaction(
6432 editor: &WeakEntity<Editor>,
6433 workspace: WeakEntity<Workspace>,
6434 transaction: ProjectTransaction,
6435 title: String,
6436 cx: &mut AsyncWindowContext,
6437 ) -> Result<()> {
6438 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6439 cx.update(|_, cx| {
6440 entries.sort_unstable_by_key(|(buffer, _)| {
6441 buffer.read(cx).file().map(|f| f.path().clone())
6442 });
6443 })?;
6444 if entries.is_empty() {
6445 return Ok(());
6446 }
6447
6448 // If the project transaction's edits are all contained within this editor, then
6449 // avoid opening a new editor to display them.
6450
6451 if let [(buffer, transaction)] = &*entries {
6452 let excerpt = editor.update(cx, |editor, cx| {
6453 editor
6454 .buffer()
6455 .read(cx)
6456 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6457 })?;
6458 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6459 && excerpted_buffer == *buffer
6460 {
6461 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6462 let excerpt_range = excerpt_range.to_offset(buffer);
6463 buffer
6464 .edited_ranges_for_transaction::<usize>(transaction)
6465 .all(|range| {
6466 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6467 })
6468 })?;
6469
6470 if all_edits_within_excerpt {
6471 return Ok(());
6472 }
6473 }
6474 }
6475
6476 let mut ranges_to_highlight = Vec::new();
6477 let excerpt_buffer = cx.new(|cx| {
6478 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6479 for (buffer_handle, transaction) in &entries {
6480 let edited_ranges = buffer_handle
6481 .read(cx)
6482 .edited_ranges_for_transaction::<Point>(transaction)
6483 .collect::<Vec<_>>();
6484 let (ranges, _) = multibuffer.set_excerpts_for_path(
6485 PathKey::for_buffer(buffer_handle, cx),
6486 buffer_handle.clone(),
6487 edited_ranges,
6488 multibuffer_context_lines(cx),
6489 cx,
6490 );
6491
6492 ranges_to_highlight.extend(ranges);
6493 }
6494 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6495 multibuffer
6496 })?;
6497
6498 workspace.update_in(cx, |workspace, window, cx| {
6499 let project = workspace.project().clone();
6500 let editor =
6501 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6502 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6503 editor.update(cx, |editor, cx| {
6504 editor.highlight_background::<Self>(
6505 &ranges_to_highlight,
6506 |theme| theme.colors().editor_highlighted_line_background,
6507 cx,
6508 );
6509 });
6510 })?;
6511
6512 Ok(())
6513 }
6514
6515 pub fn clear_code_action_providers(&mut self) {
6516 self.code_action_providers.clear();
6517 self.available_code_actions.take();
6518 }
6519
6520 pub fn add_code_action_provider(
6521 &mut self,
6522 provider: Rc<dyn CodeActionProvider>,
6523 window: &mut Window,
6524 cx: &mut Context<Self>,
6525 ) {
6526 if self
6527 .code_action_providers
6528 .iter()
6529 .any(|existing_provider| existing_provider.id() == provider.id())
6530 {
6531 return;
6532 }
6533
6534 self.code_action_providers.push(provider);
6535 self.refresh_code_actions(window, cx);
6536 }
6537
6538 pub fn remove_code_action_provider(
6539 &mut self,
6540 id: Arc<str>,
6541 window: &mut Window,
6542 cx: &mut Context<Self>,
6543 ) {
6544 self.code_action_providers
6545 .retain(|provider| provider.id() != id);
6546 self.refresh_code_actions(window, cx);
6547 }
6548
6549 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6550 !self.code_action_providers.is_empty()
6551 && EditorSettings::get_global(cx).toolbar.code_actions
6552 }
6553
6554 pub fn has_available_code_actions(&self) -> bool {
6555 self.available_code_actions
6556 .as_ref()
6557 .is_some_and(|(_, actions)| !actions.is_empty())
6558 }
6559
6560 fn render_inline_code_actions(
6561 &self,
6562 icon_size: ui::IconSize,
6563 display_row: DisplayRow,
6564 is_active: bool,
6565 cx: &mut Context<Self>,
6566 ) -> AnyElement {
6567 let show_tooltip = !self.context_menu_visible();
6568 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6569 .icon_size(icon_size)
6570 .shape(ui::IconButtonShape::Square)
6571 .icon_color(ui::Color::Hidden)
6572 .toggle_state(is_active)
6573 .when(show_tooltip, |this| {
6574 this.tooltip({
6575 let focus_handle = self.focus_handle.clone();
6576 move |window, cx| {
6577 Tooltip::for_action_in(
6578 "Toggle Code Actions",
6579 &ToggleCodeActions {
6580 deployed_from: None,
6581 quick_launch: false,
6582 },
6583 &focus_handle,
6584 window,
6585 cx,
6586 )
6587 }
6588 })
6589 })
6590 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6591 window.focus(&editor.focus_handle(cx));
6592 editor.toggle_code_actions(
6593 &crate::actions::ToggleCodeActions {
6594 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6595 display_row,
6596 )),
6597 quick_launch: false,
6598 },
6599 window,
6600 cx,
6601 );
6602 }))
6603 .into_any_element()
6604 }
6605
6606 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6607 &self.context_menu
6608 }
6609
6610 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6611 let newest_selection = self.selections.newest_anchor().clone();
6612 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6613 let buffer = self.buffer.read(cx);
6614 if newest_selection.head().diff_base_anchor.is_some() {
6615 return None;
6616 }
6617 let (start_buffer, start) =
6618 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6619 let (end_buffer, end) =
6620 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6621 if start_buffer != end_buffer {
6622 return None;
6623 }
6624
6625 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6626 cx.background_executor()
6627 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6628 .await;
6629
6630 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6631 let providers = this.code_action_providers.clone();
6632 let tasks = this
6633 .code_action_providers
6634 .iter()
6635 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6636 .collect::<Vec<_>>();
6637 (providers, tasks)
6638 })?;
6639
6640 let mut actions = Vec::new();
6641 for (provider, provider_actions) in
6642 providers.into_iter().zip(future::join_all(tasks).await)
6643 {
6644 if let Some(provider_actions) = provider_actions.log_err() {
6645 actions.extend(provider_actions.into_iter().map(|action| {
6646 AvailableCodeAction {
6647 excerpt_id: newest_selection.start.excerpt_id,
6648 action,
6649 provider: provider.clone(),
6650 }
6651 }));
6652 }
6653 }
6654
6655 this.update(cx, |this, cx| {
6656 this.available_code_actions = if actions.is_empty() {
6657 None
6658 } else {
6659 Some((
6660 Location {
6661 buffer: start_buffer,
6662 range: start..end,
6663 },
6664 actions.into(),
6665 ))
6666 };
6667 cx.notify();
6668 })
6669 }));
6670 None
6671 }
6672
6673 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6674 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6675 self.show_git_blame_inline = false;
6676
6677 self.show_git_blame_inline_delay_task =
6678 Some(cx.spawn_in(window, async move |this, cx| {
6679 cx.background_executor().timer(delay).await;
6680
6681 this.update(cx, |this, cx| {
6682 this.show_git_blame_inline = true;
6683 cx.notify();
6684 })
6685 .log_err();
6686 }));
6687 }
6688 }
6689
6690 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6691 let snapshot = self.snapshot(window, cx);
6692 let cursor = self.selections.newest::<Point>(cx).head();
6693 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6694 else {
6695 return;
6696 };
6697
6698 let Some(blame) = self.blame.as_ref() else {
6699 return;
6700 };
6701
6702 let row_info = RowInfo {
6703 buffer_id: Some(buffer.remote_id()),
6704 buffer_row: Some(point.row),
6705 ..Default::default()
6706 };
6707 let Some((buffer, blame_entry)) = blame
6708 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6709 .flatten()
6710 else {
6711 return;
6712 };
6713
6714 let anchor = self.selections.newest_anchor().head();
6715 let position = self.to_pixel_point(anchor, &snapshot, window);
6716 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6717 self.show_blame_popover(
6718 buffer,
6719 &blame_entry,
6720 position + last_bounds.origin,
6721 true,
6722 cx,
6723 );
6724 };
6725 }
6726
6727 fn show_blame_popover(
6728 &mut self,
6729 buffer: BufferId,
6730 blame_entry: &BlameEntry,
6731 position: gpui::Point<Pixels>,
6732 ignore_timeout: bool,
6733 cx: &mut Context<Self>,
6734 ) {
6735 if let Some(state) = &mut self.inline_blame_popover {
6736 state.hide_task.take();
6737 } else {
6738 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6739 let blame_entry = blame_entry.clone();
6740 let show_task = cx.spawn(async move |editor, cx| {
6741 if !ignore_timeout {
6742 cx.background_executor()
6743 .timer(std::time::Duration::from_millis(blame_popover_delay))
6744 .await;
6745 }
6746 editor
6747 .update(cx, |editor, cx| {
6748 editor.inline_blame_popover_show_task.take();
6749 let Some(blame) = editor.blame.as_ref() else {
6750 return;
6751 };
6752 let blame = blame.read(cx);
6753 let details = blame.details_for_entry(buffer, &blame_entry);
6754 let markdown = cx.new(|cx| {
6755 Markdown::new(
6756 details
6757 .as_ref()
6758 .map(|message| message.message.clone())
6759 .unwrap_or_default(),
6760 None,
6761 None,
6762 cx,
6763 )
6764 });
6765 editor.inline_blame_popover = Some(InlineBlamePopover {
6766 position,
6767 hide_task: None,
6768 popover_bounds: None,
6769 popover_state: InlineBlamePopoverState {
6770 scroll_handle: ScrollHandle::new(),
6771 commit_message: details,
6772 markdown,
6773 },
6774 keyboard_grace: ignore_timeout,
6775 });
6776 cx.notify();
6777 })
6778 .ok();
6779 });
6780 self.inline_blame_popover_show_task = Some(show_task);
6781 }
6782 }
6783
6784 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6785 self.inline_blame_popover_show_task.take();
6786 if let Some(state) = &mut self.inline_blame_popover {
6787 let hide_task = cx.spawn(async move |editor, cx| {
6788 cx.background_executor()
6789 .timer(std::time::Duration::from_millis(100))
6790 .await;
6791 editor
6792 .update(cx, |editor, cx| {
6793 editor.inline_blame_popover.take();
6794 cx.notify();
6795 })
6796 .ok();
6797 });
6798 state.hide_task = Some(hide_task);
6799 }
6800 }
6801
6802 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6803 if self.pending_rename.is_some() {
6804 return None;
6805 }
6806
6807 let provider = self.semantics_provider.clone()?;
6808 let buffer = self.buffer.read(cx);
6809 let newest_selection = self.selections.newest_anchor().clone();
6810 let cursor_position = newest_selection.head();
6811 let (cursor_buffer, cursor_buffer_position) =
6812 buffer.text_anchor_for_position(cursor_position, cx)?;
6813 let (tail_buffer, tail_buffer_position) =
6814 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6815 if cursor_buffer != tail_buffer {
6816 return None;
6817 }
6818
6819 let snapshot = cursor_buffer.read(cx).snapshot();
6820 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6821 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6822 if start_word_range != end_word_range {
6823 self.document_highlights_task.take();
6824 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6825 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6826 return None;
6827 }
6828
6829 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6830 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6831 cx.background_executor()
6832 .timer(Duration::from_millis(debounce))
6833 .await;
6834
6835 let highlights = if let Some(highlights) = cx
6836 .update(|cx| {
6837 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6838 })
6839 .ok()
6840 .flatten()
6841 {
6842 highlights.await.log_err()
6843 } else {
6844 None
6845 };
6846
6847 if let Some(highlights) = highlights {
6848 this.update(cx, |this, cx| {
6849 if this.pending_rename.is_some() {
6850 return;
6851 }
6852
6853 let buffer = this.buffer.read(cx);
6854 if buffer
6855 .text_anchor_for_position(cursor_position, cx)
6856 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6857 {
6858 return;
6859 }
6860
6861 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6862 let mut write_ranges = Vec::new();
6863 let mut read_ranges = Vec::new();
6864 for highlight in highlights {
6865 let buffer_id = cursor_buffer.read(cx).remote_id();
6866 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6867 {
6868 let start = highlight
6869 .range
6870 .start
6871 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6872 let end = highlight
6873 .range
6874 .end
6875 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6876 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6877 continue;
6878 }
6879
6880 let range = Anchor::range_in_buffer(excerpt_id, buffer_id, start..end);
6881 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6882 write_ranges.push(range);
6883 } else {
6884 read_ranges.push(range);
6885 }
6886 }
6887 }
6888
6889 this.highlight_background::<DocumentHighlightRead>(
6890 &read_ranges,
6891 |theme| theme.colors().editor_document_highlight_read_background,
6892 cx,
6893 );
6894 this.highlight_background::<DocumentHighlightWrite>(
6895 &write_ranges,
6896 |theme| theme.colors().editor_document_highlight_write_background,
6897 cx,
6898 );
6899 cx.notify();
6900 })
6901 .log_err();
6902 }
6903 }));
6904 None
6905 }
6906
6907 fn prepare_highlight_query_from_selection(
6908 &mut self,
6909 cx: &mut Context<Editor>,
6910 ) -> Option<(String, Range<Anchor>)> {
6911 if matches!(self.mode, EditorMode::SingleLine) {
6912 return None;
6913 }
6914 if !EditorSettings::get_global(cx).selection_highlight {
6915 return None;
6916 }
6917 if self.selections.count() != 1 || self.selections.line_mode() {
6918 return None;
6919 }
6920 let selection = self.selections.newest::<Point>(cx);
6921 if selection.is_empty() || selection.start.row != selection.end.row {
6922 return None;
6923 }
6924 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6925 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6926 let query = multi_buffer_snapshot
6927 .text_for_range(selection_anchor_range.clone())
6928 .collect::<String>();
6929 if query.trim().is_empty() {
6930 return None;
6931 }
6932 Some((query, selection_anchor_range))
6933 }
6934
6935 fn update_selection_occurrence_highlights(
6936 &mut self,
6937 query_text: String,
6938 query_range: Range<Anchor>,
6939 multi_buffer_range_to_query: Range<Point>,
6940 use_debounce: bool,
6941 window: &mut Window,
6942 cx: &mut Context<Editor>,
6943 ) -> Task<()> {
6944 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6945 cx.spawn_in(window, async move |editor, cx| {
6946 if use_debounce {
6947 cx.background_executor()
6948 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6949 .await;
6950 }
6951 let match_task = cx.background_spawn(async move {
6952 let buffer_ranges = multi_buffer_snapshot
6953 .range_to_buffer_ranges(multi_buffer_range_to_query)
6954 .into_iter()
6955 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6956 let mut match_ranges = Vec::new();
6957 let Ok(regex) = project::search::SearchQuery::text(
6958 query_text.clone(),
6959 false,
6960 false,
6961 false,
6962 Default::default(),
6963 Default::default(),
6964 false,
6965 None,
6966 ) else {
6967 return Vec::default();
6968 };
6969 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6970 match_ranges.extend(
6971 regex
6972 .search(buffer_snapshot, Some(search_range.clone()))
6973 .await
6974 .into_iter()
6975 .filter_map(|match_range| {
6976 let match_start = buffer_snapshot
6977 .anchor_after(search_range.start + match_range.start);
6978 let match_end = buffer_snapshot
6979 .anchor_before(search_range.start + match_range.end);
6980 let match_anchor_range = Anchor::range_in_buffer(
6981 excerpt_id,
6982 buffer_snapshot.remote_id(),
6983 match_start..match_end,
6984 );
6985 (match_anchor_range != query_range).then_some(match_anchor_range)
6986 }),
6987 );
6988 }
6989 match_ranges
6990 });
6991 let match_ranges = match_task.await;
6992 editor
6993 .update_in(cx, |editor, _, cx| {
6994 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6995 if !match_ranges.is_empty() {
6996 editor.highlight_background::<SelectedTextHighlight>(
6997 &match_ranges,
6998 |theme| theme.colors().editor_document_highlight_bracket_background,
6999 cx,
7000 )
7001 }
7002 })
7003 .log_err();
7004 })
7005 }
7006
7007 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7008 struct NewlineFold;
7009 let type_id = std::any::TypeId::of::<NewlineFold>();
7010 if !self.mode.is_single_line() {
7011 return;
7012 }
7013 let snapshot = self.snapshot(window, cx);
7014 if snapshot.buffer_snapshot().max_point().row == 0 {
7015 return;
7016 }
7017 let task = cx.background_spawn(async move {
7018 let new_newlines = snapshot
7019 .buffer_chars_at(0)
7020 .filter_map(|(c, i)| {
7021 if c == '\n' {
7022 Some(
7023 snapshot.buffer_snapshot().anchor_after(i)
7024 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7025 )
7026 } else {
7027 None
7028 }
7029 })
7030 .collect::<Vec<_>>();
7031 let existing_newlines = snapshot
7032 .folds_in_range(0..snapshot.buffer_snapshot().len())
7033 .filter_map(|fold| {
7034 if fold.placeholder.type_tag == Some(type_id) {
7035 Some(fold.range.start..fold.range.end)
7036 } else {
7037 None
7038 }
7039 })
7040 .collect::<Vec<_>>();
7041
7042 (new_newlines, existing_newlines)
7043 });
7044 self.folding_newlines = cx.spawn(async move |this, cx| {
7045 let (new_newlines, existing_newlines) = task.await;
7046 if new_newlines == existing_newlines {
7047 return;
7048 }
7049 let placeholder = FoldPlaceholder {
7050 render: Arc::new(move |_, _, cx| {
7051 div()
7052 .bg(cx.theme().status().hint_background)
7053 .border_b_1()
7054 .size_full()
7055 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7056 .border_color(cx.theme().status().hint)
7057 .child("\\n")
7058 .into_any()
7059 }),
7060 constrain_width: false,
7061 merge_adjacent: false,
7062 type_tag: Some(type_id),
7063 };
7064 let creases = new_newlines
7065 .into_iter()
7066 .map(|range| Crease::simple(range, placeholder.clone()))
7067 .collect();
7068 this.update(cx, |this, cx| {
7069 this.display_map.update(cx, |display_map, cx| {
7070 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7071 display_map.fold(creases, cx);
7072 });
7073 })
7074 .ok();
7075 });
7076 }
7077
7078 fn refresh_selected_text_highlights(
7079 &mut self,
7080 on_buffer_edit: bool,
7081 window: &mut Window,
7082 cx: &mut Context<Editor>,
7083 ) {
7084 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7085 else {
7086 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7087 self.quick_selection_highlight_task.take();
7088 self.debounced_selection_highlight_task.take();
7089 return;
7090 };
7091 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7092 if on_buffer_edit
7093 || self
7094 .quick_selection_highlight_task
7095 .as_ref()
7096 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7097 {
7098 let multi_buffer_visible_start = self
7099 .scroll_manager
7100 .anchor()
7101 .anchor
7102 .to_point(&multi_buffer_snapshot);
7103 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7104 multi_buffer_visible_start
7105 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7106 Bias::Left,
7107 );
7108 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7109 self.quick_selection_highlight_task = Some((
7110 query_range.clone(),
7111 self.update_selection_occurrence_highlights(
7112 query_text.clone(),
7113 query_range.clone(),
7114 multi_buffer_visible_range,
7115 false,
7116 window,
7117 cx,
7118 ),
7119 ));
7120 }
7121 if on_buffer_edit
7122 || self
7123 .debounced_selection_highlight_task
7124 .as_ref()
7125 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7126 {
7127 let multi_buffer_start = multi_buffer_snapshot
7128 .anchor_before(0)
7129 .to_point(&multi_buffer_snapshot);
7130 let multi_buffer_end = multi_buffer_snapshot
7131 .anchor_after(multi_buffer_snapshot.len())
7132 .to_point(&multi_buffer_snapshot);
7133 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7134 self.debounced_selection_highlight_task = Some((
7135 query_range.clone(),
7136 self.update_selection_occurrence_highlights(
7137 query_text,
7138 query_range,
7139 multi_buffer_full_range,
7140 true,
7141 window,
7142 cx,
7143 ),
7144 ));
7145 }
7146 }
7147
7148 pub fn refresh_edit_prediction(
7149 &mut self,
7150 debounce: bool,
7151 user_requested: bool,
7152 window: &mut Window,
7153 cx: &mut Context<Self>,
7154 ) -> Option<()> {
7155 if DisableAiSettings::get_global(cx).disable_ai {
7156 return None;
7157 }
7158
7159 let provider = self.edit_prediction_provider()?;
7160 let cursor = self.selections.newest_anchor().head();
7161 let (buffer, cursor_buffer_position) =
7162 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7163
7164 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7165 self.discard_edit_prediction(false, cx);
7166 return None;
7167 }
7168
7169 self.update_visible_edit_prediction(window, cx);
7170
7171 if !user_requested
7172 && (!self.should_show_edit_predictions()
7173 || !self.is_focused(window)
7174 || buffer.read(cx).is_empty())
7175 {
7176 self.discard_edit_prediction(false, cx);
7177 return None;
7178 }
7179
7180 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7181 Some(())
7182 }
7183
7184 fn show_edit_predictions_in_menu(&self) -> bool {
7185 match self.edit_prediction_settings {
7186 EditPredictionSettings::Disabled => false,
7187 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7188 }
7189 }
7190
7191 pub fn edit_predictions_enabled(&self) -> bool {
7192 match self.edit_prediction_settings {
7193 EditPredictionSettings::Disabled => false,
7194 EditPredictionSettings::Enabled { .. } => true,
7195 }
7196 }
7197
7198 fn edit_prediction_requires_modifier(&self) -> bool {
7199 match self.edit_prediction_settings {
7200 EditPredictionSettings::Disabled => false,
7201 EditPredictionSettings::Enabled {
7202 preview_requires_modifier,
7203 ..
7204 } => preview_requires_modifier,
7205 }
7206 }
7207
7208 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7209 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7210 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7211 self.discard_edit_prediction(false, cx);
7212 } else {
7213 let selection = self.selections.newest_anchor();
7214 let cursor = selection.head();
7215
7216 if let Some((buffer, cursor_buffer_position)) =
7217 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7218 {
7219 self.edit_prediction_settings =
7220 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7221 }
7222 }
7223 }
7224
7225 fn edit_prediction_settings_at_position(
7226 &self,
7227 buffer: &Entity<Buffer>,
7228 buffer_position: language::Anchor,
7229 cx: &App,
7230 ) -> EditPredictionSettings {
7231 if !self.mode.is_full()
7232 || !self.show_edit_predictions_override.unwrap_or(true)
7233 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7234 {
7235 return EditPredictionSettings::Disabled;
7236 }
7237
7238 let buffer = buffer.read(cx);
7239
7240 let file = buffer.file();
7241
7242 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7243 return EditPredictionSettings::Disabled;
7244 };
7245
7246 let by_provider = matches!(
7247 self.menu_edit_predictions_policy,
7248 MenuEditPredictionsPolicy::ByProvider
7249 );
7250
7251 let show_in_menu = by_provider
7252 && self
7253 .edit_prediction_provider
7254 .as_ref()
7255 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7256
7257 let preview_requires_modifier =
7258 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7259
7260 EditPredictionSettings::Enabled {
7261 show_in_menu,
7262 preview_requires_modifier,
7263 }
7264 }
7265
7266 fn should_show_edit_predictions(&self) -> bool {
7267 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7268 }
7269
7270 pub fn edit_prediction_preview_is_active(&self) -> bool {
7271 matches!(
7272 self.edit_prediction_preview,
7273 EditPredictionPreview::Active { .. }
7274 )
7275 }
7276
7277 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7278 let cursor = self.selections.newest_anchor().head();
7279 if let Some((buffer, cursor_position)) =
7280 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7281 {
7282 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7283 } else {
7284 false
7285 }
7286 }
7287
7288 pub fn supports_minimap(&self, cx: &App) -> bool {
7289 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7290 }
7291
7292 fn edit_predictions_enabled_in_buffer(
7293 &self,
7294 buffer: &Entity<Buffer>,
7295 buffer_position: language::Anchor,
7296 cx: &App,
7297 ) -> bool {
7298 maybe!({
7299 if self.read_only(cx) {
7300 return Some(false);
7301 }
7302 let provider = self.edit_prediction_provider()?;
7303 if !provider.is_enabled(buffer, buffer_position, cx) {
7304 return Some(false);
7305 }
7306 let buffer = buffer.read(cx);
7307 let Some(file) = buffer.file() else {
7308 return Some(true);
7309 };
7310 let settings = all_language_settings(Some(file), cx);
7311 Some(settings.edit_predictions_enabled_for_file(file, cx))
7312 })
7313 .unwrap_or(false)
7314 }
7315
7316 fn cycle_edit_prediction(
7317 &mut self,
7318 direction: Direction,
7319 window: &mut Window,
7320 cx: &mut Context<Self>,
7321 ) -> Option<()> {
7322 let provider = self.edit_prediction_provider()?;
7323 let cursor = self.selections.newest_anchor().head();
7324 let (buffer, cursor_buffer_position) =
7325 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7326 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7327 return None;
7328 }
7329
7330 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7331 self.update_visible_edit_prediction(window, cx);
7332
7333 Some(())
7334 }
7335
7336 pub fn show_edit_prediction(
7337 &mut self,
7338 _: &ShowEditPrediction,
7339 window: &mut Window,
7340 cx: &mut Context<Self>,
7341 ) {
7342 if !self.has_active_edit_prediction() {
7343 self.refresh_edit_prediction(false, true, window, cx);
7344 return;
7345 }
7346
7347 self.update_visible_edit_prediction(window, cx);
7348 }
7349
7350 pub fn display_cursor_names(
7351 &mut self,
7352 _: &DisplayCursorNames,
7353 window: &mut Window,
7354 cx: &mut Context<Self>,
7355 ) {
7356 self.show_cursor_names(window, cx);
7357 }
7358
7359 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7360 self.show_cursor_names = true;
7361 cx.notify();
7362 cx.spawn_in(window, async move |this, cx| {
7363 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7364 this.update(cx, |this, cx| {
7365 this.show_cursor_names = false;
7366 cx.notify()
7367 })
7368 .ok()
7369 })
7370 .detach();
7371 }
7372
7373 pub fn next_edit_prediction(
7374 &mut self,
7375 _: &NextEditPrediction,
7376 window: &mut Window,
7377 cx: &mut Context<Self>,
7378 ) {
7379 if self.has_active_edit_prediction() {
7380 self.cycle_edit_prediction(Direction::Next, window, cx);
7381 } else {
7382 let is_copilot_disabled = self
7383 .refresh_edit_prediction(false, true, window, cx)
7384 .is_none();
7385 if is_copilot_disabled {
7386 cx.propagate();
7387 }
7388 }
7389 }
7390
7391 pub fn previous_edit_prediction(
7392 &mut self,
7393 _: &PreviousEditPrediction,
7394 window: &mut Window,
7395 cx: &mut Context<Self>,
7396 ) {
7397 if self.has_active_edit_prediction() {
7398 self.cycle_edit_prediction(Direction::Prev, window, cx);
7399 } else {
7400 let is_copilot_disabled = self
7401 .refresh_edit_prediction(false, true, window, cx)
7402 .is_none();
7403 if is_copilot_disabled {
7404 cx.propagate();
7405 }
7406 }
7407 }
7408
7409 pub fn accept_edit_prediction(
7410 &mut self,
7411 _: &AcceptEditPrediction,
7412 window: &mut Window,
7413 cx: &mut Context<Self>,
7414 ) {
7415 if self.show_edit_predictions_in_menu() {
7416 self.hide_context_menu(window, cx);
7417 }
7418
7419 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7420 return;
7421 };
7422
7423 match &active_edit_prediction.completion {
7424 EditPrediction::MoveWithin { target, .. } => {
7425 let target = *target;
7426
7427 if let Some(position_map) = &self.last_position_map {
7428 if position_map
7429 .visible_row_range
7430 .contains(&target.to_display_point(&position_map.snapshot).row())
7431 || !self.edit_prediction_requires_modifier()
7432 {
7433 self.unfold_ranges(&[target..target], true, false, cx);
7434 // Note that this is also done in vim's handler of the Tab action.
7435 self.change_selections(
7436 SelectionEffects::scroll(Autoscroll::newest()),
7437 window,
7438 cx,
7439 |selections| {
7440 selections.select_anchor_ranges([target..target]);
7441 },
7442 );
7443 self.clear_row_highlights::<EditPredictionPreview>();
7444
7445 self.edit_prediction_preview
7446 .set_previous_scroll_position(None);
7447 } else {
7448 self.edit_prediction_preview
7449 .set_previous_scroll_position(Some(
7450 position_map.snapshot.scroll_anchor,
7451 ));
7452
7453 self.highlight_rows::<EditPredictionPreview>(
7454 target..target,
7455 cx.theme().colors().editor_highlighted_line_background,
7456 RowHighlightOptions {
7457 autoscroll: true,
7458 ..Default::default()
7459 },
7460 cx,
7461 );
7462 self.request_autoscroll(Autoscroll::fit(), cx);
7463 }
7464 }
7465 }
7466 EditPrediction::MoveOutside { snapshot, target } => {
7467 if let Some(workspace) = self.workspace() {
7468 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7469 .detach_and_log_err(cx);
7470 }
7471 }
7472 EditPrediction::Edit { edits, .. } => {
7473 self.report_edit_prediction_event(
7474 active_edit_prediction.completion_id.clone(),
7475 true,
7476 cx,
7477 );
7478
7479 if let Some(provider) = self.edit_prediction_provider() {
7480 provider.accept(cx);
7481 }
7482
7483 // Store the transaction ID and selections before applying the edit
7484 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7485
7486 let snapshot = self.buffer.read(cx).snapshot(cx);
7487 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7488
7489 self.buffer.update(cx, |buffer, cx| {
7490 buffer.edit(edits.iter().cloned(), None, cx)
7491 });
7492
7493 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7494 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7495 });
7496
7497 let selections = self.selections.disjoint_anchors_arc();
7498 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7499 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7500 if has_new_transaction {
7501 self.selection_history
7502 .insert_transaction(transaction_id_now, selections);
7503 }
7504 }
7505
7506 self.update_visible_edit_prediction(window, cx);
7507 if self.active_edit_prediction.is_none() {
7508 self.refresh_edit_prediction(true, true, window, cx);
7509 }
7510
7511 cx.notify();
7512 }
7513 }
7514
7515 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7516 }
7517
7518 pub fn accept_partial_edit_prediction(
7519 &mut self,
7520 _: &AcceptPartialEditPrediction,
7521 window: &mut Window,
7522 cx: &mut Context<Self>,
7523 ) {
7524 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7525 return;
7526 };
7527 if self.selections.count() != 1 {
7528 return;
7529 }
7530
7531 match &active_edit_prediction.completion {
7532 EditPrediction::MoveWithin { target, .. } => {
7533 let target = *target;
7534 self.change_selections(
7535 SelectionEffects::scroll(Autoscroll::newest()),
7536 window,
7537 cx,
7538 |selections| {
7539 selections.select_anchor_ranges([target..target]);
7540 },
7541 );
7542 }
7543 EditPrediction::MoveOutside { snapshot, target } => {
7544 if let Some(workspace) = self.workspace() {
7545 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7546 .detach_and_log_err(cx);
7547 }
7548 }
7549 EditPrediction::Edit { edits, .. } => {
7550 self.report_edit_prediction_event(
7551 active_edit_prediction.completion_id.clone(),
7552 true,
7553 cx,
7554 );
7555
7556 // Find an insertion that starts at the cursor position.
7557 let snapshot = self.buffer.read(cx).snapshot(cx);
7558 let cursor_offset = self.selections.newest::<usize>(cx).head();
7559 let insertion = edits.iter().find_map(|(range, text)| {
7560 let range = range.to_offset(&snapshot);
7561 if range.is_empty() && range.start == cursor_offset {
7562 Some(text)
7563 } else {
7564 None
7565 }
7566 });
7567
7568 if let Some(text) = insertion {
7569 let mut partial_completion = text
7570 .chars()
7571 .by_ref()
7572 .take_while(|c| c.is_alphabetic())
7573 .collect::<String>();
7574 if partial_completion.is_empty() {
7575 partial_completion = text
7576 .chars()
7577 .by_ref()
7578 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7579 .collect::<String>();
7580 }
7581
7582 cx.emit(EditorEvent::InputHandled {
7583 utf16_range_to_replace: None,
7584 text: partial_completion.clone().into(),
7585 });
7586
7587 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7588
7589 self.refresh_edit_prediction(true, true, window, cx);
7590 cx.notify();
7591 } else {
7592 self.accept_edit_prediction(&Default::default(), window, cx);
7593 }
7594 }
7595 }
7596 }
7597
7598 fn discard_edit_prediction(
7599 &mut self,
7600 should_report_edit_prediction_event: bool,
7601 cx: &mut Context<Self>,
7602 ) -> bool {
7603 if should_report_edit_prediction_event {
7604 let completion_id = self
7605 .active_edit_prediction
7606 .as_ref()
7607 .and_then(|active_completion| active_completion.completion_id.clone());
7608
7609 self.report_edit_prediction_event(completion_id, false, cx);
7610 }
7611
7612 if let Some(provider) = self.edit_prediction_provider() {
7613 provider.discard(cx);
7614 }
7615
7616 self.take_active_edit_prediction(cx)
7617 }
7618
7619 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7620 let Some(provider) = self.edit_prediction_provider() else {
7621 return;
7622 };
7623
7624 let Some((_, buffer, _)) = self
7625 .buffer
7626 .read(cx)
7627 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7628 else {
7629 return;
7630 };
7631
7632 let extension = buffer
7633 .read(cx)
7634 .file()
7635 .and_then(|file| Some(file.path().extension()?.to_string()));
7636
7637 let event_type = match accepted {
7638 true => "Edit Prediction Accepted",
7639 false => "Edit Prediction Discarded",
7640 };
7641 telemetry::event!(
7642 event_type,
7643 provider = provider.name(),
7644 prediction_id = id,
7645 suggestion_accepted = accepted,
7646 file_extension = extension,
7647 );
7648 }
7649
7650 fn open_editor_at_anchor(
7651 snapshot: &language::BufferSnapshot,
7652 target: language::Anchor,
7653 workspace: &Entity<Workspace>,
7654 window: &mut Window,
7655 cx: &mut App,
7656 ) -> Task<Result<()>> {
7657 workspace.update(cx, |workspace, cx| {
7658 let path = snapshot.file().map(|file| file.full_path(cx));
7659 let Some(path) =
7660 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7661 else {
7662 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7663 };
7664 let target = text::ToPoint::to_point(&target, snapshot);
7665 let item = workspace.open_path(path, None, true, window, cx);
7666 window.spawn(cx, async move |cx| {
7667 let Some(editor) = item.await?.downcast::<Editor>() else {
7668 return Ok(());
7669 };
7670 editor
7671 .update_in(cx, |editor, window, cx| {
7672 editor.go_to_singleton_buffer_point(target, window, cx);
7673 })
7674 .ok();
7675 anyhow::Ok(())
7676 })
7677 })
7678 }
7679
7680 pub fn has_active_edit_prediction(&self) -> bool {
7681 self.active_edit_prediction.is_some()
7682 }
7683
7684 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7685 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7686 return false;
7687 };
7688
7689 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7690 self.clear_highlights::<EditPredictionHighlight>(cx);
7691 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7692 true
7693 }
7694
7695 /// Returns true when we're displaying the edit prediction popover below the cursor
7696 /// like we are not previewing and the LSP autocomplete menu is visible
7697 /// or we are in `when_holding_modifier` mode.
7698 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7699 if self.edit_prediction_preview_is_active()
7700 || !self.show_edit_predictions_in_menu()
7701 || !self.edit_predictions_enabled()
7702 {
7703 return false;
7704 }
7705
7706 if self.has_visible_completions_menu() {
7707 return true;
7708 }
7709
7710 has_completion && self.edit_prediction_requires_modifier()
7711 }
7712
7713 fn handle_modifiers_changed(
7714 &mut self,
7715 modifiers: Modifiers,
7716 position_map: &PositionMap,
7717 window: &mut Window,
7718 cx: &mut Context<Self>,
7719 ) {
7720 if self.show_edit_predictions_in_menu() {
7721 self.update_edit_prediction_preview(&modifiers, window, cx);
7722 }
7723
7724 self.update_selection_mode(&modifiers, position_map, window, cx);
7725
7726 let mouse_position = window.mouse_position();
7727 if !position_map.text_hitbox.is_hovered(window) {
7728 return;
7729 }
7730
7731 self.update_hovered_link(
7732 position_map.point_for_position(mouse_position),
7733 &position_map.snapshot,
7734 modifiers,
7735 window,
7736 cx,
7737 )
7738 }
7739
7740 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7741 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7742 if invert {
7743 match multi_cursor_setting {
7744 MultiCursorModifier::Alt => modifiers.alt,
7745 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7746 }
7747 } else {
7748 match multi_cursor_setting {
7749 MultiCursorModifier::Alt => modifiers.secondary(),
7750 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7751 }
7752 }
7753 }
7754
7755 fn columnar_selection_mode(
7756 modifiers: &Modifiers,
7757 cx: &mut Context<Self>,
7758 ) -> Option<ColumnarMode> {
7759 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7760 if Self::multi_cursor_modifier(false, modifiers, cx) {
7761 Some(ColumnarMode::FromMouse)
7762 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7763 Some(ColumnarMode::FromSelection)
7764 } else {
7765 None
7766 }
7767 } else {
7768 None
7769 }
7770 }
7771
7772 fn update_selection_mode(
7773 &mut self,
7774 modifiers: &Modifiers,
7775 position_map: &PositionMap,
7776 window: &mut Window,
7777 cx: &mut Context<Self>,
7778 ) {
7779 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7780 return;
7781 };
7782 if self.selections.pending_anchor().is_none() {
7783 return;
7784 }
7785
7786 let mouse_position = window.mouse_position();
7787 let point_for_position = position_map.point_for_position(mouse_position);
7788 let position = point_for_position.previous_valid;
7789
7790 self.select(
7791 SelectPhase::BeginColumnar {
7792 position,
7793 reset: false,
7794 mode,
7795 goal_column: point_for_position.exact_unclipped.column(),
7796 },
7797 window,
7798 cx,
7799 );
7800 }
7801
7802 fn update_edit_prediction_preview(
7803 &mut self,
7804 modifiers: &Modifiers,
7805 window: &mut Window,
7806 cx: &mut Context<Self>,
7807 ) {
7808 let mut modifiers_held = false;
7809 if let Some(accept_keystroke) = self
7810 .accept_edit_prediction_keybind(false, window, cx)
7811 .keystroke()
7812 {
7813 modifiers_held = modifiers_held
7814 || (accept_keystroke.modifiers() == modifiers
7815 && accept_keystroke.modifiers().modified());
7816 };
7817 if let Some(accept_partial_keystroke) = self
7818 .accept_edit_prediction_keybind(true, window, cx)
7819 .keystroke()
7820 {
7821 modifiers_held = modifiers_held
7822 || (accept_partial_keystroke.modifiers() == modifiers
7823 && accept_partial_keystroke.modifiers().modified());
7824 }
7825
7826 if modifiers_held {
7827 if matches!(
7828 self.edit_prediction_preview,
7829 EditPredictionPreview::Inactive { .. }
7830 ) {
7831 self.edit_prediction_preview = EditPredictionPreview::Active {
7832 previous_scroll_position: None,
7833 since: Instant::now(),
7834 };
7835
7836 self.update_visible_edit_prediction(window, cx);
7837 cx.notify();
7838 }
7839 } else if let EditPredictionPreview::Active {
7840 previous_scroll_position,
7841 since,
7842 } = self.edit_prediction_preview
7843 {
7844 if let (Some(previous_scroll_position), Some(position_map)) =
7845 (previous_scroll_position, self.last_position_map.as_ref())
7846 {
7847 self.set_scroll_position(
7848 previous_scroll_position
7849 .scroll_position(&position_map.snapshot.display_snapshot),
7850 window,
7851 cx,
7852 );
7853 }
7854
7855 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7856 released_too_fast: since.elapsed() < Duration::from_millis(200),
7857 };
7858 self.clear_row_highlights::<EditPredictionPreview>();
7859 self.update_visible_edit_prediction(window, cx);
7860 cx.notify();
7861 }
7862 }
7863
7864 fn update_visible_edit_prediction(
7865 &mut self,
7866 _window: &mut Window,
7867 cx: &mut Context<Self>,
7868 ) -> Option<()> {
7869 if DisableAiSettings::get_global(cx).disable_ai {
7870 return None;
7871 }
7872
7873 if self.ime_transaction.is_some() {
7874 self.discard_edit_prediction(false, cx);
7875 return None;
7876 }
7877
7878 let selection = self.selections.newest_anchor();
7879 let cursor = selection.head();
7880 let multibuffer = self.buffer.read(cx).snapshot(cx);
7881 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7882 let excerpt_id = cursor.excerpt_id;
7883
7884 let show_in_menu = self.show_edit_predictions_in_menu();
7885 let completions_menu_has_precedence = !show_in_menu
7886 && (self.context_menu.borrow().is_some()
7887 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7888
7889 if completions_menu_has_precedence
7890 || !offset_selection.is_empty()
7891 || self
7892 .active_edit_prediction
7893 .as_ref()
7894 .is_some_and(|completion| {
7895 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7896 return false;
7897 };
7898 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7899 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7900 !invalidation_range.contains(&offset_selection.head())
7901 })
7902 {
7903 self.discard_edit_prediction(false, cx);
7904 return None;
7905 }
7906
7907 self.take_active_edit_prediction(cx);
7908 let Some(provider) = self.edit_prediction_provider() else {
7909 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7910 return None;
7911 };
7912
7913 let (buffer, cursor_buffer_position) =
7914 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7915
7916 self.edit_prediction_settings =
7917 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7918
7919 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7920
7921 if self.edit_prediction_indent_conflict {
7922 let cursor_point = cursor.to_point(&multibuffer);
7923
7924 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7925
7926 if let Some((_, indent)) = indents.iter().next()
7927 && indent.len == cursor_point.column
7928 {
7929 self.edit_prediction_indent_conflict = false;
7930 }
7931 }
7932
7933 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7934
7935 let (completion_id, edits, edit_preview) = match edit_prediction {
7936 edit_prediction::EditPrediction::Local {
7937 id,
7938 edits,
7939 edit_preview,
7940 } => (id, edits, edit_preview),
7941 edit_prediction::EditPrediction::Jump {
7942 id,
7943 snapshot,
7944 target,
7945 } => {
7946 self.stale_edit_prediction_in_menu = None;
7947 self.active_edit_prediction = Some(EditPredictionState {
7948 inlay_ids: vec![],
7949 completion: EditPrediction::MoveOutside { snapshot, target },
7950 completion_id: id,
7951 invalidation_range: None,
7952 });
7953 cx.notify();
7954 return Some(());
7955 }
7956 };
7957
7958 let edits = edits
7959 .into_iter()
7960 .flat_map(|(range, new_text)| {
7961 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7962 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7963 Some((start..end, new_text))
7964 })
7965 .collect::<Vec<_>>();
7966 if edits.is_empty() {
7967 return None;
7968 }
7969
7970 let first_edit_start = edits.first().unwrap().0.start;
7971 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7972 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7973
7974 let last_edit_end = edits.last().unwrap().0.end;
7975 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7976 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7977
7978 let cursor_row = cursor.to_point(&multibuffer).row;
7979
7980 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7981
7982 let mut inlay_ids = Vec::new();
7983 let invalidation_row_range;
7984 let move_invalidation_row_range = if cursor_row < edit_start_row {
7985 Some(cursor_row..edit_end_row)
7986 } else if cursor_row > edit_end_row {
7987 Some(edit_start_row..cursor_row)
7988 } else {
7989 None
7990 };
7991 let supports_jump = self
7992 .edit_prediction_provider
7993 .as_ref()
7994 .map(|provider| provider.provider.supports_jump_to_edit())
7995 .unwrap_or(true);
7996
7997 let is_move = supports_jump
7998 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7999 let completion = if is_move {
8000 invalidation_row_range =
8001 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8002 let target = first_edit_start;
8003 EditPrediction::MoveWithin { target, snapshot }
8004 } else {
8005 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8006 && !self.edit_predictions_hidden_for_vim_mode;
8007
8008 if show_completions_in_buffer {
8009 if edits
8010 .iter()
8011 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8012 {
8013 let mut inlays = Vec::new();
8014 for (range, new_text) in &edits {
8015 let inlay = Inlay::edit_prediction(
8016 post_inc(&mut self.next_inlay_id),
8017 range.start,
8018 new_text.as_str(),
8019 );
8020 inlay_ids.push(inlay.id);
8021 inlays.push(inlay);
8022 }
8023
8024 self.splice_inlays(&[], inlays, cx);
8025 } else {
8026 let background_color = cx.theme().status().deleted_background;
8027 self.highlight_text::<EditPredictionHighlight>(
8028 edits.iter().map(|(range, _)| range.clone()).collect(),
8029 HighlightStyle {
8030 background_color: Some(background_color),
8031 ..Default::default()
8032 },
8033 cx,
8034 );
8035 }
8036 }
8037
8038 invalidation_row_range = edit_start_row..edit_end_row;
8039
8040 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8041 if provider.show_tab_accept_marker() {
8042 EditDisplayMode::TabAccept
8043 } else {
8044 EditDisplayMode::Inline
8045 }
8046 } else {
8047 EditDisplayMode::DiffPopover
8048 };
8049
8050 EditPrediction::Edit {
8051 edits,
8052 edit_preview,
8053 display_mode,
8054 snapshot,
8055 }
8056 };
8057
8058 let invalidation_range = multibuffer
8059 .anchor_before(Point::new(invalidation_row_range.start, 0))
8060 ..multibuffer.anchor_after(Point::new(
8061 invalidation_row_range.end,
8062 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8063 ));
8064
8065 self.stale_edit_prediction_in_menu = None;
8066 self.active_edit_prediction = Some(EditPredictionState {
8067 inlay_ids,
8068 completion,
8069 completion_id,
8070 invalidation_range: Some(invalidation_range),
8071 });
8072
8073 cx.notify();
8074
8075 Some(())
8076 }
8077
8078 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8079 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8080 }
8081
8082 fn clear_tasks(&mut self) {
8083 self.tasks.clear()
8084 }
8085
8086 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8087 if self.tasks.insert(key, value).is_some() {
8088 // This case should hopefully be rare, but just in case...
8089 log::error!(
8090 "multiple different run targets found on a single line, only the last target will be rendered"
8091 )
8092 }
8093 }
8094
8095 /// Get all display points of breakpoints that will be rendered within editor
8096 ///
8097 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8098 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8099 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8100 fn active_breakpoints(
8101 &self,
8102 range: Range<DisplayRow>,
8103 window: &mut Window,
8104 cx: &mut Context<Self>,
8105 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8106 let mut breakpoint_display_points = HashMap::default();
8107
8108 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8109 return breakpoint_display_points;
8110 };
8111
8112 let snapshot = self.snapshot(window, cx);
8113
8114 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
8115 let Some(project) = self.project() else {
8116 return breakpoint_display_points;
8117 };
8118
8119 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8120 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8121
8122 for (buffer_snapshot, range, excerpt_id) in
8123 multi_buffer_snapshot.range_to_buffer_ranges(range)
8124 {
8125 let Some(buffer) = project
8126 .read(cx)
8127 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8128 else {
8129 continue;
8130 };
8131 let breakpoints = breakpoint_store.read(cx).breakpoints(
8132 &buffer,
8133 Some(
8134 buffer_snapshot.anchor_before(range.start)
8135 ..buffer_snapshot.anchor_after(range.end),
8136 ),
8137 buffer_snapshot,
8138 cx,
8139 );
8140 for (breakpoint, state) in breakpoints {
8141 let multi_buffer_anchor =
8142 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8143 let position = multi_buffer_anchor
8144 .to_point(multi_buffer_snapshot)
8145 .to_display_point(&snapshot);
8146
8147 breakpoint_display_points.insert(
8148 position.row(),
8149 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8150 );
8151 }
8152 }
8153
8154 breakpoint_display_points
8155 }
8156
8157 fn breakpoint_context_menu(
8158 &self,
8159 anchor: Anchor,
8160 window: &mut Window,
8161 cx: &mut Context<Self>,
8162 ) -> Entity<ui::ContextMenu> {
8163 let weak_editor = cx.weak_entity();
8164 let focus_handle = self.focus_handle(cx);
8165
8166 let row = self
8167 .buffer
8168 .read(cx)
8169 .snapshot(cx)
8170 .summary_for_anchor::<Point>(&anchor)
8171 .row;
8172
8173 let breakpoint = self
8174 .breakpoint_at_row(row, window, cx)
8175 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8176
8177 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8178 "Edit Log Breakpoint"
8179 } else {
8180 "Set Log Breakpoint"
8181 };
8182
8183 let condition_breakpoint_msg = if breakpoint
8184 .as_ref()
8185 .is_some_and(|bp| bp.1.condition.is_some())
8186 {
8187 "Edit Condition Breakpoint"
8188 } else {
8189 "Set Condition Breakpoint"
8190 };
8191
8192 let hit_condition_breakpoint_msg = if breakpoint
8193 .as_ref()
8194 .is_some_and(|bp| bp.1.hit_condition.is_some())
8195 {
8196 "Edit Hit Condition Breakpoint"
8197 } else {
8198 "Set Hit Condition Breakpoint"
8199 };
8200
8201 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8202 "Unset Breakpoint"
8203 } else {
8204 "Set Breakpoint"
8205 };
8206
8207 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8208
8209 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8210 BreakpointState::Enabled => Some("Disable"),
8211 BreakpointState::Disabled => Some("Enable"),
8212 });
8213
8214 let (anchor, breakpoint) =
8215 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8216
8217 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8218 menu.on_blur_subscription(Subscription::new(|| {}))
8219 .context(focus_handle)
8220 .when(run_to_cursor, |this| {
8221 let weak_editor = weak_editor.clone();
8222 this.entry("Run to cursor", None, move |window, cx| {
8223 weak_editor
8224 .update(cx, |editor, cx| {
8225 editor.change_selections(
8226 SelectionEffects::no_scroll(),
8227 window,
8228 cx,
8229 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8230 );
8231 })
8232 .ok();
8233
8234 window.dispatch_action(Box::new(RunToCursor), cx);
8235 })
8236 .separator()
8237 })
8238 .when_some(toggle_state_msg, |this, msg| {
8239 this.entry(msg, None, {
8240 let weak_editor = weak_editor.clone();
8241 let breakpoint = breakpoint.clone();
8242 move |_window, cx| {
8243 weak_editor
8244 .update(cx, |this, cx| {
8245 this.edit_breakpoint_at_anchor(
8246 anchor,
8247 breakpoint.as_ref().clone(),
8248 BreakpointEditAction::InvertState,
8249 cx,
8250 );
8251 })
8252 .log_err();
8253 }
8254 })
8255 })
8256 .entry(set_breakpoint_msg, None, {
8257 let weak_editor = weak_editor.clone();
8258 let breakpoint = breakpoint.clone();
8259 move |_window, cx| {
8260 weak_editor
8261 .update(cx, |this, cx| {
8262 this.edit_breakpoint_at_anchor(
8263 anchor,
8264 breakpoint.as_ref().clone(),
8265 BreakpointEditAction::Toggle,
8266 cx,
8267 );
8268 })
8269 .log_err();
8270 }
8271 })
8272 .entry(log_breakpoint_msg, None, {
8273 let breakpoint = breakpoint.clone();
8274 let weak_editor = weak_editor.clone();
8275 move |window, cx| {
8276 weak_editor
8277 .update(cx, |this, cx| {
8278 this.add_edit_breakpoint_block(
8279 anchor,
8280 breakpoint.as_ref(),
8281 BreakpointPromptEditAction::Log,
8282 window,
8283 cx,
8284 );
8285 })
8286 .log_err();
8287 }
8288 })
8289 .entry(condition_breakpoint_msg, None, {
8290 let breakpoint = breakpoint.clone();
8291 let weak_editor = weak_editor.clone();
8292 move |window, cx| {
8293 weak_editor
8294 .update(cx, |this, cx| {
8295 this.add_edit_breakpoint_block(
8296 anchor,
8297 breakpoint.as_ref(),
8298 BreakpointPromptEditAction::Condition,
8299 window,
8300 cx,
8301 );
8302 })
8303 .log_err();
8304 }
8305 })
8306 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8307 weak_editor
8308 .update(cx, |this, cx| {
8309 this.add_edit_breakpoint_block(
8310 anchor,
8311 breakpoint.as_ref(),
8312 BreakpointPromptEditAction::HitCondition,
8313 window,
8314 cx,
8315 );
8316 })
8317 .log_err();
8318 })
8319 })
8320 }
8321
8322 fn render_breakpoint(
8323 &self,
8324 position: Anchor,
8325 row: DisplayRow,
8326 breakpoint: &Breakpoint,
8327 state: Option<BreakpointSessionState>,
8328 cx: &mut Context<Self>,
8329 ) -> IconButton {
8330 let is_rejected = state.is_some_and(|s| !s.verified);
8331 // Is it a breakpoint that shows up when hovering over gutter?
8332 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8333 (false, false),
8334 |PhantomBreakpointIndicator {
8335 is_active,
8336 display_row,
8337 collides_with_existing_breakpoint,
8338 }| {
8339 (
8340 is_active && display_row == row,
8341 collides_with_existing_breakpoint,
8342 )
8343 },
8344 );
8345
8346 let (color, icon) = {
8347 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8348 (false, false) => ui::IconName::DebugBreakpoint,
8349 (true, false) => ui::IconName::DebugLogBreakpoint,
8350 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8351 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8352 };
8353
8354 let color = if is_phantom {
8355 Color::Hint
8356 } else if is_rejected {
8357 Color::Disabled
8358 } else {
8359 Color::Debugger
8360 };
8361
8362 (color, icon)
8363 };
8364
8365 let breakpoint = Arc::from(breakpoint.clone());
8366
8367 let alt_as_text = gpui::Keystroke {
8368 modifiers: Modifiers::secondary_key(),
8369 ..Default::default()
8370 };
8371 let primary_action_text = if breakpoint.is_disabled() {
8372 "Enable breakpoint"
8373 } else if is_phantom && !collides_with_existing {
8374 "Set breakpoint"
8375 } else {
8376 "Unset breakpoint"
8377 };
8378 let focus_handle = self.focus_handle.clone();
8379
8380 let meta = if is_rejected {
8381 SharedString::from("No executable code is associated with this line.")
8382 } else if collides_with_existing && !breakpoint.is_disabled() {
8383 SharedString::from(format!(
8384 "{alt_as_text}-click to disable,\nright-click for more options."
8385 ))
8386 } else {
8387 SharedString::from("Right-click for more options.")
8388 };
8389 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8390 .icon_size(IconSize::XSmall)
8391 .size(ui::ButtonSize::None)
8392 .when(is_rejected, |this| {
8393 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8394 })
8395 .icon_color(color)
8396 .style(ButtonStyle::Transparent)
8397 .on_click(cx.listener({
8398 move |editor, event: &ClickEvent, window, cx| {
8399 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8400 BreakpointEditAction::InvertState
8401 } else {
8402 BreakpointEditAction::Toggle
8403 };
8404
8405 window.focus(&editor.focus_handle(cx));
8406 editor.edit_breakpoint_at_anchor(
8407 position,
8408 breakpoint.as_ref().clone(),
8409 edit_action,
8410 cx,
8411 );
8412 }
8413 }))
8414 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8415 editor.set_breakpoint_context_menu(
8416 row,
8417 Some(position),
8418 event.position(),
8419 window,
8420 cx,
8421 );
8422 }))
8423 .tooltip(move |window, cx| {
8424 Tooltip::with_meta_in(
8425 primary_action_text,
8426 Some(&ToggleBreakpoint),
8427 meta.clone(),
8428 &focus_handle,
8429 window,
8430 cx,
8431 )
8432 })
8433 }
8434
8435 fn build_tasks_context(
8436 project: &Entity<Project>,
8437 buffer: &Entity<Buffer>,
8438 buffer_row: u32,
8439 tasks: &Arc<RunnableTasks>,
8440 cx: &mut Context<Self>,
8441 ) -> Task<Option<task::TaskContext>> {
8442 let position = Point::new(buffer_row, tasks.column);
8443 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8444 let location = Location {
8445 buffer: buffer.clone(),
8446 range: range_start..range_start,
8447 };
8448 // Fill in the environmental variables from the tree-sitter captures
8449 let mut captured_task_variables = TaskVariables::default();
8450 for (capture_name, value) in tasks.extra_variables.clone() {
8451 captured_task_variables.insert(
8452 task::VariableName::Custom(capture_name.into()),
8453 value.clone(),
8454 );
8455 }
8456 project.update(cx, |project, cx| {
8457 project.task_store().update(cx, |task_store, cx| {
8458 task_store.task_context_for_location(captured_task_variables, location, cx)
8459 })
8460 })
8461 }
8462
8463 pub fn spawn_nearest_task(
8464 &mut self,
8465 action: &SpawnNearestTask,
8466 window: &mut Window,
8467 cx: &mut Context<Self>,
8468 ) {
8469 let Some((workspace, _)) = self.workspace.clone() else {
8470 return;
8471 };
8472 let Some(project) = self.project.clone() else {
8473 return;
8474 };
8475
8476 // Try to find a closest, enclosing node using tree-sitter that has a task
8477 let Some((buffer, buffer_row, tasks)) = self
8478 .find_enclosing_node_task(cx)
8479 // Or find the task that's closest in row-distance.
8480 .or_else(|| self.find_closest_task(cx))
8481 else {
8482 return;
8483 };
8484
8485 let reveal_strategy = action.reveal;
8486 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8487 cx.spawn_in(window, async move |_, cx| {
8488 let context = task_context.await?;
8489 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8490
8491 let resolved = &mut resolved_task.resolved;
8492 resolved.reveal = reveal_strategy;
8493
8494 workspace
8495 .update_in(cx, |workspace, window, cx| {
8496 workspace.schedule_resolved_task(
8497 task_source_kind,
8498 resolved_task,
8499 false,
8500 window,
8501 cx,
8502 );
8503 })
8504 .ok()
8505 })
8506 .detach();
8507 }
8508
8509 fn find_closest_task(
8510 &mut self,
8511 cx: &mut Context<Self>,
8512 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8513 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8514
8515 let ((buffer_id, row), tasks) = self
8516 .tasks
8517 .iter()
8518 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8519
8520 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8521 let tasks = Arc::new(tasks.to_owned());
8522 Some((buffer, *row, tasks))
8523 }
8524
8525 fn find_enclosing_node_task(
8526 &mut self,
8527 cx: &mut Context<Self>,
8528 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8529 let snapshot = self.buffer.read(cx).snapshot(cx);
8530 let offset = self.selections.newest::<usize>(cx).head();
8531 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8532 let buffer_id = excerpt.buffer().remote_id();
8533
8534 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8535 let mut cursor = layer.node().walk();
8536
8537 while cursor.goto_first_child_for_byte(offset).is_some() {
8538 if cursor.node().end_byte() == offset {
8539 cursor.goto_next_sibling();
8540 }
8541 }
8542
8543 // Ascend to the smallest ancestor that contains the range and has a task.
8544 loop {
8545 let node = cursor.node();
8546 let node_range = node.byte_range();
8547 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8548
8549 // Check if this node contains our offset
8550 if node_range.start <= offset && node_range.end >= offset {
8551 // If it contains offset, check for task
8552 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8553 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8554 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8555 }
8556 }
8557
8558 if !cursor.goto_parent() {
8559 break;
8560 }
8561 }
8562 None
8563 }
8564
8565 fn render_run_indicator(
8566 &self,
8567 _style: &EditorStyle,
8568 is_active: bool,
8569 row: DisplayRow,
8570 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8571 cx: &mut Context<Self>,
8572 ) -> IconButton {
8573 let color = Color::Muted;
8574 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8575
8576 IconButton::new(
8577 ("run_indicator", row.0 as usize),
8578 ui::IconName::PlayOutlined,
8579 )
8580 .shape(ui::IconButtonShape::Square)
8581 .icon_size(IconSize::XSmall)
8582 .icon_color(color)
8583 .toggle_state(is_active)
8584 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8585 let quick_launch = match e {
8586 ClickEvent::Keyboard(_) => true,
8587 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8588 };
8589
8590 window.focus(&editor.focus_handle(cx));
8591 editor.toggle_code_actions(
8592 &ToggleCodeActions {
8593 deployed_from: Some(CodeActionSource::RunMenu(row)),
8594 quick_launch,
8595 },
8596 window,
8597 cx,
8598 );
8599 }))
8600 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8601 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8602 }))
8603 }
8604
8605 pub fn context_menu_visible(&self) -> bool {
8606 !self.edit_prediction_preview_is_active()
8607 && self
8608 .context_menu
8609 .borrow()
8610 .as_ref()
8611 .is_some_and(|menu| menu.visible())
8612 }
8613
8614 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8615 self.context_menu
8616 .borrow()
8617 .as_ref()
8618 .map(|menu| menu.origin())
8619 }
8620
8621 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8622 self.context_menu_options = Some(options);
8623 }
8624
8625 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8626 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8627
8628 fn render_edit_prediction_popover(
8629 &mut self,
8630 text_bounds: &Bounds<Pixels>,
8631 content_origin: gpui::Point<Pixels>,
8632 right_margin: Pixels,
8633 editor_snapshot: &EditorSnapshot,
8634 visible_row_range: Range<DisplayRow>,
8635 scroll_top: ScrollOffset,
8636 scroll_bottom: ScrollOffset,
8637 line_layouts: &[LineWithInvisibles],
8638 line_height: Pixels,
8639 scroll_position: gpui::Point<ScrollOffset>,
8640 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8641 newest_selection_head: Option<DisplayPoint>,
8642 editor_width: Pixels,
8643 style: &EditorStyle,
8644 window: &mut Window,
8645 cx: &mut App,
8646 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8647 if self.mode().is_minimap() {
8648 return None;
8649 }
8650 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8651
8652 if self.edit_prediction_visible_in_cursor_popover(true) {
8653 return None;
8654 }
8655
8656 match &active_edit_prediction.completion {
8657 EditPrediction::MoveWithin { target, .. } => {
8658 let target_display_point = target.to_display_point(editor_snapshot);
8659
8660 if self.edit_prediction_requires_modifier() {
8661 if !self.edit_prediction_preview_is_active() {
8662 return None;
8663 }
8664
8665 self.render_edit_prediction_modifier_jump_popover(
8666 text_bounds,
8667 content_origin,
8668 visible_row_range,
8669 line_layouts,
8670 line_height,
8671 scroll_pixel_position,
8672 newest_selection_head,
8673 target_display_point,
8674 window,
8675 cx,
8676 )
8677 } else {
8678 self.render_edit_prediction_eager_jump_popover(
8679 text_bounds,
8680 content_origin,
8681 editor_snapshot,
8682 visible_row_range,
8683 scroll_top,
8684 scroll_bottom,
8685 line_height,
8686 scroll_pixel_position,
8687 target_display_point,
8688 editor_width,
8689 window,
8690 cx,
8691 )
8692 }
8693 }
8694 EditPrediction::Edit {
8695 display_mode: EditDisplayMode::Inline,
8696 ..
8697 } => None,
8698 EditPrediction::Edit {
8699 display_mode: EditDisplayMode::TabAccept,
8700 edits,
8701 ..
8702 } => {
8703 let range = &edits.first()?.0;
8704 let target_display_point = range.end.to_display_point(editor_snapshot);
8705
8706 self.render_edit_prediction_end_of_line_popover(
8707 "Accept",
8708 editor_snapshot,
8709 visible_row_range,
8710 target_display_point,
8711 line_height,
8712 scroll_pixel_position,
8713 content_origin,
8714 editor_width,
8715 window,
8716 cx,
8717 )
8718 }
8719 EditPrediction::Edit {
8720 edits,
8721 edit_preview,
8722 display_mode: EditDisplayMode::DiffPopover,
8723 snapshot,
8724 } => self.render_edit_prediction_diff_popover(
8725 text_bounds,
8726 content_origin,
8727 right_margin,
8728 editor_snapshot,
8729 visible_row_range,
8730 line_layouts,
8731 line_height,
8732 scroll_position,
8733 scroll_pixel_position,
8734 newest_selection_head,
8735 editor_width,
8736 style,
8737 edits,
8738 edit_preview,
8739 snapshot,
8740 window,
8741 cx,
8742 ),
8743 EditPrediction::MoveOutside { snapshot, .. } => {
8744 let file_name = snapshot
8745 .file()
8746 .map(|file| file.file_name(cx))
8747 .unwrap_or("untitled");
8748 let mut element = self
8749 .render_edit_prediction_line_popover(
8750 format!("Jump to {file_name}"),
8751 Some(IconName::ZedPredict),
8752 window,
8753 cx,
8754 )
8755 .into_any();
8756
8757 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8758 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8759 let origin_y = text_bounds.size.height - size.height - px(30.);
8760 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8761 element.prepaint_at(origin, window, cx);
8762
8763 Some((element, origin))
8764 }
8765 }
8766 }
8767
8768 fn render_edit_prediction_modifier_jump_popover(
8769 &mut self,
8770 text_bounds: &Bounds<Pixels>,
8771 content_origin: gpui::Point<Pixels>,
8772 visible_row_range: Range<DisplayRow>,
8773 line_layouts: &[LineWithInvisibles],
8774 line_height: Pixels,
8775 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8776 newest_selection_head: Option<DisplayPoint>,
8777 target_display_point: DisplayPoint,
8778 window: &mut Window,
8779 cx: &mut App,
8780 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8781 let scrolled_content_origin =
8782 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8783
8784 const SCROLL_PADDING_Y: Pixels = px(12.);
8785
8786 if target_display_point.row() < visible_row_range.start {
8787 return self.render_edit_prediction_scroll_popover(
8788 |_| SCROLL_PADDING_Y,
8789 IconName::ArrowUp,
8790 visible_row_range,
8791 line_layouts,
8792 newest_selection_head,
8793 scrolled_content_origin,
8794 window,
8795 cx,
8796 );
8797 } else if target_display_point.row() >= visible_row_range.end {
8798 return self.render_edit_prediction_scroll_popover(
8799 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8800 IconName::ArrowDown,
8801 visible_row_range,
8802 line_layouts,
8803 newest_selection_head,
8804 scrolled_content_origin,
8805 window,
8806 cx,
8807 );
8808 }
8809
8810 const POLE_WIDTH: Pixels = px(2.);
8811
8812 let line_layout =
8813 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8814 let target_column = target_display_point.column() as usize;
8815
8816 let target_x = line_layout.x_for_index(target_column);
8817 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8818 - scroll_pixel_position.y;
8819
8820 let flag_on_right = target_x < text_bounds.size.width / 2.;
8821
8822 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8823 border_color.l += 0.001;
8824
8825 let mut element = v_flex()
8826 .items_end()
8827 .when(flag_on_right, |el| el.items_start())
8828 .child(if flag_on_right {
8829 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8830 .rounded_bl(px(0.))
8831 .rounded_tl(px(0.))
8832 .border_l_2()
8833 .border_color(border_color)
8834 } else {
8835 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8836 .rounded_br(px(0.))
8837 .rounded_tr(px(0.))
8838 .border_r_2()
8839 .border_color(border_color)
8840 })
8841 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8842 .into_any();
8843
8844 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8845
8846 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8847 - point(
8848 if flag_on_right {
8849 POLE_WIDTH
8850 } else {
8851 size.width - POLE_WIDTH
8852 },
8853 size.height - line_height,
8854 );
8855
8856 origin.x = origin.x.max(content_origin.x);
8857
8858 element.prepaint_at(origin, window, cx);
8859
8860 Some((element, origin))
8861 }
8862
8863 fn render_edit_prediction_scroll_popover(
8864 &mut self,
8865 to_y: impl Fn(Size<Pixels>) -> Pixels,
8866 scroll_icon: IconName,
8867 visible_row_range: Range<DisplayRow>,
8868 line_layouts: &[LineWithInvisibles],
8869 newest_selection_head: Option<DisplayPoint>,
8870 scrolled_content_origin: gpui::Point<Pixels>,
8871 window: &mut Window,
8872 cx: &mut App,
8873 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8874 let mut element = self
8875 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8876 .into_any();
8877
8878 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8879
8880 let cursor = newest_selection_head?;
8881 let cursor_row_layout =
8882 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8883 let cursor_column = cursor.column() as usize;
8884
8885 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8886
8887 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8888
8889 element.prepaint_at(origin, window, cx);
8890 Some((element, origin))
8891 }
8892
8893 fn render_edit_prediction_eager_jump_popover(
8894 &mut self,
8895 text_bounds: &Bounds<Pixels>,
8896 content_origin: gpui::Point<Pixels>,
8897 editor_snapshot: &EditorSnapshot,
8898 visible_row_range: Range<DisplayRow>,
8899 scroll_top: ScrollOffset,
8900 scroll_bottom: ScrollOffset,
8901 line_height: Pixels,
8902 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8903 target_display_point: DisplayPoint,
8904 editor_width: Pixels,
8905 window: &mut Window,
8906 cx: &mut App,
8907 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8908 if target_display_point.row().as_f64() < scroll_top {
8909 let mut element = self
8910 .render_edit_prediction_line_popover(
8911 "Jump to Edit",
8912 Some(IconName::ArrowUp),
8913 window,
8914 cx,
8915 )
8916 .into_any();
8917
8918 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8919 let offset = point(
8920 (text_bounds.size.width - size.width) / 2.,
8921 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8922 );
8923
8924 let origin = text_bounds.origin + offset;
8925 element.prepaint_at(origin, window, cx);
8926 Some((element, origin))
8927 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8928 let mut element = self
8929 .render_edit_prediction_line_popover(
8930 "Jump to Edit",
8931 Some(IconName::ArrowDown),
8932 window,
8933 cx,
8934 )
8935 .into_any();
8936
8937 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8938 let offset = point(
8939 (text_bounds.size.width - size.width) / 2.,
8940 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8941 );
8942
8943 let origin = text_bounds.origin + offset;
8944 element.prepaint_at(origin, window, cx);
8945 Some((element, origin))
8946 } else {
8947 self.render_edit_prediction_end_of_line_popover(
8948 "Jump to Edit",
8949 editor_snapshot,
8950 visible_row_range,
8951 target_display_point,
8952 line_height,
8953 scroll_pixel_position,
8954 content_origin,
8955 editor_width,
8956 window,
8957 cx,
8958 )
8959 }
8960 }
8961
8962 fn render_edit_prediction_end_of_line_popover(
8963 self: &mut Editor,
8964 label: &'static str,
8965 editor_snapshot: &EditorSnapshot,
8966 visible_row_range: Range<DisplayRow>,
8967 target_display_point: DisplayPoint,
8968 line_height: Pixels,
8969 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8970 content_origin: gpui::Point<Pixels>,
8971 editor_width: Pixels,
8972 window: &mut Window,
8973 cx: &mut App,
8974 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8975 let target_line_end = DisplayPoint::new(
8976 target_display_point.row(),
8977 editor_snapshot.line_len(target_display_point.row()),
8978 );
8979
8980 let mut element = self
8981 .render_edit_prediction_line_popover(label, None, window, cx)
8982 .into_any();
8983
8984 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8985
8986 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8987
8988 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8989 let mut origin = start_point
8990 + line_origin
8991 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8992 origin.x = origin.x.max(content_origin.x);
8993
8994 let max_x = content_origin.x + editor_width - size.width;
8995
8996 if origin.x > max_x {
8997 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8998
8999 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9000 origin.y += offset;
9001 IconName::ArrowUp
9002 } else {
9003 origin.y -= offset;
9004 IconName::ArrowDown
9005 };
9006
9007 element = self
9008 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9009 .into_any();
9010
9011 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9012
9013 origin.x = content_origin.x + editor_width - size.width - px(2.);
9014 }
9015
9016 element.prepaint_at(origin, window, cx);
9017 Some((element, origin))
9018 }
9019
9020 fn render_edit_prediction_diff_popover(
9021 self: &Editor,
9022 text_bounds: &Bounds<Pixels>,
9023 content_origin: gpui::Point<Pixels>,
9024 right_margin: Pixels,
9025 editor_snapshot: &EditorSnapshot,
9026 visible_row_range: Range<DisplayRow>,
9027 line_layouts: &[LineWithInvisibles],
9028 line_height: Pixels,
9029 scroll_position: gpui::Point<ScrollOffset>,
9030 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9031 newest_selection_head: Option<DisplayPoint>,
9032 editor_width: Pixels,
9033 style: &EditorStyle,
9034 edits: &Vec<(Range<Anchor>, String)>,
9035 edit_preview: &Option<language::EditPreview>,
9036 snapshot: &language::BufferSnapshot,
9037 window: &mut Window,
9038 cx: &mut App,
9039 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9040 let edit_start = edits
9041 .first()
9042 .unwrap()
9043 .0
9044 .start
9045 .to_display_point(editor_snapshot);
9046 let edit_end = edits
9047 .last()
9048 .unwrap()
9049 .0
9050 .end
9051 .to_display_point(editor_snapshot);
9052
9053 let is_visible = visible_row_range.contains(&edit_start.row())
9054 || visible_row_range.contains(&edit_end.row());
9055 if !is_visible {
9056 return None;
9057 }
9058
9059 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9060 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9061 } else {
9062 // Fallback for providers without edit_preview
9063 crate::edit_prediction_fallback_text(edits, cx)
9064 };
9065
9066 let styled_text = highlighted_edits.to_styled_text(&style.text);
9067 let line_count = highlighted_edits.text.lines().count();
9068
9069 const BORDER_WIDTH: Pixels = px(1.);
9070
9071 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9072 let has_keybind = keybind.is_some();
9073
9074 let mut element = h_flex()
9075 .items_start()
9076 .child(
9077 h_flex()
9078 .bg(cx.theme().colors().editor_background)
9079 .border(BORDER_WIDTH)
9080 .shadow_xs()
9081 .border_color(cx.theme().colors().border)
9082 .rounded_l_lg()
9083 .when(line_count > 1, |el| el.rounded_br_lg())
9084 .pr_1()
9085 .child(styled_text),
9086 )
9087 .child(
9088 h_flex()
9089 .h(line_height + BORDER_WIDTH * 2.)
9090 .px_1p5()
9091 .gap_1()
9092 // Workaround: For some reason, there's a gap if we don't do this
9093 .ml(-BORDER_WIDTH)
9094 .shadow(vec![gpui::BoxShadow {
9095 color: gpui::black().opacity(0.05),
9096 offset: point(px(1.), px(1.)),
9097 blur_radius: px(2.),
9098 spread_radius: px(0.),
9099 }])
9100 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9101 .border(BORDER_WIDTH)
9102 .border_color(cx.theme().colors().border)
9103 .rounded_r_lg()
9104 .id("edit_prediction_diff_popover_keybind")
9105 .when(!has_keybind, |el| {
9106 let status_colors = cx.theme().status();
9107
9108 el.bg(status_colors.error_background)
9109 .border_color(status_colors.error.opacity(0.6))
9110 .child(Icon::new(IconName::Info).color(Color::Error))
9111 .cursor_default()
9112 .hoverable_tooltip(move |_window, cx| {
9113 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9114 })
9115 })
9116 .children(keybind),
9117 )
9118 .into_any();
9119
9120 let longest_row =
9121 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9122 let longest_line_width = if visible_row_range.contains(&longest_row) {
9123 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9124 } else {
9125 layout_line(
9126 longest_row,
9127 editor_snapshot,
9128 style,
9129 editor_width,
9130 |_| false,
9131 window,
9132 cx,
9133 )
9134 .width
9135 };
9136
9137 let viewport_bounds =
9138 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9139 right: -right_margin,
9140 ..Default::default()
9141 });
9142
9143 let x_after_longest = Pixels::from(
9144 ScrollPixelOffset::from(
9145 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9146 ) - scroll_pixel_position.x,
9147 );
9148
9149 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9150
9151 // Fully visible if it can be displayed within the window (allow overlapping other
9152 // panes). However, this is only allowed if the popover starts within text_bounds.
9153 let can_position_to_the_right = x_after_longest < text_bounds.right()
9154 && x_after_longest + element_bounds.width < viewport_bounds.right();
9155
9156 let mut origin = if can_position_to_the_right {
9157 point(
9158 x_after_longest,
9159 text_bounds.origin.y
9160 + Pixels::from(
9161 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9162 - scroll_pixel_position.y,
9163 ),
9164 )
9165 } else {
9166 let cursor_row = newest_selection_head.map(|head| head.row());
9167 let above_edit = edit_start
9168 .row()
9169 .0
9170 .checked_sub(line_count as u32)
9171 .map(DisplayRow);
9172 let below_edit = Some(edit_end.row() + 1);
9173 let above_cursor =
9174 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9175 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9176
9177 // Place the edit popover adjacent to the edit if there is a location
9178 // available that is onscreen and does not obscure the cursor. Otherwise,
9179 // place it adjacent to the cursor.
9180 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9181 .into_iter()
9182 .flatten()
9183 .find(|&start_row| {
9184 let end_row = start_row + line_count as u32;
9185 visible_row_range.contains(&start_row)
9186 && visible_row_range.contains(&end_row)
9187 && cursor_row
9188 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9189 })?;
9190
9191 content_origin
9192 + point(
9193 Pixels::from(-scroll_pixel_position.x),
9194 Pixels::from(
9195 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9196 ),
9197 )
9198 };
9199
9200 origin.x -= BORDER_WIDTH;
9201
9202 window.defer_draw(element, origin, 1);
9203
9204 // Do not return an element, since it will already be drawn due to defer_draw.
9205 None
9206 }
9207
9208 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9209 px(30.)
9210 }
9211
9212 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9213 if self.read_only(cx) {
9214 cx.theme().players().read_only()
9215 } else {
9216 self.style.as_ref().unwrap().local_player
9217 }
9218 }
9219
9220 fn render_edit_prediction_accept_keybind(
9221 &self,
9222 window: &mut Window,
9223 cx: &App,
9224 ) -> Option<AnyElement> {
9225 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9226 let accept_keystroke = accept_binding.keystroke()?;
9227
9228 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9229
9230 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9231 Color::Accent
9232 } else {
9233 Color::Muted
9234 };
9235
9236 h_flex()
9237 .px_0p5()
9238 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9239 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9240 .text_size(TextSize::XSmall.rems(cx))
9241 .child(h_flex().children(ui::render_modifiers(
9242 accept_keystroke.modifiers(),
9243 PlatformStyle::platform(),
9244 Some(modifiers_color),
9245 Some(IconSize::XSmall.rems().into()),
9246 true,
9247 )))
9248 .when(is_platform_style_mac, |parent| {
9249 parent.child(accept_keystroke.key().to_string())
9250 })
9251 .when(!is_platform_style_mac, |parent| {
9252 parent.child(
9253 Key::new(
9254 util::capitalize(accept_keystroke.key()),
9255 Some(Color::Default),
9256 )
9257 .size(Some(IconSize::XSmall.rems().into())),
9258 )
9259 })
9260 .into_any()
9261 .into()
9262 }
9263
9264 fn render_edit_prediction_line_popover(
9265 &self,
9266 label: impl Into<SharedString>,
9267 icon: Option<IconName>,
9268 window: &mut Window,
9269 cx: &App,
9270 ) -> Stateful<Div> {
9271 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9272
9273 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9274 let has_keybind = keybind.is_some();
9275
9276 h_flex()
9277 .id("ep-line-popover")
9278 .py_0p5()
9279 .pl_1()
9280 .pr(padding_right)
9281 .gap_1()
9282 .rounded_md()
9283 .border_1()
9284 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9285 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9286 .shadow_xs()
9287 .when(!has_keybind, |el| {
9288 let status_colors = cx.theme().status();
9289
9290 el.bg(status_colors.error_background)
9291 .border_color(status_colors.error.opacity(0.6))
9292 .pl_2()
9293 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9294 .cursor_default()
9295 .hoverable_tooltip(move |_window, cx| {
9296 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9297 })
9298 })
9299 .children(keybind)
9300 .child(
9301 Label::new(label)
9302 .size(LabelSize::Small)
9303 .when(!has_keybind, |el| {
9304 el.color(cx.theme().status().error.into()).strikethrough()
9305 }),
9306 )
9307 .when(!has_keybind, |el| {
9308 el.child(
9309 h_flex().ml_1().child(
9310 Icon::new(IconName::Info)
9311 .size(IconSize::Small)
9312 .color(cx.theme().status().error.into()),
9313 ),
9314 )
9315 })
9316 .when_some(icon, |element, icon| {
9317 element.child(
9318 div()
9319 .mt(px(1.5))
9320 .child(Icon::new(icon).size(IconSize::Small)),
9321 )
9322 })
9323 }
9324
9325 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9326 let accent_color = cx.theme().colors().text_accent;
9327 let editor_bg_color = cx.theme().colors().editor_background;
9328 editor_bg_color.blend(accent_color.opacity(0.1))
9329 }
9330
9331 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9332 let accent_color = cx.theme().colors().text_accent;
9333 let editor_bg_color = cx.theme().colors().editor_background;
9334 editor_bg_color.blend(accent_color.opacity(0.6))
9335 }
9336 fn get_prediction_provider_icon_name(
9337 provider: &Option<RegisteredEditPredictionProvider>,
9338 ) -> IconName {
9339 match provider {
9340 Some(provider) => match provider.provider.name() {
9341 "copilot" => IconName::Copilot,
9342 "supermaven" => IconName::Supermaven,
9343 _ => IconName::ZedPredict,
9344 },
9345 None => IconName::ZedPredict,
9346 }
9347 }
9348
9349 fn render_edit_prediction_cursor_popover(
9350 &self,
9351 min_width: Pixels,
9352 max_width: Pixels,
9353 cursor_point: Point,
9354 style: &EditorStyle,
9355 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9356 _window: &Window,
9357 cx: &mut Context<Editor>,
9358 ) -> Option<AnyElement> {
9359 let provider = self.edit_prediction_provider.as_ref()?;
9360 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9361
9362 let is_refreshing = provider.provider.is_refreshing(cx);
9363
9364 fn pending_completion_container(icon: IconName) -> Div {
9365 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9366 }
9367
9368 let completion = match &self.active_edit_prediction {
9369 Some(prediction) => {
9370 if !self.has_visible_completions_menu() {
9371 const RADIUS: Pixels = px(6.);
9372 const BORDER_WIDTH: Pixels = px(1.);
9373
9374 return Some(
9375 h_flex()
9376 .elevation_2(cx)
9377 .border(BORDER_WIDTH)
9378 .border_color(cx.theme().colors().border)
9379 .when(accept_keystroke.is_none(), |el| {
9380 el.border_color(cx.theme().status().error)
9381 })
9382 .rounded(RADIUS)
9383 .rounded_tl(px(0.))
9384 .overflow_hidden()
9385 .child(div().px_1p5().child(match &prediction.completion {
9386 EditPrediction::MoveWithin { target, snapshot } => {
9387 use text::ToPoint as _;
9388 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9389 {
9390 Icon::new(IconName::ZedPredictDown)
9391 } else {
9392 Icon::new(IconName::ZedPredictUp)
9393 }
9394 }
9395 EditPrediction::MoveOutside { .. } => {
9396 // TODO [zeta2] custom icon for external jump?
9397 Icon::new(provider_icon)
9398 }
9399 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9400 }))
9401 .child(
9402 h_flex()
9403 .gap_1()
9404 .py_1()
9405 .px_2()
9406 .rounded_r(RADIUS - BORDER_WIDTH)
9407 .border_l_1()
9408 .border_color(cx.theme().colors().border)
9409 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9410 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9411 el.child(
9412 Label::new("Hold")
9413 .size(LabelSize::Small)
9414 .when(accept_keystroke.is_none(), |el| {
9415 el.strikethrough()
9416 })
9417 .line_height_style(LineHeightStyle::UiLabel),
9418 )
9419 })
9420 .id("edit_prediction_cursor_popover_keybind")
9421 .when(accept_keystroke.is_none(), |el| {
9422 let status_colors = cx.theme().status();
9423
9424 el.bg(status_colors.error_background)
9425 .border_color(status_colors.error.opacity(0.6))
9426 .child(Icon::new(IconName::Info).color(Color::Error))
9427 .cursor_default()
9428 .hoverable_tooltip(move |_window, cx| {
9429 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9430 .into()
9431 })
9432 })
9433 .when_some(
9434 accept_keystroke.as_ref(),
9435 |el, accept_keystroke| {
9436 el.child(h_flex().children(ui::render_modifiers(
9437 accept_keystroke.modifiers(),
9438 PlatformStyle::platform(),
9439 Some(Color::Default),
9440 Some(IconSize::XSmall.rems().into()),
9441 false,
9442 )))
9443 },
9444 ),
9445 )
9446 .into_any(),
9447 );
9448 }
9449
9450 self.render_edit_prediction_cursor_popover_preview(
9451 prediction,
9452 cursor_point,
9453 style,
9454 cx,
9455 )?
9456 }
9457
9458 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9459 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9460 stale_completion,
9461 cursor_point,
9462 style,
9463 cx,
9464 )?,
9465
9466 None => pending_completion_container(provider_icon)
9467 .child(Label::new("...").size(LabelSize::Small)),
9468 },
9469
9470 None => pending_completion_container(provider_icon)
9471 .child(Label::new("...").size(LabelSize::Small)),
9472 };
9473
9474 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9475 completion
9476 .with_animation(
9477 "loading-completion",
9478 Animation::new(Duration::from_secs(2))
9479 .repeat()
9480 .with_easing(pulsating_between(0.4, 0.8)),
9481 |label, delta| label.opacity(delta),
9482 )
9483 .into_any_element()
9484 } else {
9485 completion.into_any_element()
9486 };
9487
9488 let has_completion = self.active_edit_prediction.is_some();
9489
9490 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9491 Some(
9492 h_flex()
9493 .min_w(min_width)
9494 .max_w(max_width)
9495 .flex_1()
9496 .elevation_2(cx)
9497 .border_color(cx.theme().colors().border)
9498 .child(
9499 div()
9500 .flex_1()
9501 .py_1()
9502 .px_2()
9503 .overflow_hidden()
9504 .child(completion),
9505 )
9506 .when_some(accept_keystroke, |el, accept_keystroke| {
9507 if !accept_keystroke.modifiers().modified() {
9508 return el;
9509 }
9510
9511 el.child(
9512 h_flex()
9513 .h_full()
9514 .border_l_1()
9515 .rounded_r_lg()
9516 .border_color(cx.theme().colors().border)
9517 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9518 .gap_1()
9519 .py_1()
9520 .px_2()
9521 .child(
9522 h_flex()
9523 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9524 .when(is_platform_style_mac, |parent| parent.gap_1())
9525 .child(h_flex().children(ui::render_modifiers(
9526 accept_keystroke.modifiers(),
9527 PlatformStyle::platform(),
9528 Some(if !has_completion {
9529 Color::Muted
9530 } else {
9531 Color::Default
9532 }),
9533 None,
9534 false,
9535 ))),
9536 )
9537 .child(Label::new("Preview").into_any_element())
9538 .opacity(if has_completion { 1.0 } else { 0.4 }),
9539 )
9540 })
9541 .into_any(),
9542 )
9543 }
9544
9545 fn render_edit_prediction_cursor_popover_preview(
9546 &self,
9547 completion: &EditPredictionState,
9548 cursor_point: Point,
9549 style: &EditorStyle,
9550 cx: &mut Context<Editor>,
9551 ) -> Option<Div> {
9552 use text::ToPoint as _;
9553
9554 fn render_relative_row_jump(
9555 prefix: impl Into<String>,
9556 current_row: u32,
9557 target_row: u32,
9558 ) -> Div {
9559 let (row_diff, arrow) = if target_row < current_row {
9560 (current_row - target_row, IconName::ArrowUp)
9561 } else {
9562 (target_row - current_row, IconName::ArrowDown)
9563 };
9564
9565 h_flex()
9566 .child(
9567 Label::new(format!("{}{}", prefix.into(), row_diff))
9568 .color(Color::Muted)
9569 .size(LabelSize::Small),
9570 )
9571 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9572 }
9573
9574 let supports_jump = self
9575 .edit_prediction_provider
9576 .as_ref()
9577 .map(|provider| provider.provider.supports_jump_to_edit())
9578 .unwrap_or(true);
9579
9580 match &completion.completion {
9581 EditPrediction::MoveWithin {
9582 target, snapshot, ..
9583 } => {
9584 if !supports_jump {
9585 return None;
9586 }
9587
9588 Some(
9589 h_flex()
9590 .px_2()
9591 .gap_2()
9592 .flex_1()
9593 .child(
9594 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9595 Icon::new(IconName::ZedPredictDown)
9596 } else {
9597 Icon::new(IconName::ZedPredictUp)
9598 },
9599 )
9600 .child(Label::new("Jump to Edit")),
9601 )
9602 }
9603 EditPrediction::MoveOutside { snapshot, .. } => {
9604 let file_name = snapshot
9605 .file()
9606 .map(|file| file.file_name(cx))
9607 .unwrap_or("untitled");
9608 Some(
9609 h_flex()
9610 .px_2()
9611 .gap_2()
9612 .flex_1()
9613 .child(Icon::new(IconName::ZedPredict))
9614 .child(Label::new(format!("Jump to {file_name}"))),
9615 )
9616 }
9617 EditPrediction::Edit {
9618 edits,
9619 edit_preview,
9620 snapshot,
9621 display_mode: _,
9622 } => {
9623 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9624
9625 let (highlighted_edits, has_more_lines) =
9626 if let Some(edit_preview) = edit_preview.as_ref() {
9627 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9628 .first_line_preview()
9629 } else {
9630 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9631 };
9632
9633 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9634 .with_default_highlights(&style.text, highlighted_edits.highlights);
9635
9636 let preview = h_flex()
9637 .gap_1()
9638 .min_w_16()
9639 .child(styled_text)
9640 .when(has_more_lines, |parent| parent.child("…"));
9641
9642 let left = if supports_jump && first_edit_row != cursor_point.row {
9643 render_relative_row_jump("", cursor_point.row, first_edit_row)
9644 .into_any_element()
9645 } else {
9646 let icon_name =
9647 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9648 Icon::new(icon_name).into_any_element()
9649 };
9650
9651 Some(
9652 h_flex()
9653 .h_full()
9654 .flex_1()
9655 .gap_2()
9656 .pr_1()
9657 .overflow_x_hidden()
9658 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9659 .child(left)
9660 .child(preview),
9661 )
9662 }
9663 }
9664 }
9665
9666 pub fn render_context_menu(
9667 &self,
9668 style: &EditorStyle,
9669 max_height_in_lines: u32,
9670 window: &mut Window,
9671 cx: &mut Context<Editor>,
9672 ) -> Option<AnyElement> {
9673 let menu = self.context_menu.borrow();
9674 let menu = menu.as_ref()?;
9675 if !menu.visible() {
9676 return None;
9677 };
9678 Some(menu.render(style, max_height_in_lines, window, cx))
9679 }
9680
9681 fn render_context_menu_aside(
9682 &mut self,
9683 max_size: Size<Pixels>,
9684 window: &mut Window,
9685 cx: &mut Context<Editor>,
9686 ) -> Option<AnyElement> {
9687 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9688 if menu.visible() {
9689 menu.render_aside(max_size, window, cx)
9690 } else {
9691 None
9692 }
9693 })
9694 }
9695
9696 fn hide_context_menu(
9697 &mut self,
9698 window: &mut Window,
9699 cx: &mut Context<Self>,
9700 ) -> Option<CodeContextMenu> {
9701 cx.notify();
9702 self.completion_tasks.clear();
9703 let context_menu = self.context_menu.borrow_mut().take();
9704 self.stale_edit_prediction_in_menu.take();
9705 self.update_visible_edit_prediction(window, cx);
9706 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9707 && let Some(completion_provider) = &self.completion_provider
9708 {
9709 completion_provider.selection_changed(None, window, cx);
9710 }
9711 context_menu
9712 }
9713
9714 fn show_snippet_choices(
9715 &mut self,
9716 choices: &Vec<String>,
9717 selection: Range<Anchor>,
9718 cx: &mut Context<Self>,
9719 ) {
9720 let Some((_, buffer, _)) = self
9721 .buffer()
9722 .read(cx)
9723 .excerpt_containing(selection.start, cx)
9724 else {
9725 return;
9726 };
9727 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9728 else {
9729 return;
9730 };
9731 if buffer != end_buffer {
9732 log::error!("expected anchor range to have matching buffer IDs");
9733 return;
9734 }
9735
9736 let id = post_inc(&mut self.next_completion_id);
9737 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9738 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9739 CompletionsMenu::new_snippet_choices(
9740 id,
9741 true,
9742 choices,
9743 selection,
9744 buffer,
9745 snippet_sort_order,
9746 ),
9747 ));
9748 }
9749
9750 pub fn insert_snippet(
9751 &mut self,
9752 insertion_ranges: &[Range<usize>],
9753 snippet: Snippet,
9754 window: &mut Window,
9755 cx: &mut Context<Self>,
9756 ) -> Result<()> {
9757 struct Tabstop<T> {
9758 is_end_tabstop: bool,
9759 ranges: Vec<Range<T>>,
9760 choices: Option<Vec<String>>,
9761 }
9762
9763 let tabstops = self.buffer.update(cx, |buffer, cx| {
9764 let snippet_text: Arc<str> = snippet.text.clone().into();
9765 let edits = insertion_ranges
9766 .iter()
9767 .cloned()
9768 .map(|range| (range, snippet_text.clone()));
9769 let autoindent_mode = AutoindentMode::Block {
9770 original_indent_columns: Vec::new(),
9771 };
9772 buffer.edit(edits, Some(autoindent_mode), cx);
9773
9774 let snapshot = &*buffer.read(cx);
9775 let snippet = &snippet;
9776 snippet
9777 .tabstops
9778 .iter()
9779 .map(|tabstop| {
9780 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9781 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9782 });
9783 let mut tabstop_ranges = tabstop
9784 .ranges
9785 .iter()
9786 .flat_map(|tabstop_range| {
9787 let mut delta = 0_isize;
9788 insertion_ranges.iter().map(move |insertion_range| {
9789 let insertion_start = insertion_range.start as isize + delta;
9790 delta +=
9791 snippet.text.len() as isize - insertion_range.len() as isize;
9792
9793 let start = ((insertion_start + tabstop_range.start) as usize)
9794 .min(snapshot.len());
9795 let end = ((insertion_start + tabstop_range.end) as usize)
9796 .min(snapshot.len());
9797 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9798 })
9799 })
9800 .collect::<Vec<_>>();
9801 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9802
9803 Tabstop {
9804 is_end_tabstop,
9805 ranges: tabstop_ranges,
9806 choices: tabstop.choices.clone(),
9807 }
9808 })
9809 .collect::<Vec<_>>()
9810 });
9811 if let Some(tabstop) = tabstops.first() {
9812 self.change_selections(Default::default(), window, cx, |s| {
9813 // Reverse order so that the first range is the newest created selection.
9814 // Completions will use it and autoscroll will prioritize it.
9815 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9816 });
9817
9818 if let Some(choices) = &tabstop.choices
9819 && let Some(selection) = tabstop.ranges.first()
9820 {
9821 self.show_snippet_choices(choices, selection.clone(), cx)
9822 }
9823
9824 // If we're already at the last tabstop and it's at the end of the snippet,
9825 // we're done, we don't need to keep the state around.
9826 if !tabstop.is_end_tabstop {
9827 let choices = tabstops
9828 .iter()
9829 .map(|tabstop| tabstop.choices.clone())
9830 .collect();
9831
9832 let ranges = tabstops
9833 .into_iter()
9834 .map(|tabstop| tabstop.ranges)
9835 .collect::<Vec<_>>();
9836
9837 self.snippet_stack.push(SnippetState {
9838 active_index: 0,
9839 ranges,
9840 choices,
9841 });
9842 }
9843
9844 // Check whether the just-entered snippet ends with an auto-closable bracket.
9845 if self.autoclose_regions.is_empty() {
9846 let snapshot = self.buffer.read(cx).snapshot(cx);
9847 let mut all_selections = self.selections.all::<Point>(cx);
9848 for selection in &mut all_selections {
9849 let selection_head = selection.head();
9850 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9851 continue;
9852 };
9853
9854 let mut bracket_pair = None;
9855 let max_lookup_length = scope
9856 .brackets()
9857 .map(|(pair, _)| {
9858 pair.start
9859 .as_str()
9860 .chars()
9861 .count()
9862 .max(pair.end.as_str().chars().count())
9863 })
9864 .max();
9865 if let Some(max_lookup_length) = max_lookup_length {
9866 let next_text = snapshot
9867 .chars_at(selection_head)
9868 .take(max_lookup_length)
9869 .collect::<String>();
9870 let prev_text = snapshot
9871 .reversed_chars_at(selection_head)
9872 .take(max_lookup_length)
9873 .collect::<String>();
9874
9875 for (pair, enabled) in scope.brackets() {
9876 if enabled
9877 && pair.close
9878 && prev_text.starts_with(pair.start.as_str())
9879 && next_text.starts_with(pair.end.as_str())
9880 {
9881 bracket_pair = Some(pair.clone());
9882 break;
9883 }
9884 }
9885 }
9886
9887 if let Some(pair) = bracket_pair {
9888 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9889 let autoclose_enabled =
9890 self.use_autoclose && snapshot_settings.use_autoclose;
9891 if autoclose_enabled {
9892 let start = snapshot.anchor_after(selection_head);
9893 let end = snapshot.anchor_after(selection_head);
9894 self.autoclose_regions.push(AutocloseRegion {
9895 selection_id: selection.id,
9896 range: start..end,
9897 pair,
9898 });
9899 }
9900 }
9901 }
9902 }
9903 }
9904 Ok(())
9905 }
9906
9907 pub fn move_to_next_snippet_tabstop(
9908 &mut self,
9909 window: &mut Window,
9910 cx: &mut Context<Self>,
9911 ) -> bool {
9912 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9913 }
9914
9915 pub fn move_to_prev_snippet_tabstop(
9916 &mut self,
9917 window: &mut Window,
9918 cx: &mut Context<Self>,
9919 ) -> bool {
9920 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9921 }
9922
9923 pub fn move_to_snippet_tabstop(
9924 &mut self,
9925 bias: Bias,
9926 window: &mut Window,
9927 cx: &mut Context<Self>,
9928 ) -> bool {
9929 if let Some(mut snippet) = self.snippet_stack.pop() {
9930 match bias {
9931 Bias::Left => {
9932 if snippet.active_index > 0 {
9933 snippet.active_index -= 1;
9934 } else {
9935 self.snippet_stack.push(snippet);
9936 return false;
9937 }
9938 }
9939 Bias::Right => {
9940 if snippet.active_index + 1 < snippet.ranges.len() {
9941 snippet.active_index += 1;
9942 } else {
9943 self.snippet_stack.push(snippet);
9944 return false;
9945 }
9946 }
9947 }
9948 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9949 self.change_selections(Default::default(), window, cx, |s| {
9950 // Reverse order so that the first range is the newest created selection.
9951 // Completions will use it and autoscroll will prioritize it.
9952 s.select_ranges(current_ranges.iter().rev().cloned())
9953 });
9954
9955 if let Some(choices) = &snippet.choices[snippet.active_index]
9956 && let Some(selection) = current_ranges.first()
9957 {
9958 self.show_snippet_choices(choices, selection.clone(), cx);
9959 }
9960
9961 // If snippet state is not at the last tabstop, push it back on the stack
9962 if snippet.active_index + 1 < snippet.ranges.len() {
9963 self.snippet_stack.push(snippet);
9964 }
9965 return true;
9966 }
9967 }
9968
9969 false
9970 }
9971
9972 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9973 self.transact(window, cx, |this, window, cx| {
9974 this.select_all(&SelectAll, window, cx);
9975 this.insert("", window, cx);
9976 });
9977 }
9978
9979 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9980 if self.read_only(cx) {
9981 return;
9982 }
9983 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9984 self.transact(window, cx, |this, window, cx| {
9985 this.select_autoclose_pair(window, cx);
9986 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9987 if !this.linked_edit_ranges.is_empty() {
9988 let selections = this.selections.all::<MultiBufferPoint>(cx);
9989 let snapshot = this.buffer.read(cx).snapshot(cx);
9990
9991 for selection in selections.iter() {
9992 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9993 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9994 if selection_start.buffer_id != selection_end.buffer_id {
9995 continue;
9996 }
9997 if let Some(ranges) =
9998 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9999 {
10000 for (buffer, entries) in ranges {
10001 linked_ranges.entry(buffer).or_default().extend(entries);
10002 }
10003 }
10004 }
10005 }
10006
10007 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
10008 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10009 for selection in &mut selections {
10010 if selection.is_empty() {
10011 let old_head = selection.head();
10012 let mut new_head =
10013 movement::left(&display_map, old_head.to_display_point(&display_map))
10014 .to_point(&display_map);
10015 if let Some((buffer, line_buffer_range)) = display_map
10016 .buffer_snapshot()
10017 .buffer_line_for_row(MultiBufferRow(old_head.row))
10018 {
10019 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10020 let indent_len = match indent_size.kind {
10021 IndentKind::Space => {
10022 buffer.settings_at(line_buffer_range.start, cx).tab_size
10023 }
10024 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10025 };
10026 if old_head.column <= indent_size.len && old_head.column > 0 {
10027 let indent_len = indent_len.get();
10028 new_head = cmp::min(
10029 new_head,
10030 MultiBufferPoint::new(
10031 old_head.row,
10032 ((old_head.column - 1) / indent_len) * indent_len,
10033 ),
10034 );
10035 }
10036 }
10037
10038 selection.set_head(new_head, SelectionGoal::None);
10039 }
10040 }
10041
10042 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10043 this.insert("", window, cx);
10044 let empty_str: Arc<str> = Arc::from("");
10045 for (buffer, edits) in linked_ranges {
10046 let snapshot = buffer.read(cx).snapshot();
10047 use text::ToPoint as TP;
10048
10049 let edits = edits
10050 .into_iter()
10051 .map(|range| {
10052 let end_point = TP::to_point(&range.end, &snapshot);
10053 let mut start_point = TP::to_point(&range.start, &snapshot);
10054
10055 if end_point == start_point {
10056 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10057 .saturating_sub(1);
10058 start_point =
10059 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10060 };
10061
10062 (start_point..end_point, empty_str.clone())
10063 })
10064 .sorted_by_key(|(range, _)| range.start)
10065 .collect::<Vec<_>>();
10066 buffer.update(cx, |this, cx| {
10067 this.edit(edits, None, cx);
10068 })
10069 }
10070 this.refresh_edit_prediction(true, false, window, cx);
10071 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
10072 });
10073 }
10074
10075 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10076 if self.read_only(cx) {
10077 return;
10078 }
10079 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10080 self.transact(window, cx, |this, window, cx| {
10081 this.change_selections(Default::default(), window, cx, |s| {
10082 s.move_with(|map, selection| {
10083 if selection.is_empty() {
10084 let cursor = movement::right(map, selection.head());
10085 selection.end = cursor;
10086 selection.reversed = true;
10087 selection.goal = SelectionGoal::None;
10088 }
10089 })
10090 });
10091 this.insert("", window, cx);
10092 this.refresh_edit_prediction(true, false, window, cx);
10093 });
10094 }
10095
10096 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10097 if self.mode.is_single_line() {
10098 cx.propagate();
10099 return;
10100 }
10101
10102 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10103 if self.move_to_prev_snippet_tabstop(window, cx) {
10104 return;
10105 }
10106 self.outdent(&Outdent, window, cx);
10107 }
10108
10109 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10110 if self.mode.is_single_line() {
10111 cx.propagate();
10112 return;
10113 }
10114
10115 if self.move_to_next_snippet_tabstop(window, cx) {
10116 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10117 return;
10118 }
10119 if self.read_only(cx) {
10120 return;
10121 }
10122 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10123 let mut selections = self.selections.all_adjusted(cx);
10124 let buffer = self.buffer.read(cx);
10125 let snapshot = buffer.snapshot(cx);
10126 let rows_iter = selections.iter().map(|s| s.head().row);
10127 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10128
10129 let has_some_cursor_in_whitespace = selections
10130 .iter()
10131 .filter(|selection| selection.is_empty())
10132 .any(|selection| {
10133 let cursor = selection.head();
10134 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10135 cursor.column < current_indent.len
10136 });
10137
10138 let mut edits = Vec::new();
10139 let mut prev_edited_row = 0;
10140 let mut row_delta = 0;
10141 for selection in &mut selections {
10142 if selection.start.row != prev_edited_row {
10143 row_delta = 0;
10144 }
10145 prev_edited_row = selection.end.row;
10146
10147 // If the selection is non-empty, then increase the indentation of the selected lines.
10148 if !selection.is_empty() {
10149 row_delta =
10150 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10151 continue;
10152 }
10153
10154 let cursor = selection.head();
10155 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10156 if let Some(suggested_indent) =
10157 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10158 {
10159 // Don't do anything if already at suggested indent
10160 // and there is any other cursor which is not
10161 if has_some_cursor_in_whitespace
10162 && cursor.column == current_indent.len
10163 && current_indent.len == suggested_indent.len
10164 {
10165 continue;
10166 }
10167
10168 // Adjust line and move cursor to suggested indent
10169 // if cursor is not at suggested indent
10170 if cursor.column < suggested_indent.len
10171 && cursor.column <= current_indent.len
10172 && current_indent.len <= suggested_indent.len
10173 {
10174 selection.start = Point::new(cursor.row, suggested_indent.len);
10175 selection.end = selection.start;
10176 if row_delta == 0 {
10177 edits.extend(Buffer::edit_for_indent_size_adjustment(
10178 cursor.row,
10179 current_indent,
10180 suggested_indent,
10181 ));
10182 row_delta = suggested_indent.len - current_indent.len;
10183 }
10184 continue;
10185 }
10186
10187 // If current indent is more than suggested indent
10188 // only move cursor to current indent and skip indent
10189 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10190 selection.start = Point::new(cursor.row, current_indent.len);
10191 selection.end = selection.start;
10192 continue;
10193 }
10194 }
10195
10196 // Otherwise, insert a hard or soft tab.
10197 let settings = buffer.language_settings_at(cursor, cx);
10198 let tab_size = if settings.hard_tabs {
10199 IndentSize::tab()
10200 } else {
10201 let tab_size = settings.tab_size.get();
10202 let indent_remainder = snapshot
10203 .text_for_range(Point::new(cursor.row, 0)..cursor)
10204 .flat_map(str::chars)
10205 .fold(row_delta % tab_size, |counter: u32, c| {
10206 if c == '\t' {
10207 0
10208 } else {
10209 (counter + 1) % tab_size
10210 }
10211 });
10212
10213 let chars_to_next_tab_stop = tab_size - indent_remainder;
10214 IndentSize::spaces(chars_to_next_tab_stop)
10215 };
10216 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10217 selection.end = selection.start;
10218 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10219 row_delta += tab_size.len;
10220 }
10221
10222 self.transact(window, cx, |this, window, cx| {
10223 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10224 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10225 this.refresh_edit_prediction(true, false, window, cx);
10226 });
10227 }
10228
10229 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10230 if self.read_only(cx) {
10231 return;
10232 }
10233 if self.mode.is_single_line() {
10234 cx.propagate();
10235 return;
10236 }
10237
10238 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10239 let mut selections = self.selections.all::<Point>(cx);
10240 let mut prev_edited_row = 0;
10241 let mut row_delta = 0;
10242 let mut edits = Vec::new();
10243 let buffer = self.buffer.read(cx);
10244 let snapshot = buffer.snapshot(cx);
10245 for selection in &mut selections {
10246 if selection.start.row != prev_edited_row {
10247 row_delta = 0;
10248 }
10249 prev_edited_row = selection.end.row;
10250
10251 row_delta =
10252 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10253 }
10254
10255 self.transact(window, cx, |this, window, cx| {
10256 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10257 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10258 });
10259 }
10260
10261 fn indent_selection(
10262 buffer: &MultiBuffer,
10263 snapshot: &MultiBufferSnapshot,
10264 selection: &mut Selection<Point>,
10265 edits: &mut Vec<(Range<Point>, String)>,
10266 delta_for_start_row: u32,
10267 cx: &App,
10268 ) -> u32 {
10269 let settings = buffer.language_settings_at(selection.start, cx);
10270 let tab_size = settings.tab_size.get();
10271 let indent_kind = if settings.hard_tabs {
10272 IndentKind::Tab
10273 } else {
10274 IndentKind::Space
10275 };
10276 let mut start_row = selection.start.row;
10277 let mut end_row = selection.end.row + 1;
10278
10279 // If a selection ends at the beginning of a line, don't indent
10280 // that last line.
10281 if selection.end.column == 0 && selection.end.row > selection.start.row {
10282 end_row -= 1;
10283 }
10284
10285 // Avoid re-indenting a row that has already been indented by a
10286 // previous selection, but still update this selection's column
10287 // to reflect that indentation.
10288 if delta_for_start_row > 0 {
10289 start_row += 1;
10290 selection.start.column += delta_for_start_row;
10291 if selection.end.row == selection.start.row {
10292 selection.end.column += delta_for_start_row;
10293 }
10294 }
10295
10296 let mut delta_for_end_row = 0;
10297 let has_multiple_rows = start_row + 1 != end_row;
10298 for row in start_row..end_row {
10299 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10300 let indent_delta = match (current_indent.kind, indent_kind) {
10301 (IndentKind::Space, IndentKind::Space) => {
10302 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10303 IndentSize::spaces(columns_to_next_tab_stop)
10304 }
10305 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10306 (_, IndentKind::Tab) => IndentSize::tab(),
10307 };
10308
10309 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10310 0
10311 } else {
10312 selection.start.column
10313 };
10314 let row_start = Point::new(row, start);
10315 edits.push((
10316 row_start..row_start,
10317 indent_delta.chars().collect::<String>(),
10318 ));
10319
10320 // Update this selection's endpoints to reflect the indentation.
10321 if row == selection.start.row {
10322 selection.start.column += indent_delta.len;
10323 }
10324 if row == selection.end.row {
10325 selection.end.column += indent_delta.len;
10326 delta_for_end_row = indent_delta.len;
10327 }
10328 }
10329
10330 if selection.start.row == selection.end.row {
10331 delta_for_start_row + delta_for_end_row
10332 } else {
10333 delta_for_end_row
10334 }
10335 }
10336
10337 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10338 if self.read_only(cx) {
10339 return;
10340 }
10341 if self.mode.is_single_line() {
10342 cx.propagate();
10343 return;
10344 }
10345
10346 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10347 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10348 let selections = self.selections.all::<Point>(cx);
10349 let mut deletion_ranges = Vec::new();
10350 let mut last_outdent = None;
10351 {
10352 let buffer = self.buffer.read(cx);
10353 let snapshot = buffer.snapshot(cx);
10354 for selection in &selections {
10355 let settings = buffer.language_settings_at(selection.start, cx);
10356 let tab_size = settings.tab_size.get();
10357 let mut rows = selection.spanned_rows(false, &display_map);
10358
10359 // Avoid re-outdenting a row that has already been outdented by a
10360 // previous selection.
10361 if let Some(last_row) = last_outdent
10362 && last_row == rows.start
10363 {
10364 rows.start = rows.start.next_row();
10365 }
10366 let has_multiple_rows = rows.len() > 1;
10367 for row in rows.iter_rows() {
10368 let indent_size = snapshot.indent_size_for_line(row);
10369 if indent_size.len > 0 {
10370 let deletion_len = match indent_size.kind {
10371 IndentKind::Space => {
10372 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10373 if columns_to_prev_tab_stop == 0 {
10374 tab_size
10375 } else {
10376 columns_to_prev_tab_stop
10377 }
10378 }
10379 IndentKind::Tab => 1,
10380 };
10381 let start = if has_multiple_rows
10382 || deletion_len > selection.start.column
10383 || indent_size.len < selection.start.column
10384 {
10385 0
10386 } else {
10387 selection.start.column - deletion_len
10388 };
10389 deletion_ranges.push(
10390 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10391 );
10392 last_outdent = Some(row);
10393 }
10394 }
10395 }
10396 }
10397
10398 self.transact(window, cx, |this, window, cx| {
10399 this.buffer.update(cx, |buffer, cx| {
10400 let empty_str: Arc<str> = Arc::default();
10401 buffer.edit(
10402 deletion_ranges
10403 .into_iter()
10404 .map(|range| (range, empty_str.clone())),
10405 None,
10406 cx,
10407 );
10408 });
10409 let selections = this.selections.all::<usize>(cx);
10410 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10411 });
10412 }
10413
10414 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10415 if self.read_only(cx) {
10416 return;
10417 }
10418 if self.mode.is_single_line() {
10419 cx.propagate();
10420 return;
10421 }
10422
10423 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10424 let selections = self
10425 .selections
10426 .all::<usize>(cx)
10427 .into_iter()
10428 .map(|s| s.range());
10429
10430 self.transact(window, cx, |this, window, cx| {
10431 this.buffer.update(cx, |buffer, cx| {
10432 buffer.autoindent_ranges(selections, cx);
10433 });
10434 let selections = this.selections.all::<usize>(cx);
10435 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10436 });
10437 }
10438
10439 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10440 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10441 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10442 let selections = self.selections.all::<Point>(cx);
10443
10444 let mut new_cursors = Vec::new();
10445 let mut edit_ranges = Vec::new();
10446 let mut selections = selections.iter().peekable();
10447 while let Some(selection) = selections.next() {
10448 let mut rows = selection.spanned_rows(false, &display_map);
10449
10450 // Accumulate contiguous regions of rows that we want to delete.
10451 while let Some(next_selection) = selections.peek() {
10452 let next_rows = next_selection.spanned_rows(false, &display_map);
10453 if next_rows.start <= rows.end {
10454 rows.end = next_rows.end;
10455 selections.next().unwrap();
10456 } else {
10457 break;
10458 }
10459 }
10460
10461 let buffer = display_map.buffer_snapshot();
10462 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10463 let edit_end = if buffer.max_point().row >= rows.end.0 {
10464 // If there's a line after the range, delete the \n from the end of the row range
10465 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer)
10466 } else {
10467 // If there isn't a line after the range, delete the \n from the line before the
10468 // start of the row range
10469 edit_start = edit_start.saturating_sub(1);
10470 buffer.len()
10471 };
10472
10473 let (cursor, goal) = movement::down_by_rows(
10474 &display_map,
10475 selection.head().to_display_point(&display_map),
10476 rows.len() as u32,
10477 selection.goal,
10478 false,
10479 &self.text_layout_details(window),
10480 );
10481
10482 new_cursors.push((
10483 selection.id,
10484 buffer.anchor_after(cursor.to_point(&display_map)),
10485 goal,
10486 ));
10487 edit_ranges.push(edit_start..edit_end);
10488 }
10489
10490 self.transact(window, cx, |this, window, cx| {
10491 let buffer = this.buffer.update(cx, |buffer, cx| {
10492 let empty_str: Arc<str> = Arc::default();
10493 buffer.edit(
10494 edit_ranges
10495 .into_iter()
10496 .map(|range| (range, empty_str.clone())),
10497 None,
10498 cx,
10499 );
10500 buffer.snapshot(cx)
10501 });
10502 let new_selections = new_cursors
10503 .into_iter()
10504 .map(|(id, cursor, goal)| {
10505 let cursor = cursor.to_point(&buffer);
10506 Selection {
10507 id,
10508 start: cursor,
10509 end: cursor,
10510 reversed: false,
10511 goal,
10512 }
10513 })
10514 .collect();
10515
10516 this.change_selections(Default::default(), window, cx, |s| {
10517 s.select(new_selections);
10518 });
10519 });
10520 }
10521
10522 pub fn join_lines_impl(
10523 &mut self,
10524 insert_whitespace: bool,
10525 window: &mut Window,
10526 cx: &mut Context<Self>,
10527 ) {
10528 if self.read_only(cx) {
10529 return;
10530 }
10531 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10532 for selection in self.selections.all::<Point>(cx) {
10533 let start = MultiBufferRow(selection.start.row);
10534 // Treat single line selections as if they include the next line. Otherwise this action
10535 // would do nothing for single line selections individual cursors.
10536 let end = if selection.start.row == selection.end.row {
10537 MultiBufferRow(selection.start.row + 1)
10538 } else {
10539 MultiBufferRow(selection.end.row)
10540 };
10541
10542 if let Some(last_row_range) = row_ranges.last_mut()
10543 && start <= last_row_range.end
10544 {
10545 last_row_range.end = end;
10546 continue;
10547 }
10548 row_ranges.push(start..end);
10549 }
10550
10551 let snapshot = self.buffer.read(cx).snapshot(cx);
10552 let mut cursor_positions = Vec::new();
10553 for row_range in &row_ranges {
10554 let anchor = snapshot.anchor_before(Point::new(
10555 row_range.end.previous_row().0,
10556 snapshot.line_len(row_range.end.previous_row()),
10557 ));
10558 cursor_positions.push(anchor..anchor);
10559 }
10560
10561 self.transact(window, cx, |this, window, cx| {
10562 for row_range in row_ranges.into_iter().rev() {
10563 for row in row_range.iter_rows().rev() {
10564 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10565 let next_line_row = row.next_row();
10566 let indent = snapshot.indent_size_for_line(next_line_row);
10567 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10568
10569 let replace =
10570 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10571 " "
10572 } else {
10573 ""
10574 };
10575
10576 this.buffer.update(cx, |buffer, cx| {
10577 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10578 });
10579 }
10580 }
10581
10582 this.change_selections(Default::default(), window, cx, |s| {
10583 s.select_anchor_ranges(cursor_positions)
10584 });
10585 });
10586 }
10587
10588 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10589 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10590 self.join_lines_impl(true, window, cx);
10591 }
10592
10593 pub fn sort_lines_case_sensitive(
10594 &mut self,
10595 _: &SortLinesCaseSensitive,
10596 window: &mut Window,
10597 cx: &mut Context<Self>,
10598 ) {
10599 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10600 }
10601
10602 pub fn sort_lines_by_length(
10603 &mut self,
10604 _: &SortLinesByLength,
10605 window: &mut Window,
10606 cx: &mut Context<Self>,
10607 ) {
10608 self.manipulate_immutable_lines(window, cx, |lines| {
10609 lines.sort_by_key(|&line| line.chars().count())
10610 })
10611 }
10612
10613 pub fn sort_lines_case_insensitive(
10614 &mut self,
10615 _: &SortLinesCaseInsensitive,
10616 window: &mut Window,
10617 cx: &mut Context<Self>,
10618 ) {
10619 self.manipulate_immutable_lines(window, cx, |lines| {
10620 lines.sort_by_key(|line| line.to_lowercase())
10621 })
10622 }
10623
10624 pub fn unique_lines_case_insensitive(
10625 &mut self,
10626 _: &UniqueLinesCaseInsensitive,
10627 window: &mut Window,
10628 cx: &mut Context<Self>,
10629 ) {
10630 self.manipulate_immutable_lines(window, cx, |lines| {
10631 let mut seen = HashSet::default();
10632 lines.retain(|line| seen.insert(line.to_lowercase()));
10633 })
10634 }
10635
10636 pub fn unique_lines_case_sensitive(
10637 &mut self,
10638 _: &UniqueLinesCaseSensitive,
10639 window: &mut Window,
10640 cx: &mut Context<Self>,
10641 ) {
10642 self.manipulate_immutable_lines(window, cx, |lines| {
10643 let mut seen = HashSet::default();
10644 lines.retain(|line| seen.insert(*line));
10645 })
10646 }
10647
10648 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10649 let snapshot = self.buffer.read(cx).snapshot(cx);
10650 for selection in self.selections.disjoint_anchors_arc().iter() {
10651 if snapshot
10652 .language_at(selection.start)
10653 .and_then(|lang| lang.config().wrap_characters.as_ref())
10654 .is_some()
10655 {
10656 return true;
10657 }
10658 }
10659 false
10660 }
10661
10662 fn wrap_selections_in_tag(
10663 &mut self,
10664 _: &WrapSelectionsInTag,
10665 window: &mut Window,
10666 cx: &mut Context<Self>,
10667 ) {
10668 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10669
10670 let snapshot = self.buffer.read(cx).snapshot(cx);
10671
10672 let mut edits = Vec::new();
10673 let mut boundaries = Vec::new();
10674
10675 for selection in self.selections.all::<Point>(cx).iter() {
10676 let Some(wrap_config) = snapshot
10677 .language_at(selection.start)
10678 .and_then(|lang| lang.config().wrap_characters.clone())
10679 else {
10680 continue;
10681 };
10682
10683 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10684 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10685
10686 let start_before = snapshot.anchor_before(selection.start);
10687 let end_after = snapshot.anchor_after(selection.end);
10688
10689 edits.push((start_before..start_before, open_tag));
10690 edits.push((end_after..end_after, close_tag));
10691
10692 boundaries.push((
10693 start_before,
10694 end_after,
10695 wrap_config.start_prefix.len(),
10696 wrap_config.end_suffix.len(),
10697 ));
10698 }
10699
10700 if edits.is_empty() {
10701 return;
10702 }
10703
10704 self.transact(window, cx, |this, window, cx| {
10705 let buffer = this.buffer.update(cx, |buffer, cx| {
10706 buffer.edit(edits, None, cx);
10707 buffer.snapshot(cx)
10708 });
10709
10710 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10711 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10712 boundaries.into_iter()
10713 {
10714 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10715 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10716 new_selections.push(open_offset..open_offset);
10717 new_selections.push(close_offset..close_offset);
10718 }
10719
10720 this.change_selections(Default::default(), window, cx, |s| {
10721 s.select_ranges(new_selections);
10722 });
10723
10724 this.request_autoscroll(Autoscroll::fit(), cx);
10725 });
10726 }
10727
10728 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10729 let Some(project) = self.project.clone() else {
10730 return;
10731 };
10732 self.reload(project, window, cx)
10733 .detach_and_notify_err(window, cx);
10734 }
10735
10736 pub fn restore_file(
10737 &mut self,
10738 _: &::git::RestoreFile,
10739 window: &mut Window,
10740 cx: &mut Context<Self>,
10741 ) {
10742 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10743 let mut buffer_ids = HashSet::default();
10744 let snapshot = self.buffer().read(cx).snapshot(cx);
10745 for selection in self.selections.all::<usize>(cx) {
10746 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10747 }
10748
10749 let buffer = self.buffer().read(cx);
10750 let ranges = buffer_ids
10751 .into_iter()
10752 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10753 .collect::<Vec<_>>();
10754
10755 self.restore_hunks_in_ranges(ranges, window, cx);
10756 }
10757
10758 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10759 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10760 let selections = self
10761 .selections
10762 .all(cx)
10763 .into_iter()
10764 .map(|s| s.range())
10765 .collect();
10766 self.restore_hunks_in_ranges(selections, window, cx);
10767 }
10768
10769 pub fn restore_hunks_in_ranges(
10770 &mut self,
10771 ranges: Vec<Range<Point>>,
10772 window: &mut Window,
10773 cx: &mut Context<Editor>,
10774 ) {
10775 let mut revert_changes = HashMap::default();
10776 let chunk_by = self
10777 .snapshot(window, cx)
10778 .hunks_for_ranges(ranges)
10779 .into_iter()
10780 .chunk_by(|hunk| hunk.buffer_id);
10781 for (buffer_id, hunks) in &chunk_by {
10782 let hunks = hunks.collect::<Vec<_>>();
10783 for hunk in &hunks {
10784 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10785 }
10786 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10787 }
10788 drop(chunk_by);
10789 if !revert_changes.is_empty() {
10790 self.transact(window, cx, |editor, window, cx| {
10791 editor.restore(revert_changes, window, cx);
10792 });
10793 }
10794 }
10795
10796 pub fn open_active_item_in_terminal(
10797 &mut self,
10798 _: &OpenInTerminal,
10799 window: &mut Window,
10800 cx: &mut Context<Self>,
10801 ) {
10802 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10803 let project_path = buffer.read(cx).project_path(cx)?;
10804 let project = self.project()?.read(cx);
10805 let entry = project.entry_for_path(&project_path, cx)?;
10806 let parent = match &entry.canonical_path {
10807 Some(canonical_path) => canonical_path.to_path_buf(),
10808 None => project.absolute_path(&project_path, cx)?,
10809 }
10810 .parent()?
10811 .to_path_buf();
10812 Some(parent)
10813 }) {
10814 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10815 }
10816 }
10817
10818 fn set_breakpoint_context_menu(
10819 &mut self,
10820 display_row: DisplayRow,
10821 position: Option<Anchor>,
10822 clicked_point: gpui::Point<Pixels>,
10823 window: &mut Window,
10824 cx: &mut Context<Self>,
10825 ) {
10826 let source = self
10827 .buffer
10828 .read(cx)
10829 .snapshot(cx)
10830 .anchor_before(Point::new(display_row.0, 0u32));
10831
10832 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10833
10834 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10835 self,
10836 source,
10837 clicked_point,
10838 context_menu,
10839 window,
10840 cx,
10841 );
10842 }
10843
10844 fn add_edit_breakpoint_block(
10845 &mut self,
10846 anchor: Anchor,
10847 breakpoint: &Breakpoint,
10848 edit_action: BreakpointPromptEditAction,
10849 window: &mut Window,
10850 cx: &mut Context<Self>,
10851 ) {
10852 let weak_editor = cx.weak_entity();
10853 let bp_prompt = cx.new(|cx| {
10854 BreakpointPromptEditor::new(
10855 weak_editor,
10856 anchor,
10857 breakpoint.clone(),
10858 edit_action,
10859 window,
10860 cx,
10861 )
10862 });
10863
10864 let height = bp_prompt.update(cx, |this, cx| {
10865 this.prompt
10866 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10867 });
10868 let cloned_prompt = bp_prompt.clone();
10869 let blocks = vec![BlockProperties {
10870 style: BlockStyle::Sticky,
10871 placement: BlockPlacement::Above(anchor),
10872 height: Some(height),
10873 render: Arc::new(move |cx| {
10874 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10875 cloned_prompt.clone().into_any_element()
10876 }),
10877 priority: 0,
10878 }];
10879
10880 let focus_handle = bp_prompt.focus_handle(cx);
10881 window.focus(&focus_handle);
10882
10883 let block_ids = self.insert_blocks(blocks, None, cx);
10884 bp_prompt.update(cx, |prompt, _| {
10885 prompt.add_block_ids(block_ids);
10886 });
10887 }
10888
10889 pub(crate) fn breakpoint_at_row(
10890 &self,
10891 row: u32,
10892 window: &mut Window,
10893 cx: &mut Context<Self>,
10894 ) -> Option<(Anchor, Breakpoint)> {
10895 let snapshot = self.snapshot(window, cx);
10896 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10897
10898 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10899 }
10900
10901 pub(crate) fn breakpoint_at_anchor(
10902 &self,
10903 breakpoint_position: Anchor,
10904 snapshot: &EditorSnapshot,
10905 cx: &mut Context<Self>,
10906 ) -> Option<(Anchor, Breakpoint)> {
10907 let buffer = self
10908 .buffer
10909 .read(cx)
10910 .buffer_for_anchor(breakpoint_position, cx)?;
10911
10912 let enclosing_excerpt = breakpoint_position.excerpt_id;
10913 let buffer_snapshot = buffer.read(cx).snapshot();
10914
10915 let row = buffer_snapshot
10916 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10917 .row;
10918
10919 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10920 let anchor_end = snapshot
10921 .buffer_snapshot()
10922 .anchor_after(Point::new(row, line_len));
10923
10924 self.breakpoint_store
10925 .as_ref()?
10926 .read_with(cx, |breakpoint_store, cx| {
10927 breakpoint_store
10928 .breakpoints(
10929 &buffer,
10930 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10931 &buffer_snapshot,
10932 cx,
10933 )
10934 .next()
10935 .and_then(|(bp, _)| {
10936 let breakpoint_row = buffer_snapshot
10937 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10938 .row;
10939
10940 if breakpoint_row == row {
10941 snapshot
10942 .buffer_snapshot()
10943 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10944 .map(|position| (position, bp.bp.clone()))
10945 } else {
10946 None
10947 }
10948 })
10949 })
10950 }
10951
10952 pub fn edit_log_breakpoint(
10953 &mut self,
10954 _: &EditLogBreakpoint,
10955 window: &mut Window,
10956 cx: &mut Context<Self>,
10957 ) {
10958 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10959 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10960 message: None,
10961 state: BreakpointState::Enabled,
10962 condition: None,
10963 hit_condition: None,
10964 });
10965
10966 self.add_edit_breakpoint_block(
10967 anchor,
10968 &breakpoint,
10969 BreakpointPromptEditAction::Log,
10970 window,
10971 cx,
10972 );
10973 }
10974 }
10975
10976 fn breakpoints_at_cursors(
10977 &self,
10978 window: &mut Window,
10979 cx: &mut Context<Self>,
10980 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10981 let snapshot = self.snapshot(window, cx);
10982 let cursors = self
10983 .selections
10984 .disjoint_anchors_arc()
10985 .iter()
10986 .map(|selection| {
10987 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10988
10989 let breakpoint_position = self
10990 .breakpoint_at_row(cursor_position.row, window, cx)
10991 .map(|bp| bp.0)
10992 .unwrap_or_else(|| {
10993 snapshot
10994 .display_snapshot
10995 .buffer_snapshot()
10996 .anchor_after(Point::new(cursor_position.row, 0))
10997 });
10998
10999 let breakpoint = self
11000 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11001 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11002
11003 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11004 })
11005 // There might be multiple cursors on the same line; all of them should have the same anchors though as their breakpoints positions, which makes it possible to sort and dedup the list.
11006 .collect::<HashMap<Anchor, _>>();
11007
11008 cursors.into_iter().collect()
11009 }
11010
11011 pub fn enable_breakpoint(
11012 &mut self,
11013 _: &crate::actions::EnableBreakpoint,
11014 window: &mut Window,
11015 cx: &mut Context<Self>,
11016 ) {
11017 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11018 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11019 continue;
11020 };
11021 self.edit_breakpoint_at_anchor(
11022 anchor,
11023 breakpoint,
11024 BreakpointEditAction::InvertState,
11025 cx,
11026 );
11027 }
11028 }
11029
11030 pub fn disable_breakpoint(
11031 &mut self,
11032 _: &crate::actions::DisableBreakpoint,
11033 window: &mut Window,
11034 cx: &mut Context<Self>,
11035 ) {
11036 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11037 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11038 continue;
11039 };
11040 self.edit_breakpoint_at_anchor(
11041 anchor,
11042 breakpoint,
11043 BreakpointEditAction::InvertState,
11044 cx,
11045 );
11046 }
11047 }
11048
11049 pub fn toggle_breakpoint(
11050 &mut self,
11051 _: &crate::actions::ToggleBreakpoint,
11052 window: &mut Window,
11053 cx: &mut Context<Self>,
11054 ) {
11055 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11056 if let Some(breakpoint) = breakpoint {
11057 self.edit_breakpoint_at_anchor(
11058 anchor,
11059 breakpoint,
11060 BreakpointEditAction::Toggle,
11061 cx,
11062 );
11063 } else {
11064 self.edit_breakpoint_at_anchor(
11065 anchor,
11066 Breakpoint::new_standard(),
11067 BreakpointEditAction::Toggle,
11068 cx,
11069 );
11070 }
11071 }
11072 }
11073
11074 pub fn edit_breakpoint_at_anchor(
11075 &mut self,
11076 breakpoint_position: Anchor,
11077 breakpoint: Breakpoint,
11078 edit_action: BreakpointEditAction,
11079 cx: &mut Context<Self>,
11080 ) {
11081 let Some(breakpoint_store) = &self.breakpoint_store else {
11082 return;
11083 };
11084
11085 let Some(buffer) = self
11086 .buffer
11087 .read(cx)
11088 .buffer_for_anchor(breakpoint_position, cx)
11089 else {
11090 return;
11091 };
11092
11093 breakpoint_store.update(cx, |breakpoint_store, cx| {
11094 breakpoint_store.toggle_breakpoint(
11095 buffer,
11096 BreakpointWithPosition {
11097 position: breakpoint_position.text_anchor,
11098 bp: breakpoint,
11099 },
11100 edit_action,
11101 cx,
11102 );
11103 });
11104
11105 cx.notify();
11106 }
11107
11108 #[cfg(any(test, feature = "test-support"))]
11109 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11110 self.breakpoint_store.clone()
11111 }
11112
11113 pub fn prepare_restore_change(
11114 &self,
11115 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11116 hunk: &MultiBufferDiffHunk,
11117 cx: &mut App,
11118 ) -> Option<()> {
11119 if hunk.is_created_file() {
11120 return None;
11121 }
11122 let buffer = self.buffer.read(cx);
11123 let diff = buffer.diff_for(hunk.buffer_id)?;
11124 let buffer = buffer.buffer(hunk.buffer_id)?;
11125 let buffer = buffer.read(cx);
11126 let original_text = diff
11127 .read(cx)
11128 .base_text()
11129 .as_rope()
11130 .slice(hunk.diff_base_byte_range.clone());
11131 let buffer_snapshot = buffer.snapshot();
11132 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11133 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11134 probe
11135 .0
11136 .start
11137 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11138 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11139 }) {
11140 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11141 Some(())
11142 } else {
11143 None
11144 }
11145 }
11146
11147 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11148 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11149 }
11150
11151 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11152 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11153 }
11154
11155 fn manipulate_lines<M>(
11156 &mut self,
11157 window: &mut Window,
11158 cx: &mut Context<Self>,
11159 mut manipulate: M,
11160 ) where
11161 M: FnMut(&str) -> LineManipulationResult,
11162 {
11163 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11164
11165 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11166 let buffer = self.buffer.read(cx).snapshot(cx);
11167
11168 let mut edits = Vec::new();
11169
11170 let selections = self.selections.all::<Point>(cx);
11171 let mut selections = selections.iter().peekable();
11172 let mut contiguous_row_selections = Vec::new();
11173 let mut new_selections = Vec::new();
11174 let mut added_lines = 0;
11175 let mut removed_lines = 0;
11176
11177 while let Some(selection) = selections.next() {
11178 let (start_row, end_row) = consume_contiguous_rows(
11179 &mut contiguous_row_selections,
11180 selection,
11181 &display_map,
11182 &mut selections,
11183 );
11184
11185 let start_point = Point::new(start_row.0, 0);
11186 let end_point = Point::new(
11187 end_row.previous_row().0,
11188 buffer.line_len(end_row.previous_row()),
11189 );
11190 let text = buffer
11191 .text_for_range(start_point..end_point)
11192 .collect::<String>();
11193
11194 let LineManipulationResult {
11195 new_text,
11196 line_count_before,
11197 line_count_after,
11198 } = manipulate(&text);
11199
11200 edits.push((start_point..end_point, new_text));
11201
11202 // Selections must change based on added and removed line count
11203 let start_row =
11204 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11205 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11206 new_selections.push(Selection {
11207 id: selection.id,
11208 start: start_row,
11209 end: end_row,
11210 goal: SelectionGoal::None,
11211 reversed: selection.reversed,
11212 });
11213
11214 if line_count_after > line_count_before {
11215 added_lines += line_count_after - line_count_before;
11216 } else if line_count_before > line_count_after {
11217 removed_lines += line_count_before - line_count_after;
11218 }
11219 }
11220
11221 self.transact(window, cx, |this, window, cx| {
11222 let buffer = this.buffer.update(cx, |buffer, cx| {
11223 buffer.edit(edits, None, cx);
11224 buffer.snapshot(cx)
11225 });
11226
11227 // Recalculate offsets on newly edited buffer
11228 let new_selections = new_selections
11229 .iter()
11230 .map(|s| {
11231 let start_point = Point::new(s.start.0, 0);
11232 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11233 Selection {
11234 id: s.id,
11235 start: buffer.point_to_offset(start_point),
11236 end: buffer.point_to_offset(end_point),
11237 goal: s.goal,
11238 reversed: s.reversed,
11239 }
11240 })
11241 .collect();
11242
11243 this.change_selections(Default::default(), window, cx, |s| {
11244 s.select(new_selections);
11245 });
11246
11247 this.request_autoscroll(Autoscroll::fit(), cx);
11248 });
11249 }
11250
11251 fn manipulate_immutable_lines<Fn>(
11252 &mut self,
11253 window: &mut Window,
11254 cx: &mut Context<Self>,
11255 mut callback: Fn,
11256 ) where
11257 Fn: FnMut(&mut Vec<&str>),
11258 {
11259 self.manipulate_lines(window, cx, |text| {
11260 let mut lines: Vec<&str> = text.split('\n').collect();
11261 let line_count_before = lines.len();
11262
11263 callback(&mut lines);
11264
11265 LineManipulationResult {
11266 new_text: lines.join("\n"),
11267 line_count_before,
11268 line_count_after: lines.len(),
11269 }
11270 });
11271 }
11272
11273 fn manipulate_mutable_lines<Fn>(
11274 &mut self,
11275 window: &mut Window,
11276 cx: &mut Context<Self>,
11277 mut callback: Fn,
11278 ) where
11279 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11280 {
11281 self.manipulate_lines(window, cx, |text| {
11282 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11283 let line_count_before = lines.len();
11284
11285 callback(&mut lines);
11286
11287 LineManipulationResult {
11288 new_text: lines.join("\n"),
11289 line_count_before,
11290 line_count_after: lines.len(),
11291 }
11292 });
11293 }
11294
11295 pub fn convert_indentation_to_spaces(
11296 &mut self,
11297 _: &ConvertIndentationToSpaces,
11298 window: &mut Window,
11299 cx: &mut Context<Self>,
11300 ) {
11301 let settings = self.buffer.read(cx).language_settings(cx);
11302 let tab_size = settings.tab_size.get() as usize;
11303
11304 self.manipulate_mutable_lines(window, cx, |lines| {
11305 // Allocates a reasonably sized scratch buffer once for the whole loop
11306 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11307 // Avoids recomputing spaces that could be inserted many times
11308 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11309 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11310 .collect();
11311
11312 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11313 let mut chars = line.as_ref().chars();
11314 let mut col = 0;
11315 let mut changed = false;
11316
11317 for ch in chars.by_ref() {
11318 match ch {
11319 ' ' => {
11320 reindented_line.push(' ');
11321 col += 1;
11322 }
11323 '\t' => {
11324 // \t are converted to spaces depending on the current column
11325 let spaces_len = tab_size - (col % tab_size);
11326 reindented_line.extend(&space_cache[spaces_len - 1]);
11327 col += spaces_len;
11328 changed = true;
11329 }
11330 _ => {
11331 // If we dont append before break, the character is consumed
11332 reindented_line.push(ch);
11333 break;
11334 }
11335 }
11336 }
11337
11338 if !changed {
11339 reindented_line.clear();
11340 continue;
11341 }
11342 // Append the rest of the line and replace old reference with new one
11343 reindented_line.extend(chars);
11344 *line = Cow::Owned(reindented_line.clone());
11345 reindented_line.clear();
11346 }
11347 });
11348 }
11349
11350 pub fn convert_indentation_to_tabs(
11351 &mut self,
11352 _: &ConvertIndentationToTabs,
11353 window: &mut Window,
11354 cx: &mut Context<Self>,
11355 ) {
11356 let settings = self.buffer.read(cx).language_settings(cx);
11357 let tab_size = settings.tab_size.get() as usize;
11358
11359 self.manipulate_mutable_lines(window, cx, |lines| {
11360 // Allocates a reasonably sized buffer once for the whole loop
11361 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11362 // Avoids recomputing spaces that could be inserted many times
11363 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11364 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11365 .collect();
11366
11367 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11368 let mut chars = line.chars();
11369 let mut spaces_count = 0;
11370 let mut first_non_indent_char = None;
11371 let mut changed = false;
11372
11373 for ch in chars.by_ref() {
11374 match ch {
11375 ' ' => {
11376 // Keep track of spaces. Append \t when we reach tab_size
11377 spaces_count += 1;
11378 changed = true;
11379 if spaces_count == tab_size {
11380 reindented_line.push('\t');
11381 spaces_count = 0;
11382 }
11383 }
11384 '\t' => {
11385 reindented_line.push('\t');
11386 spaces_count = 0;
11387 }
11388 _ => {
11389 // Dont append it yet, we might have remaining spaces
11390 first_non_indent_char = Some(ch);
11391 break;
11392 }
11393 }
11394 }
11395
11396 if !changed {
11397 reindented_line.clear();
11398 continue;
11399 }
11400 // Remaining spaces that didn't make a full tab stop
11401 if spaces_count > 0 {
11402 reindented_line.extend(&space_cache[spaces_count - 1]);
11403 }
11404 // If we consume an extra character that was not indentation, add it back
11405 if let Some(extra_char) = first_non_indent_char {
11406 reindented_line.push(extra_char);
11407 }
11408 // Append the rest of the line and replace old reference with new one
11409 reindented_line.extend(chars);
11410 *line = Cow::Owned(reindented_line.clone());
11411 reindented_line.clear();
11412 }
11413 });
11414 }
11415
11416 pub fn convert_to_upper_case(
11417 &mut self,
11418 _: &ConvertToUpperCase,
11419 window: &mut Window,
11420 cx: &mut Context<Self>,
11421 ) {
11422 self.manipulate_text(window, cx, |text| text.to_uppercase())
11423 }
11424
11425 pub fn convert_to_lower_case(
11426 &mut self,
11427 _: &ConvertToLowerCase,
11428 window: &mut Window,
11429 cx: &mut Context<Self>,
11430 ) {
11431 self.manipulate_text(window, cx, |text| text.to_lowercase())
11432 }
11433
11434 pub fn convert_to_title_case(
11435 &mut self,
11436 _: &ConvertToTitleCase,
11437 window: &mut Window,
11438 cx: &mut Context<Self>,
11439 ) {
11440 self.manipulate_text(window, cx, |text| {
11441 text.split('\n')
11442 .map(|line| line.to_case(Case::Title))
11443 .join("\n")
11444 })
11445 }
11446
11447 pub fn convert_to_snake_case(
11448 &mut self,
11449 _: &ConvertToSnakeCase,
11450 window: &mut Window,
11451 cx: &mut Context<Self>,
11452 ) {
11453 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11454 }
11455
11456 pub fn convert_to_kebab_case(
11457 &mut self,
11458 _: &ConvertToKebabCase,
11459 window: &mut Window,
11460 cx: &mut Context<Self>,
11461 ) {
11462 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11463 }
11464
11465 pub fn convert_to_upper_camel_case(
11466 &mut self,
11467 _: &ConvertToUpperCamelCase,
11468 window: &mut Window,
11469 cx: &mut Context<Self>,
11470 ) {
11471 self.manipulate_text(window, cx, |text| {
11472 text.split('\n')
11473 .map(|line| line.to_case(Case::UpperCamel))
11474 .join("\n")
11475 })
11476 }
11477
11478 pub fn convert_to_lower_camel_case(
11479 &mut self,
11480 _: &ConvertToLowerCamelCase,
11481 window: &mut Window,
11482 cx: &mut Context<Self>,
11483 ) {
11484 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11485 }
11486
11487 pub fn convert_to_opposite_case(
11488 &mut self,
11489 _: &ConvertToOppositeCase,
11490 window: &mut Window,
11491 cx: &mut Context<Self>,
11492 ) {
11493 self.manipulate_text(window, cx, |text| {
11494 text.chars()
11495 .fold(String::with_capacity(text.len()), |mut t, c| {
11496 if c.is_uppercase() {
11497 t.extend(c.to_lowercase());
11498 } else {
11499 t.extend(c.to_uppercase());
11500 }
11501 t
11502 })
11503 })
11504 }
11505
11506 pub fn convert_to_sentence_case(
11507 &mut self,
11508 _: &ConvertToSentenceCase,
11509 window: &mut Window,
11510 cx: &mut Context<Self>,
11511 ) {
11512 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11513 }
11514
11515 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11516 self.manipulate_text(window, cx, |text| {
11517 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11518 if has_upper_case_characters {
11519 text.to_lowercase()
11520 } else {
11521 text.to_uppercase()
11522 }
11523 })
11524 }
11525
11526 pub fn convert_to_rot13(
11527 &mut self,
11528 _: &ConvertToRot13,
11529 window: &mut Window,
11530 cx: &mut Context<Self>,
11531 ) {
11532 self.manipulate_text(window, cx, |text| {
11533 text.chars()
11534 .map(|c| match c {
11535 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11536 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11537 _ => c,
11538 })
11539 .collect()
11540 })
11541 }
11542
11543 pub fn convert_to_rot47(
11544 &mut self,
11545 _: &ConvertToRot47,
11546 window: &mut Window,
11547 cx: &mut Context<Self>,
11548 ) {
11549 self.manipulate_text(window, cx, |text| {
11550 text.chars()
11551 .map(|c| {
11552 let code_point = c as u32;
11553 if code_point >= 33 && code_point <= 126 {
11554 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11555 }
11556 c
11557 })
11558 .collect()
11559 })
11560 }
11561
11562 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11563 where
11564 Fn: FnMut(&str) -> String,
11565 {
11566 let buffer = self.buffer.read(cx).snapshot(cx);
11567
11568 let mut new_selections = Vec::new();
11569 let mut edits = Vec::new();
11570 let mut selection_adjustment = 0i32;
11571
11572 for selection in self.selections.all_adjusted(cx) {
11573 let selection_is_empty = selection.is_empty();
11574
11575 let (start, end) = if selection_is_empty {
11576 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11577 (word_range.start, word_range.end)
11578 } else {
11579 (
11580 buffer.point_to_offset(selection.start),
11581 buffer.point_to_offset(selection.end),
11582 )
11583 };
11584
11585 let text = buffer.text_for_range(start..end).collect::<String>();
11586 let old_length = text.len() as i32;
11587 let text = callback(&text);
11588
11589 new_selections.push(Selection {
11590 start: (start as i32 - selection_adjustment) as usize,
11591 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11592 goal: SelectionGoal::None,
11593 id: selection.id,
11594 reversed: selection.reversed,
11595 });
11596
11597 selection_adjustment += old_length - text.len() as i32;
11598
11599 edits.push((start..end, text));
11600 }
11601
11602 self.transact(window, cx, |this, window, cx| {
11603 this.buffer.update(cx, |buffer, cx| {
11604 buffer.edit(edits, None, cx);
11605 });
11606
11607 this.change_selections(Default::default(), window, cx, |s| {
11608 s.select(new_selections);
11609 });
11610
11611 this.request_autoscroll(Autoscroll::fit(), cx);
11612 });
11613 }
11614
11615 pub fn move_selection_on_drop(
11616 &mut self,
11617 selection: &Selection<Anchor>,
11618 target: DisplayPoint,
11619 is_cut: bool,
11620 window: &mut Window,
11621 cx: &mut Context<Self>,
11622 ) {
11623 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11624 let buffer = display_map.buffer_snapshot();
11625 let mut edits = Vec::new();
11626 let insert_point = display_map
11627 .clip_point(target, Bias::Left)
11628 .to_point(&display_map);
11629 let text = buffer
11630 .text_for_range(selection.start..selection.end)
11631 .collect::<String>();
11632 if is_cut {
11633 edits.push(((selection.start..selection.end), String::new()));
11634 }
11635 let insert_anchor = buffer.anchor_before(insert_point);
11636 edits.push(((insert_anchor..insert_anchor), text));
11637 let last_edit_start = insert_anchor.bias_left(buffer);
11638 let last_edit_end = insert_anchor.bias_right(buffer);
11639 self.transact(window, cx, |this, window, cx| {
11640 this.buffer.update(cx, |buffer, cx| {
11641 buffer.edit(edits, None, cx);
11642 });
11643 this.change_selections(Default::default(), window, cx, |s| {
11644 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11645 });
11646 });
11647 }
11648
11649 pub fn clear_selection_drag_state(&mut self) {
11650 self.selection_drag_state = SelectionDragState::None;
11651 }
11652
11653 pub fn duplicate(
11654 &mut self,
11655 upwards: bool,
11656 whole_lines: bool,
11657 window: &mut Window,
11658 cx: &mut Context<Self>,
11659 ) {
11660 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11661
11662 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11663 let buffer = display_map.buffer_snapshot();
11664 let selections = self.selections.all::<Point>(cx);
11665
11666 let mut edits = Vec::new();
11667 let mut selections_iter = selections.iter().peekable();
11668 while let Some(selection) = selections_iter.next() {
11669 let mut rows = selection.spanned_rows(false, &display_map);
11670 // duplicate line-wise
11671 if whole_lines || selection.start == selection.end {
11672 // Avoid duplicating the same lines twice.
11673 while let Some(next_selection) = selections_iter.peek() {
11674 let next_rows = next_selection.spanned_rows(false, &display_map);
11675 if next_rows.start < rows.end {
11676 rows.end = next_rows.end;
11677 selections_iter.next().unwrap();
11678 } else {
11679 break;
11680 }
11681 }
11682
11683 // Copy the text from the selected row region and splice it either at the start
11684 // or end of the region.
11685 let start = Point::new(rows.start.0, 0);
11686 let end = Point::new(
11687 rows.end.previous_row().0,
11688 buffer.line_len(rows.end.previous_row()),
11689 );
11690 let text = buffer
11691 .text_for_range(start..end)
11692 .chain(Some("\n"))
11693 .collect::<String>();
11694 let insert_location = if upwards {
11695 Point::new(rows.end.0, 0)
11696 } else {
11697 start
11698 };
11699 edits.push((insert_location..insert_location, text));
11700 } else {
11701 // duplicate character-wise
11702 let start = selection.start;
11703 let end = selection.end;
11704 let text = buffer.text_for_range(start..end).collect::<String>();
11705 edits.push((selection.end..selection.end, text));
11706 }
11707 }
11708
11709 self.transact(window, cx, |this, _, cx| {
11710 this.buffer.update(cx, |buffer, cx| {
11711 buffer.edit(edits, None, cx);
11712 });
11713
11714 this.request_autoscroll(Autoscroll::fit(), cx);
11715 });
11716 }
11717
11718 pub fn duplicate_line_up(
11719 &mut self,
11720 _: &DuplicateLineUp,
11721 window: &mut Window,
11722 cx: &mut Context<Self>,
11723 ) {
11724 self.duplicate(true, true, window, cx);
11725 }
11726
11727 pub fn duplicate_line_down(
11728 &mut self,
11729 _: &DuplicateLineDown,
11730 window: &mut Window,
11731 cx: &mut Context<Self>,
11732 ) {
11733 self.duplicate(false, true, window, cx);
11734 }
11735
11736 pub fn duplicate_selection(
11737 &mut self,
11738 _: &DuplicateSelection,
11739 window: &mut Window,
11740 cx: &mut Context<Self>,
11741 ) {
11742 self.duplicate(false, false, window, cx);
11743 }
11744
11745 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11746 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11747 if self.mode.is_single_line() {
11748 cx.propagate();
11749 return;
11750 }
11751
11752 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11753 let buffer = self.buffer.read(cx).snapshot(cx);
11754
11755 let mut edits = Vec::new();
11756 let mut unfold_ranges = Vec::new();
11757 let mut refold_creases = Vec::new();
11758
11759 let selections = self.selections.all::<Point>(cx);
11760 let mut selections = selections.iter().peekable();
11761 let mut contiguous_row_selections = Vec::new();
11762 let mut new_selections = Vec::new();
11763
11764 while let Some(selection) = selections.next() {
11765 // Find all the selections that span a contiguous row range
11766 let (start_row, end_row) = consume_contiguous_rows(
11767 &mut contiguous_row_selections,
11768 selection,
11769 &display_map,
11770 &mut selections,
11771 );
11772
11773 // Move the text spanned by the row range to be before the line preceding the row range
11774 if start_row.0 > 0 {
11775 let range_to_move = Point::new(
11776 start_row.previous_row().0,
11777 buffer.line_len(start_row.previous_row()),
11778 )
11779 ..Point::new(
11780 end_row.previous_row().0,
11781 buffer.line_len(end_row.previous_row()),
11782 );
11783 let insertion_point = display_map
11784 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11785 .0;
11786
11787 // Don't move lines across excerpts
11788 if buffer
11789 .excerpt_containing(insertion_point..range_to_move.end)
11790 .is_some()
11791 {
11792 let text = buffer
11793 .text_for_range(range_to_move.clone())
11794 .flat_map(|s| s.chars())
11795 .skip(1)
11796 .chain(['\n'])
11797 .collect::<String>();
11798
11799 edits.push((
11800 buffer.anchor_after(range_to_move.start)
11801 ..buffer.anchor_before(range_to_move.end),
11802 String::new(),
11803 ));
11804 let insertion_anchor = buffer.anchor_after(insertion_point);
11805 edits.push((insertion_anchor..insertion_anchor, text));
11806
11807 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11808
11809 // Move selections up
11810 new_selections.extend(contiguous_row_selections.drain(..).map(
11811 |mut selection| {
11812 selection.start.row -= row_delta;
11813 selection.end.row -= row_delta;
11814 selection
11815 },
11816 ));
11817
11818 // Move folds up
11819 unfold_ranges.push(range_to_move.clone());
11820 for fold in display_map.folds_in_range(
11821 buffer.anchor_before(range_to_move.start)
11822 ..buffer.anchor_after(range_to_move.end),
11823 ) {
11824 let mut start = fold.range.start.to_point(&buffer);
11825 let mut end = fold.range.end.to_point(&buffer);
11826 start.row -= row_delta;
11827 end.row -= row_delta;
11828 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11829 }
11830 }
11831 }
11832
11833 // If we didn't move line(s), preserve the existing selections
11834 new_selections.append(&mut contiguous_row_selections);
11835 }
11836
11837 self.transact(window, cx, |this, window, cx| {
11838 this.unfold_ranges(&unfold_ranges, true, true, cx);
11839 this.buffer.update(cx, |buffer, cx| {
11840 for (range, text) in edits {
11841 buffer.edit([(range, text)], None, cx);
11842 }
11843 });
11844 this.fold_creases(refold_creases, true, window, cx);
11845 this.change_selections(Default::default(), window, cx, |s| {
11846 s.select(new_selections);
11847 })
11848 });
11849 }
11850
11851 pub fn move_line_down(
11852 &mut self,
11853 _: &MoveLineDown,
11854 window: &mut Window,
11855 cx: &mut Context<Self>,
11856 ) {
11857 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11858 if self.mode.is_single_line() {
11859 cx.propagate();
11860 return;
11861 }
11862
11863 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11864 let buffer = self.buffer.read(cx).snapshot(cx);
11865
11866 let mut edits = Vec::new();
11867 let mut unfold_ranges = Vec::new();
11868 let mut refold_creases = Vec::new();
11869
11870 let selections = self.selections.all::<Point>(cx);
11871 let mut selections = selections.iter().peekable();
11872 let mut contiguous_row_selections = Vec::new();
11873 let mut new_selections = Vec::new();
11874
11875 while let Some(selection) = selections.next() {
11876 // Find all the selections that span a contiguous row range
11877 let (start_row, end_row) = consume_contiguous_rows(
11878 &mut contiguous_row_selections,
11879 selection,
11880 &display_map,
11881 &mut selections,
11882 );
11883
11884 // Move the text spanned by the row range to be after the last line of the row range
11885 if end_row.0 <= buffer.max_point().row {
11886 let range_to_move =
11887 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11888 let insertion_point = display_map
11889 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11890 .0;
11891
11892 // Don't move lines across excerpt boundaries
11893 if buffer
11894 .excerpt_containing(range_to_move.start..insertion_point)
11895 .is_some()
11896 {
11897 let mut text = String::from("\n");
11898 text.extend(buffer.text_for_range(range_to_move.clone()));
11899 text.pop(); // Drop trailing newline
11900 edits.push((
11901 buffer.anchor_after(range_to_move.start)
11902 ..buffer.anchor_before(range_to_move.end),
11903 String::new(),
11904 ));
11905 let insertion_anchor = buffer.anchor_after(insertion_point);
11906 edits.push((insertion_anchor..insertion_anchor, text));
11907
11908 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11909
11910 // Move selections down
11911 new_selections.extend(contiguous_row_selections.drain(..).map(
11912 |mut selection| {
11913 selection.start.row += row_delta;
11914 selection.end.row += row_delta;
11915 selection
11916 },
11917 ));
11918
11919 // Move folds down
11920 unfold_ranges.push(range_to_move.clone());
11921 for fold in display_map.folds_in_range(
11922 buffer.anchor_before(range_to_move.start)
11923 ..buffer.anchor_after(range_to_move.end),
11924 ) {
11925 let mut start = fold.range.start.to_point(&buffer);
11926 let mut end = fold.range.end.to_point(&buffer);
11927 start.row += row_delta;
11928 end.row += row_delta;
11929 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11930 }
11931 }
11932 }
11933
11934 // If we didn't move line(s), preserve the existing selections
11935 new_selections.append(&mut contiguous_row_selections);
11936 }
11937
11938 self.transact(window, cx, |this, window, cx| {
11939 this.unfold_ranges(&unfold_ranges, true, true, cx);
11940 this.buffer.update(cx, |buffer, cx| {
11941 for (range, text) in edits {
11942 buffer.edit([(range, text)], None, cx);
11943 }
11944 });
11945 this.fold_creases(refold_creases, true, window, cx);
11946 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11947 });
11948 }
11949
11950 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11951 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11952 let text_layout_details = &self.text_layout_details(window);
11953 self.transact(window, cx, |this, window, cx| {
11954 let edits = this.change_selections(Default::default(), window, cx, |s| {
11955 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11956 s.move_with(|display_map, selection| {
11957 if !selection.is_empty() {
11958 return;
11959 }
11960
11961 let mut head = selection.head();
11962 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11963 if head.column() == display_map.line_len(head.row()) {
11964 transpose_offset = display_map
11965 .buffer_snapshot()
11966 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11967 }
11968
11969 if transpose_offset == 0 {
11970 return;
11971 }
11972
11973 *head.column_mut() += 1;
11974 head = display_map.clip_point(head, Bias::Right);
11975 let goal = SelectionGoal::HorizontalPosition(
11976 display_map
11977 .x_for_display_point(head, text_layout_details)
11978 .into(),
11979 );
11980 selection.collapse_to(head, goal);
11981
11982 let transpose_start = display_map
11983 .buffer_snapshot()
11984 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11985 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11986 let transpose_end = display_map
11987 .buffer_snapshot()
11988 .clip_offset(transpose_offset + 1, Bias::Right);
11989 if let Some(ch) = display_map
11990 .buffer_snapshot()
11991 .chars_at(transpose_start)
11992 .next()
11993 {
11994 edits.push((transpose_start..transpose_offset, String::new()));
11995 edits.push((transpose_end..transpose_end, ch.to_string()));
11996 }
11997 }
11998 });
11999 edits
12000 });
12001 this.buffer
12002 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12003 let selections = this.selections.all::<usize>(cx);
12004 this.change_selections(Default::default(), window, cx, |s| {
12005 s.select(selections);
12006 });
12007 });
12008 }
12009
12010 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12011 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12012 if self.mode.is_single_line() {
12013 cx.propagate();
12014 return;
12015 }
12016
12017 self.rewrap_impl(RewrapOptions::default(), cx)
12018 }
12019
12020 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12021 let buffer = self.buffer.read(cx).snapshot(cx);
12022 let selections = self.selections.all::<Point>(cx);
12023
12024 #[derive(Clone, Debug, PartialEq)]
12025 enum CommentFormat {
12026 /// single line comment, with prefix for line
12027 Line(String),
12028 /// single line within a block comment, with prefix for line
12029 BlockLine(String),
12030 /// a single line of a block comment that includes the initial delimiter
12031 BlockCommentWithStart(BlockCommentConfig),
12032 /// a single line of a block comment that includes the ending delimiter
12033 BlockCommentWithEnd(BlockCommentConfig),
12034 }
12035
12036 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12037 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12038 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12039 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12040 .peekable();
12041
12042 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12043 row
12044 } else {
12045 return Vec::new();
12046 };
12047
12048 let language_settings = buffer.language_settings_at(selection.head(), cx);
12049 let language_scope = buffer.language_scope_at(selection.head());
12050
12051 let indent_and_prefix_for_row =
12052 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12053 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12054 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12055 &language_scope
12056 {
12057 let indent_end = Point::new(row, indent.len);
12058 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12059 let line_text_after_indent = buffer
12060 .text_for_range(indent_end..line_end)
12061 .collect::<String>();
12062
12063 let is_within_comment_override = buffer
12064 .language_scope_at(indent_end)
12065 .is_some_and(|scope| scope.override_name() == Some("comment"));
12066 let comment_delimiters = if is_within_comment_override {
12067 // we are within a comment syntax node, but we don't
12068 // yet know what kind of comment: block, doc or line
12069 match (
12070 language_scope.documentation_comment(),
12071 language_scope.block_comment(),
12072 ) {
12073 (Some(config), _) | (_, Some(config))
12074 if buffer.contains_str_at(indent_end, &config.start) =>
12075 {
12076 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12077 }
12078 (Some(config), _) | (_, Some(config))
12079 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12080 {
12081 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12082 }
12083 (Some(config), _) | (_, Some(config))
12084 if buffer.contains_str_at(indent_end, &config.prefix) =>
12085 {
12086 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12087 }
12088 (_, _) => language_scope
12089 .line_comment_prefixes()
12090 .iter()
12091 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12092 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12093 }
12094 } else {
12095 // we not in an overridden comment node, but we may
12096 // be within a non-overridden line comment node
12097 language_scope
12098 .line_comment_prefixes()
12099 .iter()
12100 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12101 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12102 };
12103
12104 let rewrap_prefix = language_scope
12105 .rewrap_prefixes()
12106 .iter()
12107 .find_map(|prefix_regex| {
12108 prefix_regex.find(&line_text_after_indent).map(|mat| {
12109 if mat.start() == 0 {
12110 Some(mat.as_str().to_string())
12111 } else {
12112 None
12113 }
12114 })
12115 })
12116 .flatten();
12117 (comment_delimiters, rewrap_prefix)
12118 } else {
12119 (None, None)
12120 };
12121 (indent, comment_prefix, rewrap_prefix)
12122 };
12123
12124 let mut ranges = Vec::new();
12125 let from_empty_selection = selection.is_empty();
12126
12127 let mut current_range_start = first_row;
12128 let mut prev_row = first_row;
12129 let (
12130 mut current_range_indent,
12131 mut current_range_comment_delimiters,
12132 mut current_range_rewrap_prefix,
12133 ) = indent_and_prefix_for_row(first_row);
12134
12135 for row in non_blank_rows_iter.skip(1) {
12136 let has_paragraph_break = row > prev_row + 1;
12137
12138 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12139 indent_and_prefix_for_row(row);
12140
12141 let has_indent_change = row_indent != current_range_indent;
12142 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12143
12144 let has_boundary_change = has_comment_change
12145 || row_rewrap_prefix.is_some()
12146 || (has_indent_change && current_range_comment_delimiters.is_some());
12147
12148 if has_paragraph_break || has_boundary_change {
12149 ranges.push((
12150 language_settings.clone(),
12151 Point::new(current_range_start, 0)
12152 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12153 current_range_indent,
12154 current_range_comment_delimiters.clone(),
12155 current_range_rewrap_prefix.clone(),
12156 from_empty_selection,
12157 ));
12158 current_range_start = row;
12159 current_range_indent = row_indent;
12160 current_range_comment_delimiters = row_comment_delimiters;
12161 current_range_rewrap_prefix = row_rewrap_prefix;
12162 }
12163 prev_row = row;
12164 }
12165
12166 ranges.push((
12167 language_settings.clone(),
12168 Point::new(current_range_start, 0)
12169 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12170 current_range_indent,
12171 current_range_comment_delimiters,
12172 current_range_rewrap_prefix,
12173 from_empty_selection,
12174 ));
12175
12176 ranges
12177 });
12178
12179 let mut edits = Vec::new();
12180 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12181
12182 for (
12183 language_settings,
12184 wrap_range,
12185 mut indent_size,
12186 comment_prefix,
12187 rewrap_prefix,
12188 from_empty_selection,
12189 ) in wrap_ranges
12190 {
12191 let mut start_row = wrap_range.start.row;
12192 let mut end_row = wrap_range.end.row;
12193
12194 // Skip selections that overlap with a range that has already been rewrapped.
12195 let selection_range = start_row..end_row;
12196 if rewrapped_row_ranges
12197 .iter()
12198 .any(|range| range.overlaps(&selection_range))
12199 {
12200 continue;
12201 }
12202
12203 let tab_size = language_settings.tab_size;
12204
12205 let (line_prefix, inside_comment) = match &comment_prefix {
12206 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12207 (Some(prefix.as_str()), true)
12208 }
12209 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12210 (Some(prefix.as_ref()), true)
12211 }
12212 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12213 start: _,
12214 end: _,
12215 prefix,
12216 tab_size,
12217 })) => {
12218 indent_size.len += tab_size;
12219 (Some(prefix.as_ref()), true)
12220 }
12221 None => (None, false),
12222 };
12223 let indent_prefix = indent_size.chars().collect::<String>();
12224 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12225
12226 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12227 RewrapBehavior::InComments => inside_comment,
12228 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12229 RewrapBehavior::Anywhere => true,
12230 };
12231
12232 let should_rewrap = options.override_language_settings
12233 || allow_rewrap_based_on_language
12234 || self.hard_wrap.is_some();
12235 if !should_rewrap {
12236 continue;
12237 }
12238
12239 if from_empty_selection {
12240 'expand_upwards: while start_row > 0 {
12241 let prev_row = start_row - 1;
12242 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12243 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12244 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12245 {
12246 start_row = prev_row;
12247 } else {
12248 break 'expand_upwards;
12249 }
12250 }
12251
12252 'expand_downwards: while end_row < buffer.max_point().row {
12253 let next_row = end_row + 1;
12254 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12255 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12256 && !buffer.is_line_blank(MultiBufferRow(next_row))
12257 {
12258 end_row = next_row;
12259 } else {
12260 break 'expand_downwards;
12261 }
12262 }
12263 }
12264
12265 let start = Point::new(start_row, 0);
12266 let start_offset = ToOffset::to_offset(&start, &buffer);
12267 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12268 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12269 let mut first_line_delimiter = None;
12270 let mut last_line_delimiter = None;
12271 let Some(lines_without_prefixes) = selection_text
12272 .lines()
12273 .enumerate()
12274 .map(|(ix, line)| {
12275 let line_trimmed = line.trim_start();
12276 if rewrap_prefix.is_some() && ix > 0 {
12277 Ok(line_trimmed)
12278 } else if let Some(
12279 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12280 start,
12281 prefix,
12282 end,
12283 tab_size,
12284 })
12285 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12286 start,
12287 prefix,
12288 end,
12289 tab_size,
12290 }),
12291 ) = &comment_prefix
12292 {
12293 let line_trimmed = line_trimmed
12294 .strip_prefix(start.as_ref())
12295 .map(|s| {
12296 let mut indent_size = indent_size;
12297 indent_size.len -= tab_size;
12298 let indent_prefix: String = indent_size.chars().collect();
12299 first_line_delimiter = Some((indent_prefix, start));
12300 s.trim_start()
12301 })
12302 .unwrap_or(line_trimmed);
12303 let line_trimmed = line_trimmed
12304 .strip_suffix(end.as_ref())
12305 .map(|s| {
12306 last_line_delimiter = Some(end);
12307 s.trim_end()
12308 })
12309 .unwrap_or(line_trimmed);
12310 let line_trimmed = line_trimmed
12311 .strip_prefix(prefix.as_ref())
12312 .unwrap_or(line_trimmed);
12313 Ok(line_trimmed)
12314 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12315 line_trimmed.strip_prefix(prefix).with_context(|| {
12316 format!("line did not start with prefix {prefix:?}: {line:?}")
12317 })
12318 } else {
12319 line_trimmed
12320 .strip_prefix(&line_prefix.trim_start())
12321 .with_context(|| {
12322 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12323 })
12324 }
12325 })
12326 .collect::<Result<Vec<_>, _>>()
12327 .log_err()
12328 else {
12329 continue;
12330 };
12331
12332 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12333 buffer
12334 .language_settings_at(Point::new(start_row, 0), cx)
12335 .preferred_line_length as usize
12336 });
12337
12338 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12339 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12340 } else {
12341 line_prefix.clone()
12342 };
12343
12344 let wrapped_text = {
12345 let mut wrapped_text = wrap_with_prefix(
12346 line_prefix,
12347 subsequent_lines_prefix,
12348 lines_without_prefixes.join("\n"),
12349 wrap_column,
12350 tab_size,
12351 options.preserve_existing_whitespace,
12352 );
12353
12354 if let Some((indent, delimiter)) = first_line_delimiter {
12355 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12356 }
12357 if let Some(last_line) = last_line_delimiter {
12358 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12359 }
12360
12361 wrapped_text
12362 };
12363
12364 // TODO: should always use char-based diff while still supporting cursor behavior that
12365 // matches vim.
12366 let mut diff_options = DiffOptions::default();
12367 if options.override_language_settings {
12368 diff_options.max_word_diff_len = 0;
12369 diff_options.max_word_diff_line_count = 0;
12370 } else {
12371 diff_options.max_word_diff_len = usize::MAX;
12372 diff_options.max_word_diff_line_count = usize::MAX;
12373 }
12374
12375 for (old_range, new_text) in
12376 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12377 {
12378 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12379 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12380 edits.push((edit_start..edit_end, new_text));
12381 }
12382
12383 rewrapped_row_ranges.push(start_row..=end_row);
12384 }
12385
12386 self.buffer
12387 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12388 }
12389
12390 pub fn cut_common(
12391 &mut self,
12392 cut_no_selection_line: bool,
12393 window: &mut Window,
12394 cx: &mut Context<Self>,
12395 ) -> ClipboardItem {
12396 let mut text = String::new();
12397 let buffer = self.buffer.read(cx).snapshot(cx);
12398 let mut selections = self.selections.all::<Point>(cx);
12399 let mut clipboard_selections = Vec::with_capacity(selections.len());
12400 {
12401 let max_point = buffer.max_point();
12402 let mut is_first = true;
12403 for selection in &mut selections {
12404 let is_entire_line =
12405 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12406 if is_entire_line {
12407 selection.start = Point::new(selection.start.row, 0);
12408 if !selection.is_empty() && selection.end.column == 0 {
12409 selection.end = cmp::min(max_point, selection.end);
12410 } else {
12411 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12412 }
12413 selection.goal = SelectionGoal::None;
12414 }
12415 if is_first {
12416 is_first = false;
12417 } else {
12418 text += "\n";
12419 }
12420 let mut len = 0;
12421 for chunk in buffer.text_for_range(selection.start..selection.end) {
12422 text.push_str(chunk);
12423 len += chunk.len();
12424 }
12425 clipboard_selections.push(ClipboardSelection {
12426 len,
12427 is_entire_line,
12428 first_line_indent: buffer
12429 .indent_size_for_line(MultiBufferRow(selection.start.row))
12430 .len,
12431 });
12432 }
12433 }
12434
12435 self.transact(window, cx, |this, window, cx| {
12436 this.change_selections(Default::default(), window, cx, |s| {
12437 s.select(selections);
12438 });
12439 this.insert("", window, cx);
12440 });
12441 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12442 }
12443
12444 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12445 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12446 let item = self.cut_common(true, window, cx);
12447 cx.write_to_clipboard(item);
12448 }
12449
12450 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12451 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12452 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12453 s.move_with(|snapshot, sel| {
12454 if sel.is_empty() {
12455 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12456 }
12457 if sel.is_empty() {
12458 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12459 }
12460 });
12461 });
12462 let item = self.cut_common(false, window, cx);
12463 cx.set_global(KillRing(item))
12464 }
12465
12466 pub fn kill_ring_yank(
12467 &mut self,
12468 _: &KillRingYank,
12469 window: &mut Window,
12470 cx: &mut Context<Self>,
12471 ) {
12472 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12473 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12474 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12475 (kill_ring.text().to_string(), kill_ring.metadata_json())
12476 } else {
12477 return;
12478 }
12479 } else {
12480 return;
12481 };
12482 self.do_paste(&text, metadata, false, window, cx);
12483 }
12484
12485 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12486 self.do_copy(true, cx);
12487 }
12488
12489 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12490 self.do_copy(false, cx);
12491 }
12492
12493 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12494 let selections = self.selections.all::<Point>(cx);
12495 let buffer = self.buffer.read(cx).read(cx);
12496 let mut text = String::new();
12497
12498 let mut clipboard_selections = Vec::with_capacity(selections.len());
12499 {
12500 let max_point = buffer.max_point();
12501 let mut is_first = true;
12502 for selection in &selections {
12503 let mut start = selection.start;
12504 let mut end = selection.end;
12505 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12506 if is_entire_line {
12507 start = Point::new(start.row, 0);
12508 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12509 }
12510
12511 let mut trimmed_selections = Vec::new();
12512 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12513 let row = MultiBufferRow(start.row);
12514 let first_indent = buffer.indent_size_for_line(row);
12515 if first_indent.len == 0 || start.column > first_indent.len {
12516 trimmed_selections.push(start..end);
12517 } else {
12518 trimmed_selections.push(
12519 Point::new(row.0, first_indent.len)
12520 ..Point::new(row.0, buffer.line_len(row)),
12521 );
12522 for row in start.row + 1..=end.row {
12523 let mut line_len = buffer.line_len(MultiBufferRow(row));
12524 if row == end.row {
12525 line_len = end.column;
12526 }
12527 if line_len == 0 {
12528 trimmed_selections
12529 .push(Point::new(row, 0)..Point::new(row, line_len));
12530 continue;
12531 }
12532 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12533 if row_indent_size.len >= first_indent.len {
12534 trimmed_selections.push(
12535 Point::new(row, first_indent.len)..Point::new(row, line_len),
12536 );
12537 } else {
12538 trimmed_selections.clear();
12539 trimmed_selections.push(start..end);
12540 break;
12541 }
12542 }
12543 }
12544 } else {
12545 trimmed_selections.push(start..end);
12546 }
12547
12548 for trimmed_range in trimmed_selections {
12549 if is_first {
12550 is_first = false;
12551 } else {
12552 text += "\n";
12553 }
12554 let mut len = 0;
12555 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12556 text.push_str(chunk);
12557 len += chunk.len();
12558 }
12559 clipboard_selections.push(ClipboardSelection {
12560 len,
12561 is_entire_line,
12562 first_line_indent: buffer
12563 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12564 .len,
12565 });
12566 }
12567 }
12568 }
12569
12570 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12571 text,
12572 clipboard_selections,
12573 ));
12574 }
12575
12576 pub fn do_paste(
12577 &mut self,
12578 text: &String,
12579 clipboard_selections: Option<Vec<ClipboardSelection>>,
12580 handle_entire_lines: bool,
12581 window: &mut Window,
12582 cx: &mut Context<Self>,
12583 ) {
12584 if self.read_only(cx) {
12585 return;
12586 }
12587
12588 let clipboard_text = Cow::Borrowed(text.as_str());
12589
12590 self.transact(window, cx, |this, window, cx| {
12591 let had_active_edit_prediction = this.has_active_edit_prediction();
12592 let old_selections = this.selections.all::<usize>(cx);
12593 let cursor_offset = this.selections.last::<usize>(cx).head();
12594
12595 if let Some(mut clipboard_selections) = clipboard_selections {
12596 let all_selections_were_entire_line =
12597 clipboard_selections.iter().all(|s| s.is_entire_line);
12598 let first_selection_indent_column =
12599 clipboard_selections.first().map(|s| s.first_line_indent);
12600 if clipboard_selections.len() != old_selections.len() {
12601 clipboard_selections.drain(..);
12602 }
12603 let mut auto_indent_on_paste = true;
12604
12605 this.buffer.update(cx, |buffer, cx| {
12606 let snapshot = buffer.read(cx);
12607 auto_indent_on_paste = snapshot
12608 .language_settings_at(cursor_offset, cx)
12609 .auto_indent_on_paste;
12610
12611 let mut start_offset = 0;
12612 let mut edits = Vec::new();
12613 let mut original_indent_columns = Vec::new();
12614 for (ix, selection) in old_selections.iter().enumerate() {
12615 let to_insert;
12616 let entire_line;
12617 let original_indent_column;
12618 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12619 let end_offset = start_offset + clipboard_selection.len;
12620 to_insert = &clipboard_text[start_offset..end_offset];
12621 entire_line = clipboard_selection.is_entire_line;
12622 start_offset = end_offset + 1;
12623 original_indent_column = Some(clipboard_selection.first_line_indent);
12624 } else {
12625 to_insert = &*clipboard_text;
12626 entire_line = all_selections_were_entire_line;
12627 original_indent_column = first_selection_indent_column
12628 }
12629
12630 let (range, to_insert) =
12631 if selection.is_empty() && handle_entire_lines && entire_line {
12632 // If the corresponding selection was empty when this slice of the
12633 // clipboard text was written, then the entire line containing the
12634 // selection was copied. If this selection is also currently empty,
12635 // then paste the line before the current line of the buffer.
12636 let column = selection.start.to_point(&snapshot).column as usize;
12637 let line_start = selection.start - column;
12638 (line_start..line_start, Cow::Borrowed(to_insert))
12639 } else {
12640 let language = snapshot.language_at(selection.head());
12641 let range = selection.range();
12642 if let Some(language) = language
12643 && language.name() == "Markdown".into()
12644 {
12645 edit_for_markdown_paste(
12646 &snapshot,
12647 range,
12648 to_insert,
12649 url::Url::parse(to_insert).ok(),
12650 )
12651 } else {
12652 (range, Cow::Borrowed(to_insert))
12653 }
12654 };
12655
12656 edits.push((range, to_insert));
12657 original_indent_columns.push(original_indent_column);
12658 }
12659 drop(snapshot);
12660
12661 buffer.edit(
12662 edits,
12663 if auto_indent_on_paste {
12664 Some(AutoindentMode::Block {
12665 original_indent_columns,
12666 })
12667 } else {
12668 None
12669 },
12670 cx,
12671 );
12672 });
12673
12674 let selections = this.selections.all::<usize>(cx);
12675 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12676 } else {
12677 let url = url::Url::parse(&clipboard_text).ok();
12678
12679 let auto_indent_mode = if !clipboard_text.is_empty() {
12680 Some(AutoindentMode::Block {
12681 original_indent_columns: Vec::new(),
12682 })
12683 } else {
12684 None
12685 };
12686
12687 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12688 let snapshot = buffer.snapshot(cx);
12689
12690 let anchors = old_selections
12691 .iter()
12692 .map(|s| {
12693 let anchor = snapshot.anchor_after(s.head());
12694 s.map(|_| anchor)
12695 })
12696 .collect::<Vec<_>>();
12697
12698 let mut edits = Vec::new();
12699
12700 for selection in old_selections.iter() {
12701 let language = snapshot.language_at(selection.head());
12702 let range = selection.range();
12703
12704 let (edit_range, edit_text) = if let Some(language) = language
12705 && language.name() == "Markdown".into()
12706 {
12707 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12708 } else {
12709 (range, clipboard_text.clone())
12710 };
12711
12712 edits.push((edit_range, edit_text));
12713 }
12714
12715 drop(snapshot);
12716 buffer.edit(edits, auto_indent_mode, cx);
12717
12718 anchors
12719 });
12720
12721 this.change_selections(Default::default(), window, cx, |s| {
12722 s.select_anchors(selection_anchors);
12723 });
12724 }
12725
12726 let trigger_in_words =
12727 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12728
12729 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12730 });
12731 }
12732
12733 pub fn diff_clipboard_with_selection(
12734 &mut self,
12735 _: &DiffClipboardWithSelection,
12736 window: &mut Window,
12737 cx: &mut Context<Self>,
12738 ) {
12739 let selections = self.selections.all::<usize>(cx);
12740
12741 if selections.is_empty() {
12742 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12743 return;
12744 };
12745
12746 let clipboard_text = match cx.read_from_clipboard() {
12747 Some(item) => match item.entries().first() {
12748 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12749 _ => None,
12750 },
12751 None => None,
12752 };
12753
12754 let Some(clipboard_text) = clipboard_text else {
12755 log::warn!("Clipboard doesn't contain text.");
12756 return;
12757 };
12758
12759 window.dispatch_action(
12760 Box::new(DiffClipboardWithSelectionData {
12761 clipboard_text,
12762 editor: cx.entity(),
12763 }),
12764 cx,
12765 );
12766 }
12767
12768 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12769 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12770 if let Some(item) = cx.read_from_clipboard() {
12771 let entries = item.entries();
12772
12773 match entries.first() {
12774 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12775 // of all the pasted entries.
12776 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12777 .do_paste(
12778 clipboard_string.text(),
12779 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12780 true,
12781 window,
12782 cx,
12783 ),
12784 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12785 }
12786 }
12787 }
12788
12789 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12790 if self.read_only(cx) {
12791 return;
12792 }
12793
12794 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12795
12796 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12797 if let Some((selections, _)) =
12798 self.selection_history.transaction(transaction_id).cloned()
12799 {
12800 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12801 s.select_anchors(selections.to_vec());
12802 });
12803 } else {
12804 log::error!(
12805 "No entry in selection_history found for undo. \
12806 This may correspond to a bug where undo does not update the selection. \
12807 If this is occurring, please add details to \
12808 https://github.com/zed-industries/zed/issues/22692"
12809 );
12810 }
12811 self.request_autoscroll(Autoscroll::fit(), cx);
12812 self.unmark_text(window, cx);
12813 self.refresh_edit_prediction(true, false, window, cx);
12814 cx.emit(EditorEvent::Edited { transaction_id });
12815 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12816 }
12817 }
12818
12819 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12820 if self.read_only(cx) {
12821 return;
12822 }
12823
12824 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12825
12826 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12827 if let Some((_, Some(selections))) =
12828 self.selection_history.transaction(transaction_id).cloned()
12829 {
12830 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12831 s.select_anchors(selections.to_vec());
12832 });
12833 } else {
12834 log::error!(
12835 "No entry in selection_history found for redo. \
12836 This may correspond to a bug where undo does not update the selection. \
12837 If this is occurring, please add details to \
12838 https://github.com/zed-industries/zed/issues/22692"
12839 );
12840 }
12841 self.request_autoscroll(Autoscroll::fit(), cx);
12842 self.unmark_text(window, cx);
12843 self.refresh_edit_prediction(true, false, window, cx);
12844 cx.emit(EditorEvent::Edited { transaction_id });
12845 }
12846 }
12847
12848 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12849 self.buffer
12850 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12851 }
12852
12853 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12854 self.buffer
12855 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12856 }
12857
12858 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12859 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12860 self.change_selections(Default::default(), window, cx, |s| {
12861 s.move_with(|map, selection| {
12862 let cursor = if selection.is_empty() {
12863 movement::left(map, selection.start)
12864 } else {
12865 selection.start
12866 };
12867 selection.collapse_to(cursor, SelectionGoal::None);
12868 });
12869 })
12870 }
12871
12872 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12873 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12874 self.change_selections(Default::default(), window, cx, |s| {
12875 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12876 })
12877 }
12878
12879 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12880 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12881 self.change_selections(Default::default(), window, cx, |s| {
12882 s.move_with(|map, selection| {
12883 let cursor = if selection.is_empty() {
12884 movement::right(map, selection.end)
12885 } else {
12886 selection.end
12887 };
12888 selection.collapse_to(cursor, SelectionGoal::None)
12889 });
12890 })
12891 }
12892
12893 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12894 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12895 self.change_selections(Default::default(), window, cx, |s| {
12896 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12897 });
12898 }
12899
12900 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12901 if self.take_rename(true, window, cx).is_some() {
12902 return;
12903 }
12904
12905 if self.mode.is_single_line() {
12906 cx.propagate();
12907 return;
12908 }
12909
12910 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12911
12912 let text_layout_details = &self.text_layout_details(window);
12913 let selection_count = self.selections.count();
12914 let first_selection = self.selections.first_anchor();
12915
12916 self.change_selections(Default::default(), window, cx, |s| {
12917 s.move_with(|map, selection| {
12918 if !selection.is_empty() {
12919 selection.goal = SelectionGoal::None;
12920 }
12921 let (cursor, goal) = movement::up(
12922 map,
12923 selection.start,
12924 selection.goal,
12925 false,
12926 text_layout_details,
12927 );
12928 selection.collapse_to(cursor, goal);
12929 });
12930 });
12931
12932 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12933 {
12934 cx.propagate();
12935 }
12936 }
12937
12938 pub fn move_up_by_lines(
12939 &mut self,
12940 action: &MoveUpByLines,
12941 window: &mut Window,
12942 cx: &mut Context<Self>,
12943 ) {
12944 if self.take_rename(true, window, cx).is_some() {
12945 return;
12946 }
12947
12948 if self.mode.is_single_line() {
12949 cx.propagate();
12950 return;
12951 }
12952
12953 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12954
12955 let text_layout_details = &self.text_layout_details(window);
12956
12957 self.change_selections(Default::default(), window, cx, |s| {
12958 s.move_with(|map, selection| {
12959 if !selection.is_empty() {
12960 selection.goal = SelectionGoal::None;
12961 }
12962 let (cursor, goal) = movement::up_by_rows(
12963 map,
12964 selection.start,
12965 action.lines,
12966 selection.goal,
12967 false,
12968 text_layout_details,
12969 );
12970 selection.collapse_to(cursor, goal);
12971 });
12972 })
12973 }
12974
12975 pub fn move_down_by_lines(
12976 &mut self,
12977 action: &MoveDownByLines,
12978 window: &mut Window,
12979 cx: &mut Context<Self>,
12980 ) {
12981 if self.take_rename(true, window, cx).is_some() {
12982 return;
12983 }
12984
12985 if self.mode.is_single_line() {
12986 cx.propagate();
12987 return;
12988 }
12989
12990 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12991
12992 let text_layout_details = &self.text_layout_details(window);
12993
12994 self.change_selections(Default::default(), window, cx, |s| {
12995 s.move_with(|map, selection| {
12996 if !selection.is_empty() {
12997 selection.goal = SelectionGoal::None;
12998 }
12999 let (cursor, goal) = movement::down_by_rows(
13000 map,
13001 selection.start,
13002 action.lines,
13003 selection.goal,
13004 false,
13005 text_layout_details,
13006 );
13007 selection.collapse_to(cursor, goal);
13008 });
13009 })
13010 }
13011
13012 pub fn select_down_by_lines(
13013 &mut self,
13014 action: &SelectDownByLines,
13015 window: &mut Window,
13016 cx: &mut Context<Self>,
13017 ) {
13018 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13019 let text_layout_details = &self.text_layout_details(window);
13020 self.change_selections(Default::default(), window, cx, |s| {
13021 s.move_heads_with(|map, head, goal| {
13022 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13023 })
13024 })
13025 }
13026
13027 pub fn select_up_by_lines(
13028 &mut self,
13029 action: &SelectUpByLines,
13030 window: &mut Window,
13031 cx: &mut Context<Self>,
13032 ) {
13033 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13034 let text_layout_details = &self.text_layout_details(window);
13035 self.change_selections(Default::default(), window, cx, |s| {
13036 s.move_heads_with(|map, head, goal| {
13037 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13038 })
13039 })
13040 }
13041
13042 pub fn select_page_up(
13043 &mut self,
13044 _: &SelectPageUp,
13045 window: &mut Window,
13046 cx: &mut Context<Self>,
13047 ) {
13048 let Some(row_count) = self.visible_row_count() else {
13049 return;
13050 };
13051
13052 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13053
13054 let text_layout_details = &self.text_layout_details(window);
13055
13056 self.change_selections(Default::default(), window, cx, |s| {
13057 s.move_heads_with(|map, head, goal| {
13058 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13059 })
13060 })
13061 }
13062
13063 pub fn move_page_up(
13064 &mut self,
13065 action: &MovePageUp,
13066 window: &mut Window,
13067 cx: &mut Context<Self>,
13068 ) {
13069 if self.take_rename(true, window, cx).is_some() {
13070 return;
13071 }
13072
13073 if self
13074 .context_menu
13075 .borrow_mut()
13076 .as_mut()
13077 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13078 .unwrap_or(false)
13079 {
13080 return;
13081 }
13082
13083 if matches!(self.mode, EditorMode::SingleLine) {
13084 cx.propagate();
13085 return;
13086 }
13087
13088 let Some(row_count) = self.visible_row_count() else {
13089 return;
13090 };
13091
13092 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13093
13094 let effects = if action.center_cursor {
13095 SelectionEffects::scroll(Autoscroll::center())
13096 } else {
13097 SelectionEffects::default()
13098 };
13099
13100 let text_layout_details = &self.text_layout_details(window);
13101
13102 self.change_selections(effects, window, cx, |s| {
13103 s.move_with(|map, selection| {
13104 if !selection.is_empty() {
13105 selection.goal = SelectionGoal::None;
13106 }
13107 let (cursor, goal) = movement::up_by_rows(
13108 map,
13109 selection.end,
13110 row_count,
13111 selection.goal,
13112 false,
13113 text_layout_details,
13114 );
13115 selection.collapse_to(cursor, goal);
13116 });
13117 });
13118 }
13119
13120 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13121 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13122 let text_layout_details = &self.text_layout_details(window);
13123 self.change_selections(Default::default(), window, cx, |s| {
13124 s.move_heads_with(|map, head, goal| {
13125 movement::up(map, head, goal, false, text_layout_details)
13126 })
13127 })
13128 }
13129
13130 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13131 self.take_rename(true, window, cx);
13132
13133 if self.mode.is_single_line() {
13134 cx.propagate();
13135 return;
13136 }
13137
13138 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13139
13140 let text_layout_details = &self.text_layout_details(window);
13141 let selection_count = self.selections.count();
13142 let first_selection = self.selections.first_anchor();
13143
13144 self.change_selections(Default::default(), window, cx, |s| {
13145 s.move_with(|map, selection| {
13146 if !selection.is_empty() {
13147 selection.goal = SelectionGoal::None;
13148 }
13149 let (cursor, goal) = movement::down(
13150 map,
13151 selection.end,
13152 selection.goal,
13153 false,
13154 text_layout_details,
13155 );
13156 selection.collapse_to(cursor, goal);
13157 });
13158 });
13159
13160 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13161 {
13162 cx.propagate();
13163 }
13164 }
13165
13166 pub fn select_page_down(
13167 &mut self,
13168 _: &SelectPageDown,
13169 window: &mut Window,
13170 cx: &mut Context<Self>,
13171 ) {
13172 let Some(row_count) = self.visible_row_count() else {
13173 return;
13174 };
13175
13176 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13177
13178 let text_layout_details = &self.text_layout_details(window);
13179
13180 self.change_selections(Default::default(), window, cx, |s| {
13181 s.move_heads_with(|map, head, goal| {
13182 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13183 })
13184 })
13185 }
13186
13187 pub fn move_page_down(
13188 &mut self,
13189 action: &MovePageDown,
13190 window: &mut Window,
13191 cx: &mut Context<Self>,
13192 ) {
13193 if self.take_rename(true, window, cx).is_some() {
13194 return;
13195 }
13196
13197 if self
13198 .context_menu
13199 .borrow_mut()
13200 .as_mut()
13201 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13202 .unwrap_or(false)
13203 {
13204 return;
13205 }
13206
13207 if matches!(self.mode, EditorMode::SingleLine) {
13208 cx.propagate();
13209 return;
13210 }
13211
13212 let Some(row_count) = self.visible_row_count() else {
13213 return;
13214 };
13215
13216 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13217
13218 let effects = if action.center_cursor {
13219 SelectionEffects::scroll(Autoscroll::center())
13220 } else {
13221 SelectionEffects::default()
13222 };
13223
13224 let text_layout_details = &self.text_layout_details(window);
13225 self.change_selections(effects, window, cx, |s| {
13226 s.move_with(|map, selection| {
13227 if !selection.is_empty() {
13228 selection.goal = SelectionGoal::None;
13229 }
13230 let (cursor, goal) = movement::down_by_rows(
13231 map,
13232 selection.end,
13233 row_count,
13234 selection.goal,
13235 false,
13236 text_layout_details,
13237 );
13238 selection.collapse_to(cursor, goal);
13239 });
13240 });
13241 }
13242
13243 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13244 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13245 let text_layout_details = &self.text_layout_details(window);
13246 self.change_selections(Default::default(), window, cx, |s| {
13247 s.move_heads_with(|map, head, goal| {
13248 movement::down(map, head, goal, false, text_layout_details)
13249 })
13250 });
13251 }
13252
13253 pub fn context_menu_first(
13254 &mut self,
13255 _: &ContextMenuFirst,
13256 window: &mut Window,
13257 cx: &mut Context<Self>,
13258 ) {
13259 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13260 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13261 }
13262 }
13263
13264 pub fn context_menu_prev(
13265 &mut self,
13266 _: &ContextMenuPrevious,
13267 window: &mut Window,
13268 cx: &mut Context<Self>,
13269 ) {
13270 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13271 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13272 }
13273 }
13274
13275 pub fn context_menu_next(
13276 &mut self,
13277 _: &ContextMenuNext,
13278 window: &mut Window,
13279 cx: &mut Context<Self>,
13280 ) {
13281 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13282 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13283 }
13284 }
13285
13286 pub fn context_menu_last(
13287 &mut self,
13288 _: &ContextMenuLast,
13289 window: &mut Window,
13290 cx: &mut Context<Self>,
13291 ) {
13292 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13293 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13294 }
13295 }
13296
13297 pub fn signature_help_prev(
13298 &mut self,
13299 _: &SignatureHelpPrevious,
13300 _: &mut Window,
13301 cx: &mut Context<Self>,
13302 ) {
13303 if let Some(popover) = self.signature_help_state.popover_mut() {
13304 if popover.current_signature == 0 {
13305 popover.current_signature = popover.signatures.len() - 1;
13306 } else {
13307 popover.current_signature -= 1;
13308 }
13309 cx.notify();
13310 }
13311 }
13312
13313 pub fn signature_help_next(
13314 &mut self,
13315 _: &SignatureHelpNext,
13316 _: &mut Window,
13317 cx: &mut Context<Self>,
13318 ) {
13319 if let Some(popover) = self.signature_help_state.popover_mut() {
13320 if popover.current_signature + 1 == popover.signatures.len() {
13321 popover.current_signature = 0;
13322 } else {
13323 popover.current_signature += 1;
13324 }
13325 cx.notify();
13326 }
13327 }
13328
13329 pub fn move_to_previous_word_start(
13330 &mut self,
13331 _: &MoveToPreviousWordStart,
13332 window: &mut Window,
13333 cx: &mut Context<Self>,
13334 ) {
13335 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13336 self.change_selections(Default::default(), window, cx, |s| {
13337 s.move_cursors_with(|map, head, _| {
13338 (
13339 movement::previous_word_start(map, head),
13340 SelectionGoal::None,
13341 )
13342 });
13343 })
13344 }
13345
13346 pub fn move_to_previous_subword_start(
13347 &mut self,
13348 _: &MoveToPreviousSubwordStart,
13349 window: &mut Window,
13350 cx: &mut Context<Self>,
13351 ) {
13352 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13353 self.change_selections(Default::default(), window, cx, |s| {
13354 s.move_cursors_with(|map, head, _| {
13355 (
13356 movement::previous_subword_start(map, head),
13357 SelectionGoal::None,
13358 )
13359 });
13360 })
13361 }
13362
13363 pub fn select_to_previous_word_start(
13364 &mut self,
13365 _: &SelectToPreviousWordStart,
13366 window: &mut Window,
13367 cx: &mut Context<Self>,
13368 ) {
13369 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13370 self.change_selections(Default::default(), window, cx, |s| {
13371 s.move_heads_with(|map, head, _| {
13372 (
13373 movement::previous_word_start(map, head),
13374 SelectionGoal::None,
13375 )
13376 });
13377 })
13378 }
13379
13380 pub fn select_to_previous_subword_start(
13381 &mut self,
13382 _: &SelectToPreviousSubwordStart,
13383 window: &mut Window,
13384 cx: &mut Context<Self>,
13385 ) {
13386 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13387 self.change_selections(Default::default(), window, cx, |s| {
13388 s.move_heads_with(|map, head, _| {
13389 (
13390 movement::previous_subword_start(map, head),
13391 SelectionGoal::None,
13392 )
13393 });
13394 })
13395 }
13396
13397 pub fn delete_to_previous_word_start(
13398 &mut self,
13399 action: &DeleteToPreviousWordStart,
13400 window: &mut Window,
13401 cx: &mut Context<Self>,
13402 ) {
13403 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13404 self.transact(window, cx, |this, window, cx| {
13405 this.select_autoclose_pair(window, cx);
13406 this.change_selections(Default::default(), window, cx, |s| {
13407 s.move_with(|map, selection| {
13408 if selection.is_empty() {
13409 let mut cursor = if action.ignore_newlines {
13410 movement::previous_word_start(map, selection.head())
13411 } else {
13412 movement::previous_word_start_or_newline(map, selection.head())
13413 };
13414 cursor = movement::adjust_greedy_deletion(
13415 map,
13416 selection.head(),
13417 cursor,
13418 action.ignore_brackets,
13419 );
13420 selection.set_head(cursor, SelectionGoal::None);
13421 }
13422 });
13423 });
13424 this.insert("", window, cx);
13425 });
13426 }
13427
13428 pub fn delete_to_previous_subword_start(
13429 &mut self,
13430 _: &DeleteToPreviousSubwordStart,
13431 window: &mut Window,
13432 cx: &mut Context<Self>,
13433 ) {
13434 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13435 self.transact(window, cx, |this, window, cx| {
13436 this.select_autoclose_pair(window, cx);
13437 this.change_selections(Default::default(), window, cx, |s| {
13438 s.move_with(|map, selection| {
13439 if selection.is_empty() {
13440 let mut cursor = movement::previous_subword_start(map, selection.head());
13441 cursor =
13442 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13443 selection.set_head(cursor, SelectionGoal::None);
13444 }
13445 });
13446 });
13447 this.insert("", window, cx);
13448 });
13449 }
13450
13451 pub fn move_to_next_word_end(
13452 &mut self,
13453 _: &MoveToNextWordEnd,
13454 window: &mut Window,
13455 cx: &mut Context<Self>,
13456 ) {
13457 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13458 self.change_selections(Default::default(), window, cx, |s| {
13459 s.move_cursors_with(|map, head, _| {
13460 (movement::next_word_end(map, head), SelectionGoal::None)
13461 });
13462 })
13463 }
13464
13465 pub fn move_to_next_subword_end(
13466 &mut self,
13467 _: &MoveToNextSubwordEnd,
13468 window: &mut Window,
13469 cx: &mut Context<Self>,
13470 ) {
13471 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13472 self.change_selections(Default::default(), window, cx, |s| {
13473 s.move_cursors_with(|map, head, _| {
13474 (movement::next_subword_end(map, head), SelectionGoal::None)
13475 });
13476 })
13477 }
13478
13479 pub fn select_to_next_word_end(
13480 &mut self,
13481 _: &SelectToNextWordEnd,
13482 window: &mut Window,
13483 cx: &mut Context<Self>,
13484 ) {
13485 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13486 self.change_selections(Default::default(), window, cx, |s| {
13487 s.move_heads_with(|map, head, _| {
13488 (movement::next_word_end(map, head), SelectionGoal::None)
13489 });
13490 })
13491 }
13492
13493 pub fn select_to_next_subword_end(
13494 &mut self,
13495 _: &SelectToNextSubwordEnd,
13496 window: &mut Window,
13497 cx: &mut Context<Self>,
13498 ) {
13499 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13500 self.change_selections(Default::default(), window, cx, |s| {
13501 s.move_heads_with(|map, head, _| {
13502 (movement::next_subword_end(map, head), SelectionGoal::None)
13503 });
13504 })
13505 }
13506
13507 pub fn delete_to_next_word_end(
13508 &mut self,
13509 action: &DeleteToNextWordEnd,
13510 window: &mut Window,
13511 cx: &mut Context<Self>,
13512 ) {
13513 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13514 self.transact(window, cx, |this, window, cx| {
13515 this.change_selections(Default::default(), window, cx, |s| {
13516 s.move_with(|map, selection| {
13517 if selection.is_empty() {
13518 let mut cursor = if action.ignore_newlines {
13519 movement::next_word_end(map, selection.head())
13520 } else {
13521 movement::next_word_end_or_newline(map, selection.head())
13522 };
13523 cursor = movement::adjust_greedy_deletion(
13524 map,
13525 selection.head(),
13526 cursor,
13527 action.ignore_brackets,
13528 );
13529 selection.set_head(cursor, SelectionGoal::None);
13530 }
13531 });
13532 });
13533 this.insert("", window, cx);
13534 });
13535 }
13536
13537 pub fn delete_to_next_subword_end(
13538 &mut self,
13539 _: &DeleteToNextSubwordEnd,
13540 window: &mut Window,
13541 cx: &mut Context<Self>,
13542 ) {
13543 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13544 self.transact(window, cx, |this, window, cx| {
13545 this.change_selections(Default::default(), window, cx, |s| {
13546 s.move_with(|map, selection| {
13547 if selection.is_empty() {
13548 let mut cursor = movement::next_subword_end(map, selection.head());
13549 cursor =
13550 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13551 selection.set_head(cursor, SelectionGoal::None);
13552 }
13553 });
13554 });
13555 this.insert("", window, cx);
13556 });
13557 }
13558
13559 pub fn move_to_beginning_of_line(
13560 &mut self,
13561 action: &MoveToBeginningOfLine,
13562 window: &mut Window,
13563 cx: &mut Context<Self>,
13564 ) {
13565 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13566 self.change_selections(Default::default(), window, cx, |s| {
13567 s.move_cursors_with(|map, head, _| {
13568 (
13569 movement::indented_line_beginning(
13570 map,
13571 head,
13572 action.stop_at_soft_wraps,
13573 action.stop_at_indent,
13574 ),
13575 SelectionGoal::None,
13576 )
13577 });
13578 })
13579 }
13580
13581 pub fn select_to_beginning_of_line(
13582 &mut self,
13583 action: &SelectToBeginningOfLine,
13584 window: &mut Window,
13585 cx: &mut Context<Self>,
13586 ) {
13587 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13588 self.change_selections(Default::default(), window, cx, |s| {
13589 s.move_heads_with(|map, head, _| {
13590 (
13591 movement::indented_line_beginning(
13592 map,
13593 head,
13594 action.stop_at_soft_wraps,
13595 action.stop_at_indent,
13596 ),
13597 SelectionGoal::None,
13598 )
13599 });
13600 });
13601 }
13602
13603 pub fn delete_to_beginning_of_line(
13604 &mut self,
13605 action: &DeleteToBeginningOfLine,
13606 window: &mut Window,
13607 cx: &mut Context<Self>,
13608 ) {
13609 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13610 self.transact(window, cx, |this, window, cx| {
13611 this.change_selections(Default::default(), window, cx, |s| {
13612 s.move_with(|_, selection| {
13613 selection.reversed = true;
13614 });
13615 });
13616
13617 this.select_to_beginning_of_line(
13618 &SelectToBeginningOfLine {
13619 stop_at_soft_wraps: false,
13620 stop_at_indent: action.stop_at_indent,
13621 },
13622 window,
13623 cx,
13624 );
13625 this.backspace(&Backspace, window, cx);
13626 });
13627 }
13628
13629 pub fn move_to_end_of_line(
13630 &mut self,
13631 action: &MoveToEndOfLine,
13632 window: &mut Window,
13633 cx: &mut Context<Self>,
13634 ) {
13635 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13636 self.change_selections(Default::default(), window, cx, |s| {
13637 s.move_cursors_with(|map, head, _| {
13638 (
13639 movement::line_end(map, head, action.stop_at_soft_wraps),
13640 SelectionGoal::None,
13641 )
13642 });
13643 })
13644 }
13645
13646 pub fn select_to_end_of_line(
13647 &mut self,
13648 action: &SelectToEndOfLine,
13649 window: &mut Window,
13650 cx: &mut Context<Self>,
13651 ) {
13652 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13653 self.change_selections(Default::default(), window, cx, |s| {
13654 s.move_heads_with(|map, head, _| {
13655 (
13656 movement::line_end(map, head, action.stop_at_soft_wraps),
13657 SelectionGoal::None,
13658 )
13659 });
13660 })
13661 }
13662
13663 pub fn delete_to_end_of_line(
13664 &mut self,
13665 _: &DeleteToEndOfLine,
13666 window: &mut Window,
13667 cx: &mut Context<Self>,
13668 ) {
13669 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13670 self.transact(window, cx, |this, window, cx| {
13671 this.select_to_end_of_line(
13672 &SelectToEndOfLine {
13673 stop_at_soft_wraps: false,
13674 },
13675 window,
13676 cx,
13677 );
13678 this.delete(&Delete, window, cx);
13679 });
13680 }
13681
13682 pub fn cut_to_end_of_line(
13683 &mut self,
13684 action: &CutToEndOfLine,
13685 window: &mut Window,
13686 cx: &mut Context<Self>,
13687 ) {
13688 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13689 self.transact(window, cx, |this, window, cx| {
13690 this.select_to_end_of_line(
13691 &SelectToEndOfLine {
13692 stop_at_soft_wraps: false,
13693 },
13694 window,
13695 cx,
13696 );
13697 if !action.stop_at_newlines {
13698 this.change_selections(Default::default(), window, cx, |s| {
13699 s.move_with(|_, sel| {
13700 if sel.is_empty() {
13701 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13702 }
13703 });
13704 });
13705 }
13706 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13707 let item = this.cut_common(false, window, cx);
13708 cx.write_to_clipboard(item);
13709 });
13710 }
13711
13712 pub fn move_to_start_of_paragraph(
13713 &mut self,
13714 _: &MoveToStartOfParagraph,
13715 window: &mut Window,
13716 cx: &mut Context<Self>,
13717 ) {
13718 if matches!(self.mode, EditorMode::SingleLine) {
13719 cx.propagate();
13720 return;
13721 }
13722 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13723 self.change_selections(Default::default(), window, cx, |s| {
13724 s.move_with(|map, selection| {
13725 selection.collapse_to(
13726 movement::start_of_paragraph(map, selection.head(), 1),
13727 SelectionGoal::None,
13728 )
13729 });
13730 })
13731 }
13732
13733 pub fn move_to_end_of_paragraph(
13734 &mut self,
13735 _: &MoveToEndOfParagraph,
13736 window: &mut Window,
13737 cx: &mut Context<Self>,
13738 ) {
13739 if matches!(self.mode, EditorMode::SingleLine) {
13740 cx.propagate();
13741 return;
13742 }
13743 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13744 self.change_selections(Default::default(), window, cx, |s| {
13745 s.move_with(|map, selection| {
13746 selection.collapse_to(
13747 movement::end_of_paragraph(map, selection.head(), 1),
13748 SelectionGoal::None,
13749 )
13750 });
13751 })
13752 }
13753
13754 pub fn select_to_start_of_paragraph(
13755 &mut self,
13756 _: &SelectToStartOfParagraph,
13757 window: &mut Window,
13758 cx: &mut Context<Self>,
13759 ) {
13760 if matches!(self.mode, EditorMode::SingleLine) {
13761 cx.propagate();
13762 return;
13763 }
13764 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13765 self.change_selections(Default::default(), window, cx, |s| {
13766 s.move_heads_with(|map, head, _| {
13767 (
13768 movement::start_of_paragraph(map, head, 1),
13769 SelectionGoal::None,
13770 )
13771 });
13772 })
13773 }
13774
13775 pub fn select_to_end_of_paragraph(
13776 &mut self,
13777 _: &SelectToEndOfParagraph,
13778 window: &mut Window,
13779 cx: &mut Context<Self>,
13780 ) {
13781 if matches!(self.mode, EditorMode::SingleLine) {
13782 cx.propagate();
13783 return;
13784 }
13785 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13786 self.change_selections(Default::default(), window, cx, |s| {
13787 s.move_heads_with(|map, head, _| {
13788 (
13789 movement::end_of_paragraph(map, head, 1),
13790 SelectionGoal::None,
13791 )
13792 });
13793 })
13794 }
13795
13796 pub fn move_to_start_of_excerpt(
13797 &mut self,
13798 _: &MoveToStartOfExcerpt,
13799 window: &mut Window,
13800 cx: &mut Context<Self>,
13801 ) {
13802 if matches!(self.mode, EditorMode::SingleLine) {
13803 cx.propagate();
13804 return;
13805 }
13806 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13807 self.change_selections(Default::default(), window, cx, |s| {
13808 s.move_with(|map, selection| {
13809 selection.collapse_to(
13810 movement::start_of_excerpt(
13811 map,
13812 selection.head(),
13813 workspace::searchable::Direction::Prev,
13814 ),
13815 SelectionGoal::None,
13816 )
13817 });
13818 })
13819 }
13820
13821 pub fn move_to_start_of_next_excerpt(
13822 &mut self,
13823 _: &MoveToStartOfNextExcerpt,
13824 window: &mut Window,
13825 cx: &mut Context<Self>,
13826 ) {
13827 if matches!(self.mode, EditorMode::SingleLine) {
13828 cx.propagate();
13829 return;
13830 }
13831
13832 self.change_selections(Default::default(), window, cx, |s| {
13833 s.move_with(|map, selection| {
13834 selection.collapse_to(
13835 movement::start_of_excerpt(
13836 map,
13837 selection.head(),
13838 workspace::searchable::Direction::Next,
13839 ),
13840 SelectionGoal::None,
13841 )
13842 });
13843 })
13844 }
13845
13846 pub fn move_to_end_of_excerpt(
13847 &mut self,
13848 _: &MoveToEndOfExcerpt,
13849 window: &mut Window,
13850 cx: &mut Context<Self>,
13851 ) {
13852 if matches!(self.mode, EditorMode::SingleLine) {
13853 cx.propagate();
13854 return;
13855 }
13856 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13857 self.change_selections(Default::default(), window, cx, |s| {
13858 s.move_with(|map, selection| {
13859 selection.collapse_to(
13860 movement::end_of_excerpt(
13861 map,
13862 selection.head(),
13863 workspace::searchable::Direction::Next,
13864 ),
13865 SelectionGoal::None,
13866 )
13867 });
13868 })
13869 }
13870
13871 pub fn move_to_end_of_previous_excerpt(
13872 &mut self,
13873 _: &MoveToEndOfPreviousExcerpt,
13874 window: &mut Window,
13875 cx: &mut Context<Self>,
13876 ) {
13877 if matches!(self.mode, EditorMode::SingleLine) {
13878 cx.propagate();
13879 return;
13880 }
13881 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13882 self.change_selections(Default::default(), window, cx, |s| {
13883 s.move_with(|map, selection| {
13884 selection.collapse_to(
13885 movement::end_of_excerpt(
13886 map,
13887 selection.head(),
13888 workspace::searchable::Direction::Prev,
13889 ),
13890 SelectionGoal::None,
13891 )
13892 });
13893 })
13894 }
13895
13896 pub fn select_to_start_of_excerpt(
13897 &mut self,
13898 _: &SelectToStartOfExcerpt,
13899 window: &mut Window,
13900 cx: &mut Context<Self>,
13901 ) {
13902 if matches!(self.mode, EditorMode::SingleLine) {
13903 cx.propagate();
13904 return;
13905 }
13906 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13907 self.change_selections(Default::default(), window, cx, |s| {
13908 s.move_heads_with(|map, head, _| {
13909 (
13910 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13911 SelectionGoal::None,
13912 )
13913 });
13914 })
13915 }
13916
13917 pub fn select_to_start_of_next_excerpt(
13918 &mut self,
13919 _: &SelectToStartOfNextExcerpt,
13920 window: &mut Window,
13921 cx: &mut Context<Self>,
13922 ) {
13923 if matches!(self.mode, EditorMode::SingleLine) {
13924 cx.propagate();
13925 return;
13926 }
13927 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13928 self.change_selections(Default::default(), window, cx, |s| {
13929 s.move_heads_with(|map, head, _| {
13930 (
13931 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13932 SelectionGoal::None,
13933 )
13934 });
13935 })
13936 }
13937
13938 pub fn select_to_end_of_excerpt(
13939 &mut self,
13940 _: &SelectToEndOfExcerpt,
13941 window: &mut Window,
13942 cx: &mut Context<Self>,
13943 ) {
13944 if matches!(self.mode, EditorMode::SingleLine) {
13945 cx.propagate();
13946 return;
13947 }
13948 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13949 self.change_selections(Default::default(), window, cx, |s| {
13950 s.move_heads_with(|map, head, _| {
13951 (
13952 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13953 SelectionGoal::None,
13954 )
13955 });
13956 })
13957 }
13958
13959 pub fn select_to_end_of_previous_excerpt(
13960 &mut self,
13961 _: &SelectToEndOfPreviousExcerpt,
13962 window: &mut Window,
13963 cx: &mut Context<Self>,
13964 ) {
13965 if matches!(self.mode, EditorMode::SingleLine) {
13966 cx.propagate();
13967 return;
13968 }
13969 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13970 self.change_selections(Default::default(), window, cx, |s| {
13971 s.move_heads_with(|map, head, _| {
13972 (
13973 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13974 SelectionGoal::None,
13975 )
13976 });
13977 })
13978 }
13979
13980 pub fn move_to_beginning(
13981 &mut self,
13982 _: &MoveToBeginning,
13983 window: &mut Window,
13984 cx: &mut Context<Self>,
13985 ) {
13986 if matches!(self.mode, EditorMode::SingleLine) {
13987 cx.propagate();
13988 return;
13989 }
13990 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13991 self.change_selections(Default::default(), window, cx, |s| {
13992 s.select_ranges(vec![0..0]);
13993 });
13994 }
13995
13996 pub fn select_to_beginning(
13997 &mut self,
13998 _: &SelectToBeginning,
13999 window: &mut Window,
14000 cx: &mut Context<Self>,
14001 ) {
14002 let mut selection = self.selections.last::<Point>(cx);
14003 selection.set_head(Point::zero(), SelectionGoal::None);
14004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14005 self.change_selections(Default::default(), window, cx, |s| {
14006 s.select(vec![selection]);
14007 });
14008 }
14009
14010 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14011 if matches!(self.mode, EditorMode::SingleLine) {
14012 cx.propagate();
14013 return;
14014 }
14015 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14016 let cursor = self.buffer.read(cx).read(cx).len();
14017 self.change_selections(Default::default(), window, cx, |s| {
14018 s.select_ranges(vec![cursor..cursor])
14019 });
14020 }
14021
14022 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14023 self.nav_history = nav_history;
14024 }
14025
14026 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14027 self.nav_history.as_ref()
14028 }
14029
14030 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14031 self.push_to_nav_history(
14032 self.selections.newest_anchor().head(),
14033 None,
14034 false,
14035 true,
14036 cx,
14037 );
14038 }
14039
14040 fn push_to_nav_history(
14041 &mut self,
14042 cursor_anchor: Anchor,
14043 new_position: Option<Point>,
14044 is_deactivate: bool,
14045 always: bool,
14046 cx: &mut Context<Self>,
14047 ) {
14048 if let Some(nav_history) = self.nav_history.as_mut() {
14049 let buffer = self.buffer.read(cx).read(cx);
14050 let cursor_position = cursor_anchor.to_point(&buffer);
14051 let scroll_state = self.scroll_manager.anchor();
14052 let scroll_top_row = scroll_state.top_row(&buffer);
14053 drop(buffer);
14054
14055 if let Some(new_position) = new_position {
14056 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14057 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14058 return;
14059 }
14060 }
14061
14062 nav_history.push(
14063 Some(NavigationData {
14064 cursor_anchor,
14065 cursor_position,
14066 scroll_anchor: scroll_state,
14067 scroll_top_row,
14068 }),
14069 cx,
14070 );
14071 cx.emit(EditorEvent::PushedToNavHistory {
14072 anchor: cursor_anchor,
14073 is_deactivate,
14074 })
14075 }
14076 }
14077
14078 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14079 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14080 let buffer = self.buffer.read(cx).snapshot(cx);
14081 let mut selection = self.selections.first::<usize>(cx);
14082 selection.set_head(buffer.len(), SelectionGoal::None);
14083 self.change_selections(Default::default(), window, cx, |s| {
14084 s.select(vec![selection]);
14085 });
14086 }
14087
14088 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14089 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14090 let end = self.buffer.read(cx).read(cx).len();
14091 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14092 s.select_ranges(vec![0..end]);
14093 });
14094 }
14095
14096 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14097 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14098 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14099 let mut selections = self.selections.all::<Point>(cx);
14100 let max_point = display_map.buffer_snapshot().max_point();
14101 for selection in &mut selections {
14102 let rows = selection.spanned_rows(true, &display_map);
14103 selection.start = Point::new(rows.start.0, 0);
14104 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14105 selection.reversed = false;
14106 }
14107 self.change_selections(Default::default(), window, cx, |s| {
14108 s.select(selections);
14109 });
14110 }
14111
14112 pub fn split_selection_into_lines(
14113 &mut self,
14114 action: &SplitSelectionIntoLines,
14115 window: &mut Window,
14116 cx: &mut Context<Self>,
14117 ) {
14118 let selections = self
14119 .selections
14120 .all::<Point>(cx)
14121 .into_iter()
14122 .map(|selection| selection.start..selection.end)
14123 .collect::<Vec<_>>();
14124 self.unfold_ranges(&selections, true, true, cx);
14125
14126 let mut new_selection_ranges = Vec::new();
14127 {
14128 let buffer = self.buffer.read(cx).read(cx);
14129 for selection in selections {
14130 for row in selection.start.row..selection.end.row {
14131 let line_start = Point::new(row, 0);
14132 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14133
14134 if action.keep_selections {
14135 // Keep the selection range for each line
14136 let selection_start = if row == selection.start.row {
14137 selection.start
14138 } else {
14139 line_start
14140 };
14141 new_selection_ranges.push(selection_start..line_end);
14142 } else {
14143 // Collapse to cursor at end of line
14144 new_selection_ranges.push(line_end..line_end);
14145 }
14146 }
14147
14148 let is_multiline_selection = selection.start.row != selection.end.row;
14149 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14150 // so this action feels more ergonomic when paired with other selection operations
14151 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14152 if !should_skip_last {
14153 if action.keep_selections {
14154 if is_multiline_selection {
14155 let line_start = Point::new(selection.end.row, 0);
14156 new_selection_ranges.push(line_start..selection.end);
14157 } else {
14158 new_selection_ranges.push(selection.start..selection.end);
14159 }
14160 } else {
14161 new_selection_ranges.push(selection.end..selection.end);
14162 }
14163 }
14164 }
14165 }
14166 self.change_selections(Default::default(), window, cx, |s| {
14167 s.select_ranges(new_selection_ranges);
14168 });
14169 }
14170
14171 pub fn add_selection_above(
14172 &mut self,
14173 _: &AddSelectionAbove,
14174 window: &mut Window,
14175 cx: &mut Context<Self>,
14176 ) {
14177 self.add_selection(true, window, cx);
14178 }
14179
14180 pub fn add_selection_below(
14181 &mut self,
14182 _: &AddSelectionBelow,
14183 window: &mut Window,
14184 cx: &mut Context<Self>,
14185 ) {
14186 self.add_selection(false, window, cx);
14187 }
14188
14189 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14190 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14191
14192 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14193 let all_selections = self.selections.all::<Point>(cx);
14194 let text_layout_details = self.text_layout_details(window);
14195
14196 let (mut columnar_selections, new_selections_to_columnarize) = {
14197 if let Some(state) = self.add_selections_state.as_ref() {
14198 let columnar_selection_ids: HashSet<_> = state
14199 .groups
14200 .iter()
14201 .flat_map(|group| group.stack.iter())
14202 .copied()
14203 .collect();
14204
14205 all_selections
14206 .into_iter()
14207 .partition(|s| columnar_selection_ids.contains(&s.id))
14208 } else {
14209 (Vec::new(), all_selections)
14210 }
14211 };
14212
14213 let mut state = self
14214 .add_selections_state
14215 .take()
14216 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14217
14218 for selection in new_selections_to_columnarize {
14219 let range = selection.display_range(&display_map).sorted();
14220 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14221 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14222 let positions = start_x.min(end_x)..start_x.max(end_x);
14223 let mut stack = Vec::new();
14224 for row in range.start.row().0..=range.end.row().0 {
14225 if let Some(selection) = self.selections.build_columnar_selection(
14226 &display_map,
14227 DisplayRow(row),
14228 &positions,
14229 selection.reversed,
14230 &text_layout_details,
14231 ) {
14232 stack.push(selection.id);
14233 columnar_selections.push(selection);
14234 }
14235 }
14236 if !stack.is_empty() {
14237 if above {
14238 stack.reverse();
14239 }
14240 state.groups.push(AddSelectionsGroup { above, stack });
14241 }
14242 }
14243
14244 let mut final_selections = Vec::new();
14245 let end_row = if above {
14246 DisplayRow(0)
14247 } else {
14248 display_map.max_point().row()
14249 };
14250
14251 let mut last_added_item_per_group = HashMap::default();
14252 for group in state.groups.iter_mut() {
14253 if let Some(last_id) = group.stack.last() {
14254 last_added_item_per_group.insert(*last_id, group);
14255 }
14256 }
14257
14258 for selection in columnar_selections {
14259 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14260 if above == group.above {
14261 let range = selection.display_range(&display_map).sorted();
14262 debug_assert_eq!(range.start.row(), range.end.row());
14263 let mut row = range.start.row();
14264 let positions =
14265 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14266 Pixels::from(start)..Pixels::from(end)
14267 } else {
14268 let start_x =
14269 display_map.x_for_display_point(range.start, &text_layout_details);
14270 let end_x =
14271 display_map.x_for_display_point(range.end, &text_layout_details);
14272 start_x.min(end_x)..start_x.max(end_x)
14273 };
14274
14275 let mut maybe_new_selection = None;
14276 while row != end_row {
14277 if above {
14278 row.0 -= 1;
14279 } else {
14280 row.0 += 1;
14281 }
14282 if let Some(new_selection) = self.selections.build_columnar_selection(
14283 &display_map,
14284 row,
14285 &positions,
14286 selection.reversed,
14287 &text_layout_details,
14288 ) {
14289 maybe_new_selection = Some(new_selection);
14290 break;
14291 }
14292 }
14293
14294 if let Some(new_selection) = maybe_new_selection {
14295 group.stack.push(new_selection.id);
14296 if above {
14297 final_selections.push(new_selection);
14298 final_selections.push(selection);
14299 } else {
14300 final_selections.push(selection);
14301 final_selections.push(new_selection);
14302 }
14303 } else {
14304 final_selections.push(selection);
14305 }
14306 } else {
14307 group.stack.pop();
14308 }
14309 } else {
14310 final_selections.push(selection);
14311 }
14312 }
14313
14314 self.change_selections(Default::default(), window, cx, |s| {
14315 s.select(final_selections);
14316 });
14317
14318 let final_selection_ids: HashSet<_> = self
14319 .selections
14320 .all::<Point>(cx)
14321 .iter()
14322 .map(|s| s.id)
14323 .collect();
14324 state.groups.retain_mut(|group| {
14325 // selections might get merged above so we remove invalid items from stacks
14326 group.stack.retain(|id| final_selection_ids.contains(id));
14327
14328 // single selection in stack can be treated as initial state
14329 group.stack.len() > 1
14330 });
14331
14332 if !state.groups.is_empty() {
14333 self.add_selections_state = Some(state);
14334 }
14335 }
14336
14337 fn select_match_ranges(
14338 &mut self,
14339 range: Range<usize>,
14340 reversed: bool,
14341 replace_newest: bool,
14342 auto_scroll: Option<Autoscroll>,
14343 window: &mut Window,
14344 cx: &mut Context<Editor>,
14345 ) {
14346 self.unfold_ranges(
14347 std::slice::from_ref(&range),
14348 false,
14349 auto_scroll.is_some(),
14350 cx,
14351 );
14352 let effects = if let Some(scroll) = auto_scroll {
14353 SelectionEffects::scroll(scroll)
14354 } else {
14355 SelectionEffects::no_scroll()
14356 };
14357 self.change_selections(effects, window, cx, |s| {
14358 if replace_newest {
14359 s.delete(s.newest_anchor().id);
14360 }
14361 if reversed {
14362 s.insert_range(range.end..range.start);
14363 } else {
14364 s.insert_range(range);
14365 }
14366 });
14367 }
14368
14369 pub fn select_next_match_internal(
14370 &mut self,
14371 display_map: &DisplaySnapshot,
14372 replace_newest: bool,
14373 autoscroll: Option<Autoscroll>,
14374 window: &mut Window,
14375 cx: &mut Context<Self>,
14376 ) -> Result<()> {
14377 let buffer = display_map.buffer_snapshot();
14378 let mut selections = self.selections.all::<usize>(cx);
14379 if let Some(mut select_next_state) = self.select_next_state.take() {
14380 let query = &select_next_state.query;
14381 if !select_next_state.done {
14382 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14383 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14384 let mut next_selected_range = None;
14385
14386 let bytes_after_last_selection =
14387 buffer.bytes_in_range(last_selection.end..buffer.len());
14388 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14389 let query_matches = query
14390 .stream_find_iter(bytes_after_last_selection)
14391 .map(|result| (last_selection.end, result))
14392 .chain(
14393 query
14394 .stream_find_iter(bytes_before_first_selection)
14395 .map(|result| (0, result)),
14396 );
14397
14398 for (start_offset, query_match) in query_matches {
14399 let query_match = query_match.unwrap(); // can only fail due to I/O
14400 let offset_range =
14401 start_offset + query_match.start()..start_offset + query_match.end();
14402
14403 if !select_next_state.wordwise
14404 || (!buffer.is_inside_word(offset_range.start, None)
14405 && !buffer.is_inside_word(offset_range.end, None))
14406 {
14407 // TODO: This is n^2, because we might check all the selections
14408 if !selections
14409 .iter()
14410 .any(|selection| selection.range().overlaps(&offset_range))
14411 {
14412 next_selected_range = Some(offset_range);
14413 break;
14414 }
14415 }
14416 }
14417
14418 if let Some(next_selected_range) = next_selected_range {
14419 self.select_match_ranges(
14420 next_selected_range,
14421 last_selection.reversed,
14422 replace_newest,
14423 autoscroll,
14424 window,
14425 cx,
14426 );
14427 } else {
14428 select_next_state.done = true;
14429 }
14430 }
14431
14432 self.select_next_state = Some(select_next_state);
14433 } else {
14434 let mut only_carets = true;
14435 let mut same_text_selected = true;
14436 let mut selected_text = None;
14437
14438 let mut selections_iter = selections.iter().peekable();
14439 while let Some(selection) = selections_iter.next() {
14440 if selection.start != selection.end {
14441 only_carets = false;
14442 }
14443
14444 if same_text_selected {
14445 if selected_text.is_none() {
14446 selected_text =
14447 Some(buffer.text_for_range(selection.range()).collect::<String>());
14448 }
14449
14450 if let Some(next_selection) = selections_iter.peek() {
14451 if next_selection.range().len() == selection.range().len() {
14452 let next_selected_text = buffer
14453 .text_for_range(next_selection.range())
14454 .collect::<String>();
14455 if Some(next_selected_text) != selected_text {
14456 same_text_selected = false;
14457 selected_text = None;
14458 }
14459 } else {
14460 same_text_selected = false;
14461 selected_text = None;
14462 }
14463 }
14464 }
14465 }
14466
14467 if only_carets {
14468 for selection in &mut selections {
14469 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14470 selection.start = word_range.start;
14471 selection.end = word_range.end;
14472 selection.goal = SelectionGoal::None;
14473 selection.reversed = false;
14474 self.select_match_ranges(
14475 selection.start..selection.end,
14476 selection.reversed,
14477 replace_newest,
14478 autoscroll,
14479 window,
14480 cx,
14481 );
14482 }
14483
14484 if selections.len() == 1 {
14485 let selection = selections
14486 .last()
14487 .expect("ensured that there's only one selection");
14488 let query = buffer
14489 .text_for_range(selection.start..selection.end)
14490 .collect::<String>();
14491 let is_empty = query.is_empty();
14492 let select_state = SelectNextState {
14493 query: AhoCorasick::new(&[query])?,
14494 wordwise: true,
14495 done: is_empty,
14496 };
14497 self.select_next_state = Some(select_state);
14498 } else {
14499 self.select_next_state = None;
14500 }
14501 } else if let Some(selected_text) = selected_text {
14502 self.select_next_state = Some(SelectNextState {
14503 query: AhoCorasick::new(&[selected_text])?,
14504 wordwise: false,
14505 done: false,
14506 });
14507 self.select_next_match_internal(
14508 display_map,
14509 replace_newest,
14510 autoscroll,
14511 window,
14512 cx,
14513 )?;
14514 }
14515 }
14516 Ok(())
14517 }
14518
14519 pub fn select_all_matches(
14520 &mut self,
14521 _action: &SelectAllMatches,
14522 window: &mut Window,
14523 cx: &mut Context<Self>,
14524 ) -> Result<()> {
14525 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14526
14527 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14528
14529 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14530 let Some(select_next_state) = self.select_next_state.as_mut() else {
14531 return Ok(());
14532 };
14533 if select_next_state.done {
14534 return Ok(());
14535 }
14536
14537 let mut new_selections = Vec::new();
14538
14539 let reversed = self.selections.oldest::<usize>(cx).reversed;
14540 let buffer = display_map.buffer_snapshot();
14541 let query_matches = select_next_state
14542 .query
14543 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14544
14545 for query_match in query_matches.into_iter() {
14546 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14547 let offset_range = if reversed {
14548 query_match.end()..query_match.start()
14549 } else {
14550 query_match.start()..query_match.end()
14551 };
14552
14553 if !select_next_state.wordwise
14554 || (!buffer.is_inside_word(offset_range.start, None)
14555 && !buffer.is_inside_word(offset_range.end, None))
14556 {
14557 new_selections.push(offset_range.start..offset_range.end);
14558 }
14559 }
14560
14561 select_next_state.done = true;
14562
14563 if new_selections.is_empty() {
14564 log::error!("bug: new_selections is empty in select_all_matches");
14565 return Ok(());
14566 }
14567
14568 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14569 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14570 selections.select_ranges(new_selections)
14571 });
14572
14573 Ok(())
14574 }
14575
14576 pub fn select_next(
14577 &mut self,
14578 action: &SelectNext,
14579 window: &mut Window,
14580 cx: &mut Context<Self>,
14581 ) -> Result<()> {
14582 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14583 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14584 self.select_next_match_internal(
14585 &display_map,
14586 action.replace_newest,
14587 Some(Autoscroll::newest()),
14588 window,
14589 cx,
14590 )?;
14591 Ok(())
14592 }
14593
14594 pub fn select_previous(
14595 &mut self,
14596 action: &SelectPrevious,
14597 window: &mut Window,
14598 cx: &mut Context<Self>,
14599 ) -> Result<()> {
14600 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14601 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14602 let buffer = display_map.buffer_snapshot();
14603 let mut selections = self.selections.all::<usize>(cx);
14604 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14605 let query = &select_prev_state.query;
14606 if !select_prev_state.done {
14607 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14608 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14609 let mut next_selected_range = None;
14610 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14611 let bytes_before_last_selection =
14612 buffer.reversed_bytes_in_range(0..last_selection.start);
14613 let bytes_after_first_selection =
14614 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14615 let query_matches = query
14616 .stream_find_iter(bytes_before_last_selection)
14617 .map(|result| (last_selection.start, result))
14618 .chain(
14619 query
14620 .stream_find_iter(bytes_after_first_selection)
14621 .map(|result| (buffer.len(), result)),
14622 );
14623 for (end_offset, query_match) in query_matches {
14624 let query_match = query_match.unwrap(); // can only fail due to I/O
14625 let offset_range =
14626 end_offset - query_match.end()..end_offset - query_match.start();
14627
14628 if !select_prev_state.wordwise
14629 || (!buffer.is_inside_word(offset_range.start, None)
14630 && !buffer.is_inside_word(offset_range.end, None))
14631 {
14632 next_selected_range = Some(offset_range);
14633 break;
14634 }
14635 }
14636
14637 if let Some(next_selected_range) = next_selected_range {
14638 self.select_match_ranges(
14639 next_selected_range,
14640 last_selection.reversed,
14641 action.replace_newest,
14642 Some(Autoscroll::newest()),
14643 window,
14644 cx,
14645 );
14646 } else {
14647 select_prev_state.done = true;
14648 }
14649 }
14650
14651 self.select_prev_state = Some(select_prev_state);
14652 } else {
14653 let mut only_carets = true;
14654 let mut same_text_selected = true;
14655 let mut selected_text = None;
14656
14657 let mut selections_iter = selections.iter().peekable();
14658 while let Some(selection) = selections_iter.next() {
14659 if selection.start != selection.end {
14660 only_carets = false;
14661 }
14662
14663 if same_text_selected {
14664 if selected_text.is_none() {
14665 selected_text =
14666 Some(buffer.text_for_range(selection.range()).collect::<String>());
14667 }
14668
14669 if let Some(next_selection) = selections_iter.peek() {
14670 if next_selection.range().len() == selection.range().len() {
14671 let next_selected_text = buffer
14672 .text_for_range(next_selection.range())
14673 .collect::<String>();
14674 if Some(next_selected_text) != selected_text {
14675 same_text_selected = false;
14676 selected_text = None;
14677 }
14678 } else {
14679 same_text_selected = false;
14680 selected_text = None;
14681 }
14682 }
14683 }
14684 }
14685
14686 if only_carets {
14687 for selection in &mut selections {
14688 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14689 selection.start = word_range.start;
14690 selection.end = word_range.end;
14691 selection.goal = SelectionGoal::None;
14692 selection.reversed = false;
14693 self.select_match_ranges(
14694 selection.start..selection.end,
14695 selection.reversed,
14696 action.replace_newest,
14697 Some(Autoscroll::newest()),
14698 window,
14699 cx,
14700 );
14701 }
14702 if selections.len() == 1 {
14703 let selection = selections
14704 .last()
14705 .expect("ensured that there's only one selection");
14706 let query = buffer
14707 .text_for_range(selection.start..selection.end)
14708 .collect::<String>();
14709 let is_empty = query.is_empty();
14710 let select_state = SelectNextState {
14711 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14712 wordwise: true,
14713 done: is_empty,
14714 };
14715 self.select_prev_state = Some(select_state);
14716 } else {
14717 self.select_prev_state = None;
14718 }
14719 } else if let Some(selected_text) = selected_text {
14720 self.select_prev_state = Some(SelectNextState {
14721 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14722 wordwise: false,
14723 done: false,
14724 });
14725 self.select_previous(action, window, cx)?;
14726 }
14727 }
14728 Ok(())
14729 }
14730
14731 pub fn find_next_match(
14732 &mut self,
14733 _: &FindNextMatch,
14734 window: &mut Window,
14735 cx: &mut Context<Self>,
14736 ) -> Result<()> {
14737 let selections = self.selections.disjoint_anchors_arc();
14738 match selections.first() {
14739 Some(first) if selections.len() >= 2 => {
14740 self.change_selections(Default::default(), window, cx, |s| {
14741 s.select_ranges([first.range()]);
14742 });
14743 }
14744 _ => self.select_next(
14745 &SelectNext {
14746 replace_newest: true,
14747 },
14748 window,
14749 cx,
14750 )?,
14751 }
14752 Ok(())
14753 }
14754
14755 pub fn find_previous_match(
14756 &mut self,
14757 _: &FindPreviousMatch,
14758 window: &mut Window,
14759 cx: &mut Context<Self>,
14760 ) -> Result<()> {
14761 let selections = self.selections.disjoint_anchors_arc();
14762 match selections.last() {
14763 Some(last) if selections.len() >= 2 => {
14764 self.change_selections(Default::default(), window, cx, |s| {
14765 s.select_ranges([last.range()]);
14766 });
14767 }
14768 _ => self.select_previous(
14769 &SelectPrevious {
14770 replace_newest: true,
14771 },
14772 window,
14773 cx,
14774 )?,
14775 }
14776 Ok(())
14777 }
14778
14779 pub fn toggle_comments(
14780 &mut self,
14781 action: &ToggleComments,
14782 window: &mut Window,
14783 cx: &mut Context<Self>,
14784 ) {
14785 if self.read_only(cx) {
14786 return;
14787 }
14788 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14789 let text_layout_details = &self.text_layout_details(window);
14790 self.transact(window, cx, |this, window, cx| {
14791 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14792 let mut edits = Vec::new();
14793 let mut selection_edit_ranges = Vec::new();
14794 let mut last_toggled_row = None;
14795 let snapshot = this.buffer.read(cx).read(cx);
14796 let empty_str: Arc<str> = Arc::default();
14797 let mut suffixes_inserted = Vec::new();
14798 let ignore_indent = action.ignore_indent;
14799
14800 fn comment_prefix_range(
14801 snapshot: &MultiBufferSnapshot,
14802 row: MultiBufferRow,
14803 comment_prefix: &str,
14804 comment_prefix_whitespace: &str,
14805 ignore_indent: bool,
14806 ) -> Range<Point> {
14807 let indent_size = if ignore_indent {
14808 0
14809 } else {
14810 snapshot.indent_size_for_line(row).len
14811 };
14812
14813 let start = Point::new(row.0, indent_size);
14814
14815 let mut line_bytes = snapshot
14816 .bytes_in_range(start..snapshot.max_point())
14817 .flatten()
14818 .copied();
14819
14820 // If this line currently begins with the line comment prefix, then record
14821 // the range containing the prefix.
14822 if line_bytes
14823 .by_ref()
14824 .take(comment_prefix.len())
14825 .eq(comment_prefix.bytes())
14826 {
14827 // Include any whitespace that matches the comment prefix.
14828 let matching_whitespace_len = line_bytes
14829 .zip(comment_prefix_whitespace.bytes())
14830 .take_while(|(a, b)| a == b)
14831 .count() as u32;
14832 let end = Point::new(
14833 start.row,
14834 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14835 );
14836 start..end
14837 } else {
14838 start..start
14839 }
14840 }
14841
14842 fn comment_suffix_range(
14843 snapshot: &MultiBufferSnapshot,
14844 row: MultiBufferRow,
14845 comment_suffix: &str,
14846 comment_suffix_has_leading_space: bool,
14847 ) -> Range<Point> {
14848 let end = Point::new(row.0, snapshot.line_len(row));
14849 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14850
14851 let mut line_end_bytes = snapshot
14852 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14853 .flatten()
14854 .copied();
14855
14856 let leading_space_len = if suffix_start_column > 0
14857 && line_end_bytes.next() == Some(b' ')
14858 && comment_suffix_has_leading_space
14859 {
14860 1
14861 } else {
14862 0
14863 };
14864
14865 // If this line currently begins with the line comment prefix, then record
14866 // the range containing the prefix.
14867 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14868 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14869 start..end
14870 } else {
14871 end..end
14872 }
14873 }
14874
14875 // TODO: Handle selections that cross excerpts
14876 for selection in &mut selections {
14877 let start_column = snapshot
14878 .indent_size_for_line(MultiBufferRow(selection.start.row))
14879 .len;
14880 let language = if let Some(language) =
14881 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14882 {
14883 language
14884 } else {
14885 continue;
14886 };
14887
14888 selection_edit_ranges.clear();
14889
14890 // If multiple selections contain a given row, avoid processing that
14891 // row more than once.
14892 let mut start_row = MultiBufferRow(selection.start.row);
14893 if last_toggled_row == Some(start_row) {
14894 start_row = start_row.next_row();
14895 }
14896 let end_row =
14897 if selection.end.row > selection.start.row && selection.end.column == 0 {
14898 MultiBufferRow(selection.end.row - 1)
14899 } else {
14900 MultiBufferRow(selection.end.row)
14901 };
14902 last_toggled_row = Some(end_row);
14903
14904 if start_row > end_row {
14905 continue;
14906 }
14907
14908 // If the language has line comments, toggle those.
14909 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14910
14911 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14912 if ignore_indent {
14913 full_comment_prefixes = full_comment_prefixes
14914 .into_iter()
14915 .map(|s| Arc::from(s.trim_end()))
14916 .collect();
14917 }
14918
14919 if !full_comment_prefixes.is_empty() {
14920 let first_prefix = full_comment_prefixes
14921 .first()
14922 .expect("prefixes is non-empty");
14923 let prefix_trimmed_lengths = full_comment_prefixes
14924 .iter()
14925 .map(|p| p.trim_end_matches(' ').len())
14926 .collect::<SmallVec<[usize; 4]>>();
14927
14928 let mut all_selection_lines_are_comments = true;
14929
14930 for row in start_row.0..=end_row.0 {
14931 let row = MultiBufferRow(row);
14932 if start_row < end_row && snapshot.is_line_blank(row) {
14933 continue;
14934 }
14935
14936 let prefix_range = full_comment_prefixes
14937 .iter()
14938 .zip(prefix_trimmed_lengths.iter().copied())
14939 .map(|(prefix, trimmed_prefix_len)| {
14940 comment_prefix_range(
14941 snapshot.deref(),
14942 row,
14943 &prefix[..trimmed_prefix_len],
14944 &prefix[trimmed_prefix_len..],
14945 ignore_indent,
14946 )
14947 })
14948 .max_by_key(|range| range.end.column - range.start.column)
14949 .expect("prefixes is non-empty");
14950
14951 if prefix_range.is_empty() {
14952 all_selection_lines_are_comments = false;
14953 }
14954
14955 selection_edit_ranges.push(prefix_range);
14956 }
14957
14958 if all_selection_lines_are_comments {
14959 edits.extend(
14960 selection_edit_ranges
14961 .iter()
14962 .cloned()
14963 .map(|range| (range, empty_str.clone())),
14964 );
14965 } else {
14966 let min_column = selection_edit_ranges
14967 .iter()
14968 .map(|range| range.start.column)
14969 .min()
14970 .unwrap_or(0);
14971 edits.extend(selection_edit_ranges.iter().map(|range| {
14972 let position = Point::new(range.start.row, min_column);
14973 (position..position, first_prefix.clone())
14974 }));
14975 }
14976 } else if let Some(BlockCommentConfig {
14977 start: full_comment_prefix,
14978 end: comment_suffix,
14979 ..
14980 }) = language.block_comment()
14981 {
14982 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14983 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14984 let prefix_range = comment_prefix_range(
14985 snapshot.deref(),
14986 start_row,
14987 comment_prefix,
14988 comment_prefix_whitespace,
14989 ignore_indent,
14990 );
14991 let suffix_range = comment_suffix_range(
14992 snapshot.deref(),
14993 end_row,
14994 comment_suffix.trim_start_matches(' '),
14995 comment_suffix.starts_with(' '),
14996 );
14997
14998 if prefix_range.is_empty() || suffix_range.is_empty() {
14999 edits.push((
15000 prefix_range.start..prefix_range.start,
15001 full_comment_prefix.clone(),
15002 ));
15003 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15004 suffixes_inserted.push((end_row, comment_suffix.len()));
15005 } else {
15006 edits.push((prefix_range, empty_str.clone()));
15007 edits.push((suffix_range, empty_str.clone()));
15008 }
15009 } else {
15010 continue;
15011 }
15012 }
15013
15014 drop(snapshot);
15015 this.buffer.update(cx, |buffer, cx| {
15016 buffer.edit(edits, None, cx);
15017 });
15018
15019 // Adjust selections so that they end before any comment suffixes that
15020 // were inserted.
15021 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15022 let mut selections = this.selections.all::<Point>(cx);
15023 let snapshot = this.buffer.read(cx).read(cx);
15024 for selection in &mut selections {
15025 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15026 match row.cmp(&MultiBufferRow(selection.end.row)) {
15027 Ordering::Less => {
15028 suffixes_inserted.next();
15029 continue;
15030 }
15031 Ordering::Greater => break,
15032 Ordering::Equal => {
15033 if selection.end.column == snapshot.line_len(row) {
15034 if selection.is_empty() {
15035 selection.start.column -= suffix_len as u32;
15036 }
15037 selection.end.column -= suffix_len as u32;
15038 }
15039 break;
15040 }
15041 }
15042 }
15043 }
15044
15045 drop(snapshot);
15046 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15047
15048 let selections = this.selections.all::<Point>(cx);
15049 let selections_on_single_row = selections.windows(2).all(|selections| {
15050 selections[0].start.row == selections[1].start.row
15051 && selections[0].end.row == selections[1].end.row
15052 && selections[0].start.row == selections[0].end.row
15053 });
15054 let selections_selecting = selections
15055 .iter()
15056 .any(|selection| selection.start != selection.end);
15057 let advance_downwards = action.advance_downwards
15058 && selections_on_single_row
15059 && !selections_selecting
15060 && !matches!(this.mode, EditorMode::SingleLine);
15061
15062 if advance_downwards {
15063 let snapshot = this.buffer.read(cx).snapshot(cx);
15064
15065 this.change_selections(Default::default(), window, cx, |s| {
15066 s.move_cursors_with(|display_snapshot, display_point, _| {
15067 let mut point = display_point.to_point(display_snapshot);
15068 point.row += 1;
15069 point = snapshot.clip_point(point, Bias::Left);
15070 let display_point = point.to_display_point(display_snapshot);
15071 let goal = SelectionGoal::HorizontalPosition(
15072 display_snapshot
15073 .x_for_display_point(display_point, text_layout_details)
15074 .into(),
15075 );
15076 (display_point, goal)
15077 })
15078 });
15079 }
15080 });
15081 }
15082
15083 pub fn select_enclosing_symbol(
15084 &mut self,
15085 _: &SelectEnclosingSymbol,
15086 window: &mut Window,
15087 cx: &mut Context<Self>,
15088 ) {
15089 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15090
15091 let buffer = self.buffer.read(cx).snapshot(cx);
15092 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
15093
15094 fn update_selection(
15095 selection: &Selection<usize>,
15096 buffer_snap: &MultiBufferSnapshot,
15097 ) -> Option<Selection<usize>> {
15098 let cursor = selection.head();
15099 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15100 for symbol in symbols.iter().rev() {
15101 let start = symbol.range.start.to_offset(buffer_snap);
15102 let end = symbol.range.end.to_offset(buffer_snap);
15103 let new_range = start..end;
15104 if start < selection.start || end > selection.end {
15105 return Some(Selection {
15106 id: selection.id,
15107 start: new_range.start,
15108 end: new_range.end,
15109 goal: SelectionGoal::None,
15110 reversed: selection.reversed,
15111 });
15112 }
15113 }
15114 None
15115 }
15116
15117 let mut selected_larger_symbol = false;
15118 let new_selections = old_selections
15119 .iter()
15120 .map(|selection| match update_selection(selection, &buffer) {
15121 Some(new_selection) => {
15122 if new_selection.range() != selection.range() {
15123 selected_larger_symbol = true;
15124 }
15125 new_selection
15126 }
15127 None => selection.clone(),
15128 })
15129 .collect::<Vec<_>>();
15130
15131 if selected_larger_symbol {
15132 self.change_selections(Default::default(), window, cx, |s| {
15133 s.select(new_selections);
15134 });
15135 }
15136 }
15137
15138 pub fn select_larger_syntax_node(
15139 &mut self,
15140 _: &SelectLargerSyntaxNode,
15141 window: &mut Window,
15142 cx: &mut Context<Self>,
15143 ) {
15144 let Some(visible_row_count) = self.visible_row_count() else {
15145 return;
15146 };
15147 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15148 if old_selections.is_empty() {
15149 return;
15150 }
15151
15152 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15153
15154 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15155 let buffer = self.buffer.read(cx).snapshot(cx);
15156
15157 let mut selected_larger_node = false;
15158 let mut new_selections = old_selections
15159 .iter()
15160 .map(|selection| {
15161 let old_range = selection.start..selection.end;
15162
15163 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15164 // manually select word at selection
15165 if ["string_content", "inline"].contains(&node.kind()) {
15166 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15167 // ignore if word is already selected
15168 if !word_range.is_empty() && old_range != word_range {
15169 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15170 // only select word if start and end point belongs to same word
15171 if word_range == last_word_range {
15172 selected_larger_node = true;
15173 return Selection {
15174 id: selection.id,
15175 start: word_range.start,
15176 end: word_range.end,
15177 goal: SelectionGoal::None,
15178 reversed: selection.reversed,
15179 };
15180 }
15181 }
15182 }
15183 }
15184
15185 let mut new_range = old_range.clone();
15186 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15187 new_range = range;
15188 if !node.is_named() {
15189 continue;
15190 }
15191 if !display_map.intersects_fold(new_range.start)
15192 && !display_map.intersects_fold(new_range.end)
15193 {
15194 break;
15195 }
15196 }
15197
15198 selected_larger_node |= new_range != old_range;
15199 Selection {
15200 id: selection.id,
15201 start: new_range.start,
15202 end: new_range.end,
15203 goal: SelectionGoal::None,
15204 reversed: selection.reversed,
15205 }
15206 })
15207 .collect::<Vec<_>>();
15208
15209 if !selected_larger_node {
15210 return; // don't put this call in the history
15211 }
15212
15213 // scroll based on transformation done to the last selection created by the user
15214 let (last_old, last_new) = old_selections
15215 .last()
15216 .zip(new_selections.last().cloned())
15217 .expect("old_selections isn't empty");
15218
15219 // revert selection
15220 let is_selection_reversed = {
15221 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15222 new_selections.last_mut().expect("checked above").reversed =
15223 should_newest_selection_be_reversed;
15224 should_newest_selection_be_reversed
15225 };
15226
15227 if selected_larger_node {
15228 self.select_syntax_node_history.disable_clearing = true;
15229 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15230 s.select(new_selections.clone());
15231 });
15232 self.select_syntax_node_history.disable_clearing = false;
15233 }
15234
15235 let start_row = last_new.start.to_display_point(&display_map).row().0;
15236 let end_row = last_new.end.to_display_point(&display_map).row().0;
15237 let selection_height = end_row - start_row + 1;
15238 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15239
15240 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15241 let scroll_behavior = if fits_on_the_screen {
15242 self.request_autoscroll(Autoscroll::fit(), cx);
15243 SelectSyntaxNodeScrollBehavior::FitSelection
15244 } else if is_selection_reversed {
15245 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15246 SelectSyntaxNodeScrollBehavior::CursorTop
15247 } else {
15248 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15249 SelectSyntaxNodeScrollBehavior::CursorBottom
15250 };
15251
15252 self.select_syntax_node_history.push((
15253 old_selections,
15254 scroll_behavior,
15255 is_selection_reversed,
15256 ));
15257 }
15258
15259 pub fn select_smaller_syntax_node(
15260 &mut self,
15261 _: &SelectSmallerSyntaxNode,
15262 window: &mut Window,
15263 cx: &mut Context<Self>,
15264 ) {
15265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15266
15267 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15268 self.select_syntax_node_history.pop()
15269 {
15270 if let Some(selection) = selections.last_mut() {
15271 selection.reversed = is_selection_reversed;
15272 }
15273
15274 self.select_syntax_node_history.disable_clearing = true;
15275 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15276 s.select(selections.to_vec());
15277 });
15278 self.select_syntax_node_history.disable_clearing = false;
15279
15280 match scroll_behavior {
15281 SelectSyntaxNodeScrollBehavior::CursorTop => {
15282 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15283 }
15284 SelectSyntaxNodeScrollBehavior::FitSelection => {
15285 self.request_autoscroll(Autoscroll::fit(), cx);
15286 }
15287 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15288 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15289 }
15290 }
15291 }
15292 }
15293
15294 pub fn unwrap_syntax_node(
15295 &mut self,
15296 _: &UnwrapSyntaxNode,
15297 window: &mut Window,
15298 cx: &mut Context<Self>,
15299 ) {
15300 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15301
15302 let buffer = self.buffer.read(cx).snapshot(cx);
15303 let selections = self
15304 .selections
15305 .all::<usize>(cx)
15306 .into_iter()
15307 // subtracting the offset requires sorting
15308 .sorted_by_key(|i| i.start);
15309
15310 let full_edits = selections
15311 .into_iter()
15312 .filter_map(|selection| {
15313 let child = if selection.is_empty()
15314 && let Some((_, ancestor_range)) =
15315 buffer.syntax_ancestor(selection.start..selection.end)
15316 {
15317 ancestor_range
15318 } else {
15319 selection.range()
15320 };
15321
15322 let mut parent = child.clone();
15323 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15324 parent = ancestor_range;
15325 if parent.start < child.start || parent.end > child.end {
15326 break;
15327 }
15328 }
15329
15330 if parent == child {
15331 return None;
15332 }
15333 let text = buffer.text_for_range(child).collect::<String>();
15334 Some((selection.id, parent, text))
15335 })
15336 .collect::<Vec<_>>();
15337 if full_edits.is_empty() {
15338 return;
15339 }
15340
15341 self.transact(window, cx, |this, window, cx| {
15342 this.buffer.update(cx, |buffer, cx| {
15343 buffer.edit(
15344 full_edits
15345 .iter()
15346 .map(|(_, p, t)| (p.clone(), t.clone()))
15347 .collect::<Vec<_>>(),
15348 None,
15349 cx,
15350 );
15351 });
15352 this.change_selections(Default::default(), window, cx, |s| {
15353 let mut offset = 0;
15354 let mut selections = vec![];
15355 for (id, parent, text) in full_edits {
15356 let start = parent.start - offset;
15357 offset += parent.len() - text.len();
15358 selections.push(Selection {
15359 id,
15360 start,
15361 end: start + text.len(),
15362 reversed: false,
15363 goal: Default::default(),
15364 });
15365 }
15366 s.select(selections);
15367 });
15368 });
15369 }
15370
15371 pub fn select_next_syntax_node(
15372 &mut self,
15373 _: &SelectNextSyntaxNode,
15374 window: &mut Window,
15375 cx: &mut Context<Self>,
15376 ) {
15377 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15378 if old_selections.is_empty() {
15379 return;
15380 }
15381
15382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15383
15384 let buffer = self.buffer.read(cx).snapshot(cx);
15385 let mut selected_sibling = false;
15386
15387 let new_selections = old_selections
15388 .iter()
15389 .map(|selection| {
15390 let old_range = selection.start..selection.end;
15391
15392 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15393 let new_range = node.byte_range();
15394 selected_sibling = true;
15395 Selection {
15396 id: selection.id,
15397 start: new_range.start,
15398 end: new_range.end,
15399 goal: SelectionGoal::None,
15400 reversed: selection.reversed,
15401 }
15402 } else {
15403 selection.clone()
15404 }
15405 })
15406 .collect::<Vec<_>>();
15407
15408 if selected_sibling {
15409 self.change_selections(
15410 SelectionEffects::scroll(Autoscroll::fit()),
15411 window,
15412 cx,
15413 |s| {
15414 s.select(new_selections);
15415 },
15416 );
15417 }
15418 }
15419
15420 pub fn select_prev_syntax_node(
15421 &mut self,
15422 _: &SelectPreviousSyntaxNode,
15423 window: &mut Window,
15424 cx: &mut Context<Self>,
15425 ) {
15426 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15427 if old_selections.is_empty() {
15428 return;
15429 }
15430
15431 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15432
15433 let buffer = self.buffer.read(cx).snapshot(cx);
15434 let mut selected_sibling = false;
15435
15436 let new_selections = old_selections
15437 .iter()
15438 .map(|selection| {
15439 let old_range = selection.start..selection.end;
15440
15441 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15442 let new_range = node.byte_range();
15443 selected_sibling = true;
15444 Selection {
15445 id: selection.id,
15446 start: new_range.start,
15447 end: new_range.end,
15448 goal: SelectionGoal::None,
15449 reversed: selection.reversed,
15450 }
15451 } else {
15452 selection.clone()
15453 }
15454 })
15455 .collect::<Vec<_>>();
15456
15457 if selected_sibling {
15458 self.change_selections(
15459 SelectionEffects::scroll(Autoscroll::fit()),
15460 window,
15461 cx,
15462 |s| {
15463 s.select(new_selections);
15464 },
15465 );
15466 }
15467 }
15468
15469 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15470 if !EditorSettings::get_global(cx).gutter.runnables {
15471 self.clear_tasks();
15472 return Task::ready(());
15473 }
15474 let project = self.project().map(Entity::downgrade);
15475 let task_sources = self.lsp_task_sources(cx);
15476 let multi_buffer = self.buffer.downgrade();
15477 cx.spawn_in(window, async move |editor, cx| {
15478 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15479 let Some(project) = project.and_then(|p| p.upgrade()) else {
15480 return;
15481 };
15482 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15483 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15484 }) else {
15485 return;
15486 };
15487
15488 let hide_runnables = project
15489 .update(cx, |project, _| project.is_via_collab())
15490 .unwrap_or(true);
15491 if hide_runnables {
15492 return;
15493 }
15494 let new_rows =
15495 cx.background_spawn({
15496 let snapshot = display_snapshot.clone();
15497 async move {
15498 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15499 }
15500 })
15501 .await;
15502 let Ok(lsp_tasks) =
15503 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15504 else {
15505 return;
15506 };
15507 let lsp_tasks = lsp_tasks.await;
15508
15509 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15510 lsp_tasks
15511 .into_iter()
15512 .flat_map(|(kind, tasks)| {
15513 tasks.into_iter().filter_map(move |(location, task)| {
15514 Some((kind.clone(), location?, task))
15515 })
15516 })
15517 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15518 let buffer = location.target.buffer;
15519 let buffer_snapshot = buffer.read(cx).snapshot();
15520 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15521 |(excerpt_id, snapshot, _)| {
15522 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15523 display_snapshot
15524 .buffer_snapshot()
15525 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15526 } else {
15527 None
15528 }
15529 },
15530 );
15531 if let Some(offset) = offset {
15532 let task_buffer_range =
15533 location.target.range.to_point(&buffer_snapshot);
15534 let context_buffer_range =
15535 task_buffer_range.to_offset(&buffer_snapshot);
15536 let context_range = BufferOffset(context_buffer_range.start)
15537 ..BufferOffset(context_buffer_range.end);
15538
15539 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15540 .or_insert_with(|| RunnableTasks {
15541 templates: Vec::new(),
15542 offset,
15543 column: task_buffer_range.start.column,
15544 extra_variables: HashMap::default(),
15545 context_range,
15546 })
15547 .templates
15548 .push((kind, task.original_task().clone()));
15549 }
15550
15551 acc
15552 })
15553 }) else {
15554 return;
15555 };
15556
15557 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15558 buffer.language_settings(cx).tasks.prefer_lsp
15559 }) else {
15560 return;
15561 };
15562
15563 let rows = Self::runnable_rows(
15564 project,
15565 display_snapshot,
15566 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15567 new_rows,
15568 cx.clone(),
15569 )
15570 .await;
15571 editor
15572 .update(cx, |editor, _| {
15573 editor.clear_tasks();
15574 for (key, mut value) in rows {
15575 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15576 value.templates.extend(lsp_tasks.templates);
15577 }
15578
15579 editor.insert_tasks(key, value);
15580 }
15581 for (key, value) in lsp_tasks_by_rows {
15582 editor.insert_tasks(key, value);
15583 }
15584 })
15585 .ok();
15586 })
15587 }
15588 fn fetch_runnable_ranges(
15589 snapshot: &DisplaySnapshot,
15590 range: Range<Anchor>,
15591 ) -> Vec<language::RunnableRange> {
15592 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15593 }
15594
15595 fn runnable_rows(
15596 project: Entity<Project>,
15597 snapshot: DisplaySnapshot,
15598 prefer_lsp: bool,
15599 runnable_ranges: Vec<RunnableRange>,
15600 cx: AsyncWindowContext,
15601 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15602 cx.spawn(async move |cx| {
15603 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15604 for mut runnable in runnable_ranges {
15605 let Some(tasks) = cx
15606 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15607 .ok()
15608 else {
15609 continue;
15610 };
15611 let mut tasks = tasks.await;
15612
15613 if prefer_lsp {
15614 tasks.retain(|(task_kind, _)| {
15615 !matches!(task_kind, TaskSourceKind::Language { .. })
15616 });
15617 }
15618 if tasks.is_empty() {
15619 continue;
15620 }
15621
15622 let point = runnable
15623 .run_range
15624 .start
15625 .to_point(&snapshot.buffer_snapshot());
15626 let Some(row) = snapshot
15627 .buffer_snapshot()
15628 .buffer_line_for_row(MultiBufferRow(point.row))
15629 .map(|(_, range)| range.start.row)
15630 else {
15631 continue;
15632 };
15633
15634 let context_range =
15635 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15636 runnable_rows.push((
15637 (runnable.buffer_id, row),
15638 RunnableTasks {
15639 templates: tasks,
15640 offset: snapshot
15641 .buffer_snapshot()
15642 .anchor_before(runnable.run_range.start),
15643 context_range,
15644 column: point.column,
15645 extra_variables: runnable.extra_captures,
15646 },
15647 ));
15648 }
15649 runnable_rows
15650 })
15651 }
15652
15653 fn templates_with_tags(
15654 project: &Entity<Project>,
15655 runnable: &mut Runnable,
15656 cx: &mut App,
15657 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15658 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15659 let (worktree_id, file) = project
15660 .buffer_for_id(runnable.buffer, cx)
15661 .and_then(|buffer| buffer.read(cx).file())
15662 .map(|file| (file.worktree_id(cx), file.clone()))
15663 .unzip();
15664
15665 (
15666 project.task_store().read(cx).task_inventory().cloned(),
15667 worktree_id,
15668 file,
15669 )
15670 });
15671
15672 let tags = mem::take(&mut runnable.tags);
15673 let language = runnable.language.clone();
15674 cx.spawn(async move |cx| {
15675 let mut templates_with_tags = Vec::new();
15676 if let Some(inventory) = inventory {
15677 for RunnableTag(tag) in tags {
15678 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15679 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15680 }) else {
15681 return templates_with_tags;
15682 };
15683 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15684 move |(_, template)| {
15685 template.tags.iter().any(|source_tag| source_tag == &tag)
15686 },
15687 ));
15688 }
15689 }
15690 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15691
15692 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15693 // Strongest source wins; if we have worktree tag binding, prefer that to
15694 // global and language bindings;
15695 // if we have a global binding, prefer that to language binding.
15696 let first_mismatch = templates_with_tags
15697 .iter()
15698 .position(|(tag_source, _)| tag_source != leading_tag_source);
15699 if let Some(index) = first_mismatch {
15700 templates_with_tags.truncate(index);
15701 }
15702 }
15703
15704 templates_with_tags
15705 })
15706 }
15707
15708 pub fn move_to_enclosing_bracket(
15709 &mut self,
15710 _: &MoveToEnclosingBracket,
15711 window: &mut Window,
15712 cx: &mut Context<Self>,
15713 ) {
15714 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15715 self.change_selections(Default::default(), window, cx, |s| {
15716 s.move_offsets_with(|snapshot, selection| {
15717 let Some(enclosing_bracket_ranges) =
15718 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15719 else {
15720 return;
15721 };
15722
15723 let mut best_length = usize::MAX;
15724 let mut best_inside = false;
15725 let mut best_in_bracket_range = false;
15726 let mut best_destination = None;
15727 for (open, close) in enclosing_bracket_ranges {
15728 let close = close.to_inclusive();
15729 let length = close.end() - open.start;
15730 let inside = selection.start >= open.end && selection.end <= *close.start();
15731 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15732 || close.contains(&selection.head());
15733
15734 // If best is next to a bracket and current isn't, skip
15735 if !in_bracket_range && best_in_bracket_range {
15736 continue;
15737 }
15738
15739 // Prefer smaller lengths unless best is inside and current isn't
15740 if length > best_length && (best_inside || !inside) {
15741 continue;
15742 }
15743
15744 best_length = length;
15745 best_inside = inside;
15746 best_in_bracket_range = in_bracket_range;
15747 best_destination = Some(
15748 if close.contains(&selection.start) && close.contains(&selection.end) {
15749 if inside { open.end } else { open.start }
15750 } else if inside {
15751 *close.start()
15752 } else {
15753 *close.end()
15754 },
15755 );
15756 }
15757
15758 if let Some(destination) = best_destination {
15759 selection.collapse_to(destination, SelectionGoal::None);
15760 }
15761 })
15762 });
15763 }
15764
15765 pub fn undo_selection(
15766 &mut self,
15767 _: &UndoSelection,
15768 window: &mut Window,
15769 cx: &mut Context<Self>,
15770 ) {
15771 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15772 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15773 self.selection_history.mode = SelectionHistoryMode::Undoing;
15774 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15775 this.end_selection(window, cx);
15776 this.change_selections(
15777 SelectionEffects::scroll(Autoscroll::newest()),
15778 window,
15779 cx,
15780 |s| s.select_anchors(entry.selections.to_vec()),
15781 );
15782 });
15783 self.selection_history.mode = SelectionHistoryMode::Normal;
15784
15785 self.select_next_state = entry.select_next_state;
15786 self.select_prev_state = entry.select_prev_state;
15787 self.add_selections_state = entry.add_selections_state;
15788 }
15789 }
15790
15791 pub fn redo_selection(
15792 &mut self,
15793 _: &RedoSelection,
15794 window: &mut Window,
15795 cx: &mut Context<Self>,
15796 ) {
15797 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15798 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15799 self.selection_history.mode = SelectionHistoryMode::Redoing;
15800 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15801 this.end_selection(window, cx);
15802 this.change_selections(
15803 SelectionEffects::scroll(Autoscroll::newest()),
15804 window,
15805 cx,
15806 |s| s.select_anchors(entry.selections.to_vec()),
15807 );
15808 });
15809 self.selection_history.mode = SelectionHistoryMode::Normal;
15810
15811 self.select_next_state = entry.select_next_state;
15812 self.select_prev_state = entry.select_prev_state;
15813 self.add_selections_state = entry.add_selections_state;
15814 }
15815 }
15816
15817 pub fn expand_excerpts(
15818 &mut self,
15819 action: &ExpandExcerpts,
15820 _: &mut Window,
15821 cx: &mut Context<Self>,
15822 ) {
15823 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15824 }
15825
15826 pub fn expand_excerpts_down(
15827 &mut self,
15828 action: &ExpandExcerptsDown,
15829 _: &mut Window,
15830 cx: &mut Context<Self>,
15831 ) {
15832 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15833 }
15834
15835 pub fn expand_excerpts_up(
15836 &mut self,
15837 action: &ExpandExcerptsUp,
15838 _: &mut Window,
15839 cx: &mut Context<Self>,
15840 ) {
15841 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15842 }
15843
15844 pub fn expand_excerpts_for_direction(
15845 &mut self,
15846 lines: u32,
15847 direction: ExpandExcerptDirection,
15848
15849 cx: &mut Context<Self>,
15850 ) {
15851 let selections = self.selections.disjoint_anchors_arc();
15852
15853 let lines = if lines == 0 {
15854 EditorSettings::get_global(cx).expand_excerpt_lines
15855 } else {
15856 lines
15857 };
15858
15859 self.buffer.update(cx, |buffer, cx| {
15860 let snapshot = buffer.snapshot(cx);
15861 let mut excerpt_ids = selections
15862 .iter()
15863 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15864 .collect::<Vec<_>>();
15865 excerpt_ids.sort();
15866 excerpt_ids.dedup();
15867 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15868 })
15869 }
15870
15871 pub fn expand_excerpt(
15872 &mut self,
15873 excerpt: ExcerptId,
15874 direction: ExpandExcerptDirection,
15875 window: &mut Window,
15876 cx: &mut Context<Self>,
15877 ) {
15878 let current_scroll_position = self.scroll_position(cx);
15879 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15880 let mut should_scroll_up = false;
15881
15882 if direction == ExpandExcerptDirection::Down {
15883 let multi_buffer = self.buffer.read(cx);
15884 let snapshot = multi_buffer.snapshot(cx);
15885 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15886 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15887 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15888 {
15889 let buffer_snapshot = buffer.read(cx).snapshot();
15890 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15891 let last_row = buffer_snapshot.max_point().row;
15892 let lines_below = last_row.saturating_sub(excerpt_end_row);
15893 should_scroll_up = lines_below >= lines_to_expand;
15894 }
15895 }
15896
15897 self.buffer.update(cx, |buffer, cx| {
15898 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15899 });
15900
15901 if should_scroll_up {
15902 let new_scroll_position =
15903 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15904 self.set_scroll_position(new_scroll_position, window, cx);
15905 }
15906 }
15907
15908 pub fn go_to_singleton_buffer_point(
15909 &mut self,
15910 point: Point,
15911 window: &mut Window,
15912 cx: &mut Context<Self>,
15913 ) {
15914 self.go_to_singleton_buffer_range(point..point, window, cx);
15915 }
15916
15917 pub fn go_to_singleton_buffer_range(
15918 &mut self,
15919 range: Range<Point>,
15920 window: &mut Window,
15921 cx: &mut Context<Self>,
15922 ) {
15923 let multibuffer = self.buffer().read(cx);
15924 let Some(buffer) = multibuffer.as_singleton() else {
15925 return;
15926 };
15927 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15928 return;
15929 };
15930 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15931 return;
15932 };
15933 self.change_selections(
15934 SelectionEffects::default().nav_history(true),
15935 window,
15936 cx,
15937 |s| s.select_anchor_ranges([start..end]),
15938 );
15939 }
15940
15941 pub fn go_to_diagnostic(
15942 &mut self,
15943 action: &GoToDiagnostic,
15944 window: &mut Window,
15945 cx: &mut Context<Self>,
15946 ) {
15947 if !self.diagnostics_enabled() {
15948 return;
15949 }
15950 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15951 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15952 }
15953
15954 pub fn go_to_prev_diagnostic(
15955 &mut self,
15956 action: &GoToPreviousDiagnostic,
15957 window: &mut Window,
15958 cx: &mut Context<Self>,
15959 ) {
15960 if !self.diagnostics_enabled() {
15961 return;
15962 }
15963 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15964 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15965 }
15966
15967 pub fn go_to_diagnostic_impl(
15968 &mut self,
15969 direction: Direction,
15970 severity: GoToDiagnosticSeverityFilter,
15971 window: &mut Window,
15972 cx: &mut Context<Self>,
15973 ) {
15974 let buffer = self.buffer.read(cx).snapshot(cx);
15975 let selection = self.selections.newest::<usize>(cx);
15976
15977 let mut active_group_id = None;
15978 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15979 && active_group.active_range.start.to_offset(&buffer) == selection.start
15980 {
15981 active_group_id = Some(active_group.group_id);
15982 }
15983
15984 fn filtered<'a>(
15985 snapshot: EditorSnapshot,
15986 severity: GoToDiagnosticSeverityFilter,
15987 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
15988 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
15989 diagnostics
15990 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15991 .filter(|entry| entry.range.start != entry.range.end)
15992 .filter(|entry| !entry.diagnostic.is_unnecessary)
15993 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15994 }
15995
15996 let snapshot = self.snapshot(window, cx);
15997 let before = filtered(
15998 snapshot.clone(),
15999 severity,
16000 buffer
16001 .diagnostics_in_range(0..selection.start)
16002 .filter(|entry| entry.range.start <= selection.start),
16003 );
16004 let after = filtered(
16005 snapshot,
16006 severity,
16007 buffer
16008 .diagnostics_in_range(selection.start..buffer.len())
16009 .filter(|entry| entry.range.start >= selection.start),
16010 );
16011
16012 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16013 if direction == Direction::Prev {
16014 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16015 {
16016 for diagnostic in prev_diagnostics.into_iter().rev() {
16017 if diagnostic.range.start != selection.start
16018 || active_group_id
16019 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16020 {
16021 found = Some(diagnostic);
16022 break 'outer;
16023 }
16024 }
16025 }
16026 } else {
16027 for diagnostic in after.chain(before) {
16028 if diagnostic.range.start != selection.start
16029 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16030 {
16031 found = Some(diagnostic);
16032 break;
16033 }
16034 }
16035 }
16036 let Some(next_diagnostic) = found else {
16037 return;
16038 };
16039
16040 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16041 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16042 return;
16043 };
16044 self.change_selections(Default::default(), window, cx, |s| {
16045 s.select_ranges(vec![
16046 next_diagnostic.range.start..next_diagnostic.range.start,
16047 ])
16048 });
16049 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16050 self.refresh_edit_prediction(false, true, window, cx);
16051 }
16052
16053 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16054 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16055 let snapshot = self.snapshot(window, cx);
16056 let selection = self.selections.newest::<Point>(cx);
16057 self.go_to_hunk_before_or_after_position(
16058 &snapshot,
16059 selection.head(),
16060 Direction::Next,
16061 window,
16062 cx,
16063 );
16064 }
16065
16066 pub fn go_to_hunk_before_or_after_position(
16067 &mut self,
16068 snapshot: &EditorSnapshot,
16069 position: Point,
16070 direction: Direction,
16071 window: &mut Window,
16072 cx: &mut Context<Editor>,
16073 ) {
16074 let row = if direction == Direction::Next {
16075 self.hunk_after_position(snapshot, position)
16076 .map(|hunk| hunk.row_range.start)
16077 } else {
16078 self.hunk_before_position(snapshot, position)
16079 };
16080
16081 if let Some(row) = row {
16082 let destination = Point::new(row.0, 0);
16083 let autoscroll = Autoscroll::center();
16084
16085 self.unfold_ranges(&[destination..destination], false, false, cx);
16086 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16087 s.select_ranges([destination..destination]);
16088 });
16089 }
16090 }
16091
16092 fn hunk_after_position(
16093 &mut self,
16094 snapshot: &EditorSnapshot,
16095 position: Point,
16096 ) -> Option<MultiBufferDiffHunk> {
16097 snapshot
16098 .buffer_snapshot()
16099 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16100 .find(|hunk| hunk.row_range.start.0 > position.row)
16101 .or_else(|| {
16102 snapshot
16103 .buffer_snapshot()
16104 .diff_hunks_in_range(Point::zero()..position)
16105 .find(|hunk| hunk.row_range.end.0 < position.row)
16106 })
16107 }
16108
16109 fn go_to_prev_hunk(
16110 &mut self,
16111 _: &GoToPreviousHunk,
16112 window: &mut Window,
16113 cx: &mut Context<Self>,
16114 ) {
16115 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16116 let snapshot = self.snapshot(window, cx);
16117 let selection = self.selections.newest::<Point>(cx);
16118 self.go_to_hunk_before_or_after_position(
16119 &snapshot,
16120 selection.head(),
16121 Direction::Prev,
16122 window,
16123 cx,
16124 );
16125 }
16126
16127 fn hunk_before_position(
16128 &mut self,
16129 snapshot: &EditorSnapshot,
16130 position: Point,
16131 ) -> Option<MultiBufferRow> {
16132 snapshot
16133 .buffer_snapshot()
16134 .diff_hunk_before(position)
16135 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16136 }
16137
16138 fn go_to_next_change(
16139 &mut self,
16140 _: &GoToNextChange,
16141 window: &mut Window,
16142 cx: &mut Context<Self>,
16143 ) {
16144 if let Some(selections) = self
16145 .change_list
16146 .next_change(1, Direction::Next)
16147 .map(|s| s.to_vec())
16148 {
16149 self.change_selections(Default::default(), window, cx, |s| {
16150 let map = s.display_map();
16151 s.select_display_ranges(selections.iter().map(|a| {
16152 let point = a.to_display_point(&map);
16153 point..point
16154 }))
16155 })
16156 }
16157 }
16158
16159 fn go_to_previous_change(
16160 &mut self,
16161 _: &GoToPreviousChange,
16162 window: &mut Window,
16163 cx: &mut Context<Self>,
16164 ) {
16165 if let Some(selections) = self
16166 .change_list
16167 .next_change(1, Direction::Prev)
16168 .map(|s| s.to_vec())
16169 {
16170 self.change_selections(Default::default(), window, cx, |s| {
16171 let map = s.display_map();
16172 s.select_display_ranges(selections.iter().map(|a| {
16173 let point = a.to_display_point(&map);
16174 point..point
16175 }))
16176 })
16177 }
16178 }
16179
16180 pub fn go_to_next_document_highlight(
16181 &mut self,
16182 _: &GoToNextDocumentHighlight,
16183 window: &mut Window,
16184 cx: &mut Context<Self>,
16185 ) {
16186 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16187 }
16188
16189 pub fn go_to_prev_document_highlight(
16190 &mut self,
16191 _: &GoToPreviousDocumentHighlight,
16192 window: &mut Window,
16193 cx: &mut Context<Self>,
16194 ) {
16195 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16196 }
16197
16198 pub fn go_to_document_highlight_before_or_after_position(
16199 &mut self,
16200 direction: Direction,
16201 window: &mut Window,
16202 cx: &mut Context<Editor>,
16203 ) {
16204 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16205 let snapshot = self.snapshot(window, cx);
16206 let buffer = &snapshot.buffer_snapshot();
16207 let position = self.selections.newest::<Point>(cx).head();
16208 let anchor_position = buffer.anchor_after(position);
16209
16210 // Get all document highlights (both read and write)
16211 let mut all_highlights = Vec::new();
16212
16213 if let Some((_, read_highlights)) = self
16214 .background_highlights
16215 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16216 {
16217 all_highlights.extend(read_highlights.iter());
16218 }
16219
16220 if let Some((_, write_highlights)) = self
16221 .background_highlights
16222 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16223 {
16224 all_highlights.extend(write_highlights.iter());
16225 }
16226
16227 if all_highlights.is_empty() {
16228 return;
16229 }
16230
16231 // Sort highlights by position
16232 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16233
16234 let target_highlight = match direction {
16235 Direction::Next => {
16236 // Find the first highlight after the current position
16237 all_highlights
16238 .iter()
16239 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16240 }
16241 Direction::Prev => {
16242 // Find the last highlight before the current position
16243 all_highlights
16244 .iter()
16245 .rev()
16246 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16247 }
16248 };
16249
16250 if let Some(highlight) = target_highlight {
16251 let destination = highlight.start.to_point(buffer);
16252 let autoscroll = Autoscroll::center();
16253
16254 self.unfold_ranges(&[destination..destination], false, false, cx);
16255 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16256 s.select_ranges([destination..destination]);
16257 });
16258 }
16259 }
16260
16261 fn go_to_line<T: 'static>(
16262 &mut self,
16263 position: Anchor,
16264 highlight_color: Option<Hsla>,
16265 window: &mut Window,
16266 cx: &mut Context<Self>,
16267 ) {
16268 let snapshot = self.snapshot(window, cx).display_snapshot;
16269 let position = position.to_point(&snapshot.buffer_snapshot());
16270 let start = snapshot
16271 .buffer_snapshot()
16272 .clip_point(Point::new(position.row, 0), Bias::Left);
16273 let end = start + Point::new(1, 0);
16274 let start = snapshot.buffer_snapshot().anchor_before(start);
16275 let end = snapshot.buffer_snapshot().anchor_before(end);
16276
16277 self.highlight_rows::<T>(
16278 start..end,
16279 highlight_color
16280 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16281 Default::default(),
16282 cx,
16283 );
16284
16285 if self.buffer.read(cx).is_singleton() {
16286 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16287 }
16288 }
16289
16290 pub fn go_to_definition(
16291 &mut self,
16292 _: &GoToDefinition,
16293 window: &mut Window,
16294 cx: &mut Context<Self>,
16295 ) -> Task<Result<Navigated>> {
16296 let definition =
16297 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16298 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16299 cx.spawn_in(window, async move |editor, cx| {
16300 if definition.await? == Navigated::Yes {
16301 return Ok(Navigated::Yes);
16302 }
16303 match fallback_strategy {
16304 GoToDefinitionFallback::None => Ok(Navigated::No),
16305 GoToDefinitionFallback::FindAllReferences => {
16306 match editor.update_in(cx, |editor, window, cx| {
16307 editor.find_all_references(&FindAllReferences, window, cx)
16308 })? {
16309 Some(references) => references.await,
16310 None => Ok(Navigated::No),
16311 }
16312 }
16313 }
16314 })
16315 }
16316
16317 pub fn go_to_declaration(
16318 &mut self,
16319 _: &GoToDeclaration,
16320 window: &mut Window,
16321 cx: &mut Context<Self>,
16322 ) -> Task<Result<Navigated>> {
16323 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16324 }
16325
16326 pub fn go_to_declaration_split(
16327 &mut self,
16328 _: &GoToDeclaration,
16329 window: &mut Window,
16330 cx: &mut Context<Self>,
16331 ) -> Task<Result<Navigated>> {
16332 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16333 }
16334
16335 pub fn go_to_implementation(
16336 &mut self,
16337 _: &GoToImplementation,
16338 window: &mut Window,
16339 cx: &mut Context<Self>,
16340 ) -> Task<Result<Navigated>> {
16341 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16342 }
16343
16344 pub fn go_to_implementation_split(
16345 &mut self,
16346 _: &GoToImplementationSplit,
16347 window: &mut Window,
16348 cx: &mut Context<Self>,
16349 ) -> Task<Result<Navigated>> {
16350 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16351 }
16352
16353 pub fn go_to_type_definition(
16354 &mut self,
16355 _: &GoToTypeDefinition,
16356 window: &mut Window,
16357 cx: &mut Context<Self>,
16358 ) -> Task<Result<Navigated>> {
16359 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16360 }
16361
16362 pub fn go_to_definition_split(
16363 &mut self,
16364 _: &GoToDefinitionSplit,
16365 window: &mut Window,
16366 cx: &mut Context<Self>,
16367 ) -> Task<Result<Navigated>> {
16368 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16369 }
16370
16371 pub fn go_to_type_definition_split(
16372 &mut self,
16373 _: &GoToTypeDefinitionSplit,
16374 window: &mut Window,
16375 cx: &mut Context<Self>,
16376 ) -> Task<Result<Navigated>> {
16377 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16378 }
16379
16380 fn go_to_definition_of_kind(
16381 &mut self,
16382 kind: GotoDefinitionKind,
16383 split: bool,
16384 window: &mut Window,
16385 cx: &mut Context<Self>,
16386 ) -> Task<Result<Navigated>> {
16387 let Some(provider) = self.semantics_provider.clone() else {
16388 return Task::ready(Ok(Navigated::No));
16389 };
16390 let head = self.selections.newest::<usize>(cx).head();
16391 let buffer = self.buffer.read(cx);
16392 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16393 return Task::ready(Ok(Navigated::No));
16394 };
16395 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16396 return Task::ready(Ok(Navigated::No));
16397 };
16398
16399 cx.spawn_in(window, async move |editor, cx| {
16400 let Some(definitions) = definitions.await? else {
16401 return Ok(Navigated::No);
16402 };
16403 let navigated = editor
16404 .update_in(cx, |editor, window, cx| {
16405 editor.navigate_to_hover_links(
16406 Some(kind),
16407 definitions
16408 .into_iter()
16409 .filter(|location| {
16410 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16411 })
16412 .map(HoverLink::Text)
16413 .collect::<Vec<_>>(),
16414 split,
16415 window,
16416 cx,
16417 )
16418 })?
16419 .await?;
16420 anyhow::Ok(navigated)
16421 })
16422 }
16423
16424 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16425 let selection = self.selections.newest_anchor();
16426 let head = selection.head();
16427 let tail = selection.tail();
16428
16429 let Some((buffer, start_position)) =
16430 self.buffer.read(cx).text_anchor_for_position(head, cx)
16431 else {
16432 return;
16433 };
16434
16435 let end_position = if head != tail {
16436 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16437 return;
16438 };
16439 Some(pos)
16440 } else {
16441 None
16442 };
16443
16444 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16445 let url = if let Some(end_pos) = end_position {
16446 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16447 } else {
16448 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16449 };
16450
16451 if let Some(url) = url {
16452 cx.update(|window, cx| {
16453 if parse_zed_link(&url, cx).is_some() {
16454 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16455 } else {
16456 cx.open_url(&url);
16457 }
16458 })?;
16459 }
16460
16461 anyhow::Ok(())
16462 });
16463
16464 url_finder.detach();
16465 }
16466
16467 pub fn open_selected_filename(
16468 &mut self,
16469 _: &OpenSelectedFilename,
16470 window: &mut Window,
16471 cx: &mut Context<Self>,
16472 ) {
16473 let Some(workspace) = self.workspace() else {
16474 return;
16475 };
16476
16477 let position = self.selections.newest_anchor().head();
16478
16479 let Some((buffer, buffer_position)) =
16480 self.buffer.read(cx).text_anchor_for_position(position, cx)
16481 else {
16482 return;
16483 };
16484
16485 let project = self.project.clone();
16486
16487 cx.spawn_in(window, async move |_, cx| {
16488 let result = find_file(&buffer, project, buffer_position, cx).await;
16489
16490 if let Some((_, path)) = result {
16491 workspace
16492 .update_in(cx, |workspace, window, cx| {
16493 workspace.open_resolved_path(path, window, cx)
16494 })?
16495 .await?;
16496 }
16497 anyhow::Ok(())
16498 })
16499 .detach();
16500 }
16501
16502 pub(crate) fn navigate_to_hover_links(
16503 &mut self,
16504 kind: Option<GotoDefinitionKind>,
16505 definitions: Vec<HoverLink>,
16506 split: bool,
16507 window: &mut Window,
16508 cx: &mut Context<Editor>,
16509 ) -> Task<Result<Navigated>> {
16510 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16511 let mut first_url_or_file = None;
16512 let definitions: Vec<_> = definitions
16513 .into_iter()
16514 .filter_map(|def| match def {
16515 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16516 HoverLink::InlayHint(lsp_location, server_id) => {
16517 let computation =
16518 self.compute_target_location(lsp_location, server_id, window, cx);
16519 Some(cx.background_spawn(computation))
16520 }
16521 HoverLink::Url(url) => {
16522 first_url_or_file = Some(Either::Left(url));
16523 None
16524 }
16525 HoverLink::File(path) => {
16526 first_url_or_file = Some(Either::Right(path));
16527 None
16528 }
16529 })
16530 .collect();
16531
16532 let workspace = self.workspace();
16533
16534 cx.spawn_in(window, async move |editor, cx| {
16535 let locations: Vec<Location> = future::join_all(definitions)
16536 .await
16537 .into_iter()
16538 .filter_map(|location| location.transpose())
16539 .collect::<Result<_>>()
16540 .context("location tasks")?;
16541 let mut locations = cx.update(|_, cx| {
16542 locations
16543 .into_iter()
16544 .map(|location| {
16545 let buffer = location.buffer.read(cx);
16546 (location.buffer, location.range.to_point(buffer))
16547 })
16548 .into_group_map()
16549 })?;
16550 let mut num_locations = 0;
16551 for ranges in locations.values_mut() {
16552 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16553 ranges.dedup();
16554 num_locations += ranges.len();
16555 }
16556
16557 if num_locations > 1 {
16558 let Some(workspace) = workspace else {
16559 return Ok(Navigated::No);
16560 };
16561
16562 let tab_kind = match kind {
16563 Some(GotoDefinitionKind::Implementation) => "Implementations",
16564 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16565 Some(GotoDefinitionKind::Declaration) => "Declarations",
16566 Some(GotoDefinitionKind::Type) => "Types",
16567 };
16568 let title = editor
16569 .update_in(cx, |_, _, cx| {
16570 let target = locations
16571 .iter()
16572 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16573 .map(|(buffer, location)| {
16574 buffer
16575 .read(cx)
16576 .text_for_range(location.clone())
16577 .collect::<String>()
16578 })
16579 .filter(|text| !text.contains('\n'))
16580 .unique()
16581 .take(3)
16582 .join(", ");
16583 if target.is_empty() {
16584 tab_kind.to_owned()
16585 } else {
16586 format!("{tab_kind} for {target}")
16587 }
16588 })
16589 .context("buffer title")?;
16590
16591 let opened = workspace
16592 .update_in(cx, |workspace, window, cx| {
16593 Self::open_locations_in_multibuffer(
16594 workspace,
16595 locations,
16596 title,
16597 split,
16598 MultibufferSelectionMode::First,
16599 window,
16600 cx,
16601 )
16602 })
16603 .is_ok();
16604
16605 anyhow::Ok(Navigated::from_bool(opened))
16606 } else if num_locations == 0 {
16607 // If there is one url or file, open it directly
16608 match first_url_or_file {
16609 Some(Either::Left(url)) => {
16610 cx.update(|_, cx| cx.open_url(&url))?;
16611 Ok(Navigated::Yes)
16612 }
16613 Some(Either::Right(path)) => {
16614 let Some(workspace) = workspace else {
16615 return Ok(Navigated::No);
16616 };
16617
16618 workspace
16619 .update_in(cx, |workspace, window, cx| {
16620 workspace.open_resolved_path(path, window, cx)
16621 })?
16622 .await?;
16623 Ok(Navigated::Yes)
16624 }
16625 None => Ok(Navigated::No),
16626 }
16627 } else {
16628 let Some(workspace) = workspace else {
16629 return Ok(Navigated::No);
16630 };
16631
16632 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16633 let target_range = target_ranges.first().unwrap().clone();
16634
16635 editor.update_in(cx, |editor, window, cx| {
16636 let range = target_range.to_point(target_buffer.read(cx));
16637 let range = editor.range_for_match(&range);
16638 let range = collapse_multiline_range(range);
16639
16640 if !split
16641 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16642 {
16643 editor.go_to_singleton_buffer_range(range, window, cx);
16644 } else {
16645 let pane = workspace.read(cx).active_pane().clone();
16646 window.defer(cx, move |window, cx| {
16647 let target_editor: Entity<Self> =
16648 workspace.update(cx, |workspace, cx| {
16649 let pane = if split {
16650 workspace.adjacent_pane(window, cx)
16651 } else {
16652 workspace.active_pane().clone()
16653 };
16654
16655 workspace.open_project_item(
16656 pane,
16657 target_buffer.clone(),
16658 true,
16659 true,
16660 window,
16661 cx,
16662 )
16663 });
16664 target_editor.update(cx, |target_editor, cx| {
16665 // When selecting a definition in a different buffer, disable the nav history
16666 // to avoid creating a history entry at the previous cursor location.
16667 pane.update(cx, |pane, _| pane.disable_history());
16668 target_editor.go_to_singleton_buffer_range(range, window, cx);
16669 pane.update(cx, |pane, _| pane.enable_history());
16670 });
16671 });
16672 }
16673 Navigated::Yes
16674 })
16675 }
16676 })
16677 }
16678
16679 fn compute_target_location(
16680 &self,
16681 lsp_location: lsp::Location,
16682 server_id: LanguageServerId,
16683 window: &mut Window,
16684 cx: &mut Context<Self>,
16685 ) -> Task<anyhow::Result<Option<Location>>> {
16686 let Some(project) = self.project.clone() else {
16687 return Task::ready(Ok(None));
16688 };
16689
16690 cx.spawn_in(window, async move |editor, cx| {
16691 let location_task = editor.update(cx, |_, cx| {
16692 project.update(cx, |project, cx| {
16693 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16694 })
16695 })?;
16696 let location = Some({
16697 let target_buffer_handle = location_task.await.context("open local buffer")?;
16698 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16699 let target_start = target_buffer
16700 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16701 let target_end = target_buffer
16702 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16703 target_buffer.anchor_after(target_start)
16704 ..target_buffer.anchor_before(target_end)
16705 })?;
16706 Location {
16707 buffer: target_buffer_handle,
16708 range,
16709 }
16710 });
16711 Ok(location)
16712 })
16713 }
16714
16715 pub fn find_all_references(
16716 &mut self,
16717 _: &FindAllReferences,
16718 window: &mut Window,
16719 cx: &mut Context<Self>,
16720 ) -> Option<Task<Result<Navigated>>> {
16721 let selection = self.selections.newest::<usize>(cx);
16722 let multi_buffer = self.buffer.read(cx);
16723 let head = selection.head();
16724
16725 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16726 let head_anchor = multi_buffer_snapshot.anchor_at(
16727 head,
16728 if head < selection.tail() {
16729 Bias::Right
16730 } else {
16731 Bias::Left
16732 },
16733 );
16734
16735 match self
16736 .find_all_references_task_sources
16737 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16738 {
16739 Ok(_) => {
16740 log::info!(
16741 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16742 );
16743 return None;
16744 }
16745 Err(i) => {
16746 self.find_all_references_task_sources.insert(i, head_anchor);
16747 }
16748 }
16749
16750 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16751 let workspace = self.workspace()?;
16752 let project = workspace.read(cx).project().clone();
16753 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16754 Some(cx.spawn_in(window, async move |editor, cx| {
16755 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16756 if let Ok(i) = editor
16757 .find_all_references_task_sources
16758 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16759 {
16760 editor.find_all_references_task_sources.remove(i);
16761 }
16762 });
16763
16764 let Some(locations) = references.await? else {
16765 return anyhow::Ok(Navigated::No);
16766 };
16767 let mut locations = cx.update(|_, cx| {
16768 locations
16769 .into_iter()
16770 .map(|location| {
16771 let buffer = location.buffer.read(cx);
16772 (location.buffer, location.range.to_point(buffer))
16773 })
16774 .into_group_map()
16775 })?;
16776 if locations.is_empty() {
16777 return anyhow::Ok(Navigated::No);
16778 }
16779 for ranges in locations.values_mut() {
16780 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16781 ranges.dedup();
16782 }
16783
16784 workspace.update_in(cx, |workspace, window, cx| {
16785 let target = locations
16786 .iter()
16787 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16788 .map(|(buffer, location)| {
16789 buffer
16790 .read(cx)
16791 .text_for_range(location.clone())
16792 .collect::<String>()
16793 })
16794 .filter(|text| !text.contains('\n'))
16795 .unique()
16796 .take(3)
16797 .join(", ");
16798 let title = if target.is_empty() {
16799 "References".to_owned()
16800 } else {
16801 format!("References to {target}")
16802 };
16803 Self::open_locations_in_multibuffer(
16804 workspace,
16805 locations,
16806 title,
16807 false,
16808 MultibufferSelectionMode::First,
16809 window,
16810 cx,
16811 );
16812 Navigated::Yes
16813 })
16814 }))
16815 }
16816
16817 /// Opens a multibuffer with the given project locations in it
16818 pub fn open_locations_in_multibuffer(
16819 workspace: &mut Workspace,
16820 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16821 title: String,
16822 split: bool,
16823 multibuffer_selection_mode: MultibufferSelectionMode,
16824 window: &mut Window,
16825 cx: &mut Context<Workspace>,
16826 ) {
16827 if locations.is_empty() {
16828 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16829 return;
16830 }
16831
16832 let capability = workspace.project().read(cx).capability();
16833 let mut ranges = <Vec<Range<Anchor>>>::new();
16834
16835 // a key to find existing multibuffer editors with the same set of locations
16836 // to prevent us from opening more and more multibuffer tabs for searches and the like
16837 let mut key = (title.clone(), vec![]);
16838 let excerpt_buffer = cx.new(|cx| {
16839 let key = &mut key.1;
16840 let mut multibuffer = MultiBuffer::new(capability);
16841 for (buffer, mut ranges_for_buffer) in locations {
16842 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16843 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16844 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16845 PathKey::for_buffer(&buffer, cx),
16846 buffer.clone(),
16847 ranges_for_buffer,
16848 multibuffer_context_lines(cx),
16849 cx,
16850 );
16851 ranges.extend(new_ranges)
16852 }
16853
16854 multibuffer.with_title(title)
16855 });
16856 let existing = workspace.active_pane().update(cx, |pane, cx| {
16857 pane.items()
16858 .filter_map(|item| item.downcast::<Editor>())
16859 .find(|editor| {
16860 editor
16861 .read(cx)
16862 .lookup_key
16863 .as_ref()
16864 .and_then(|it| {
16865 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16866 })
16867 .is_some_and(|it| *it == key)
16868 })
16869 });
16870 let editor = existing.unwrap_or_else(|| {
16871 cx.new(|cx| {
16872 let mut editor = Editor::for_multibuffer(
16873 excerpt_buffer,
16874 Some(workspace.project().clone()),
16875 window,
16876 cx,
16877 );
16878 editor.lookup_key = Some(Box::new(key));
16879 editor
16880 })
16881 });
16882 editor.update(cx, |editor, cx| {
16883 match multibuffer_selection_mode {
16884 MultibufferSelectionMode::First => {
16885 if let Some(first_range) = ranges.first() {
16886 editor.change_selections(
16887 SelectionEffects::no_scroll(),
16888 window,
16889 cx,
16890 |selections| {
16891 selections.clear_disjoint();
16892 selections
16893 .select_anchor_ranges(std::iter::once(first_range.clone()));
16894 },
16895 );
16896 }
16897 editor.highlight_background::<Self>(
16898 &ranges,
16899 |theme| theme.colors().editor_highlighted_line_background,
16900 cx,
16901 );
16902 }
16903 MultibufferSelectionMode::All => {
16904 editor.change_selections(
16905 SelectionEffects::no_scroll(),
16906 window,
16907 cx,
16908 |selections| {
16909 selections.clear_disjoint();
16910 selections.select_anchor_ranges(ranges);
16911 },
16912 );
16913 }
16914 }
16915 editor.register_buffers_with_language_servers(cx);
16916 });
16917
16918 let item = Box::new(editor);
16919 let item_id = item.item_id();
16920
16921 if split {
16922 let pane = workspace.adjacent_pane(window, cx);
16923 workspace.add_item(pane, item, None, true, true, window, cx);
16924 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16925 let (preview_item_id, preview_item_idx) =
16926 workspace.active_pane().read_with(cx, |pane, _| {
16927 (pane.preview_item_id(), pane.preview_item_idx())
16928 });
16929
16930 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16931
16932 if let Some(preview_item_id) = preview_item_id {
16933 workspace.active_pane().update(cx, |pane, cx| {
16934 pane.remove_item(preview_item_id, false, false, window, cx);
16935 });
16936 }
16937 } else {
16938 workspace.add_item_to_active_pane(item, None, true, window, cx);
16939 }
16940 workspace.active_pane().update(cx, |pane, cx| {
16941 pane.set_preview_item_id(Some(item_id), cx);
16942 });
16943 }
16944
16945 pub fn rename(
16946 &mut self,
16947 _: &Rename,
16948 window: &mut Window,
16949 cx: &mut Context<Self>,
16950 ) -> Option<Task<Result<()>>> {
16951 use language::ToOffset as _;
16952
16953 let provider = self.semantics_provider.clone()?;
16954 let selection = self.selections.newest_anchor().clone();
16955 let (cursor_buffer, cursor_buffer_position) = self
16956 .buffer
16957 .read(cx)
16958 .text_anchor_for_position(selection.head(), cx)?;
16959 let (tail_buffer, cursor_buffer_position_end) = self
16960 .buffer
16961 .read(cx)
16962 .text_anchor_for_position(selection.tail(), cx)?;
16963 if tail_buffer != cursor_buffer {
16964 return None;
16965 }
16966
16967 let snapshot = cursor_buffer.read(cx).snapshot();
16968 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16969 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16970 let prepare_rename = provider
16971 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16972 .unwrap_or_else(|| Task::ready(Ok(None)));
16973 drop(snapshot);
16974
16975 Some(cx.spawn_in(window, async move |this, cx| {
16976 let rename_range = if let Some(range) = prepare_rename.await? {
16977 Some(range)
16978 } else {
16979 this.update(cx, |this, cx| {
16980 let buffer = this.buffer.read(cx).snapshot(cx);
16981 let mut buffer_highlights = this
16982 .document_highlights_for_position(selection.head(), &buffer)
16983 .filter(|highlight| {
16984 highlight.start.excerpt_id == selection.head().excerpt_id
16985 && highlight.end.excerpt_id == selection.head().excerpt_id
16986 });
16987 buffer_highlights
16988 .next()
16989 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16990 })?
16991 };
16992 if let Some(rename_range) = rename_range {
16993 this.update_in(cx, |this, window, cx| {
16994 let snapshot = cursor_buffer.read(cx).snapshot();
16995 let rename_buffer_range = rename_range.to_offset(&snapshot);
16996 let cursor_offset_in_rename_range =
16997 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16998 let cursor_offset_in_rename_range_end =
16999 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17000
17001 this.take_rename(false, window, cx);
17002 let buffer = this.buffer.read(cx).read(cx);
17003 let cursor_offset = selection.head().to_offset(&buffer);
17004 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17005 let rename_end = rename_start + rename_buffer_range.len();
17006 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17007 let mut old_highlight_id = None;
17008 let old_name: Arc<str> = buffer
17009 .chunks(rename_start..rename_end, true)
17010 .map(|chunk| {
17011 if old_highlight_id.is_none() {
17012 old_highlight_id = chunk.syntax_highlight_id;
17013 }
17014 chunk.text
17015 })
17016 .collect::<String>()
17017 .into();
17018
17019 drop(buffer);
17020
17021 // Position the selection in the rename editor so that it matches the current selection.
17022 this.show_local_selections = false;
17023 let rename_editor = cx.new(|cx| {
17024 let mut editor = Editor::single_line(window, cx);
17025 editor.buffer.update(cx, |buffer, cx| {
17026 buffer.edit([(0..0, old_name.clone())], None, cx)
17027 });
17028 let rename_selection_range = match cursor_offset_in_rename_range
17029 .cmp(&cursor_offset_in_rename_range_end)
17030 {
17031 Ordering::Equal => {
17032 editor.select_all(&SelectAll, window, cx);
17033 return editor;
17034 }
17035 Ordering::Less => {
17036 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17037 }
17038 Ordering::Greater => {
17039 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17040 }
17041 };
17042 if rename_selection_range.end > old_name.len() {
17043 editor.select_all(&SelectAll, window, cx);
17044 } else {
17045 editor.change_selections(Default::default(), window, cx, |s| {
17046 s.select_ranges([rename_selection_range]);
17047 });
17048 }
17049 editor
17050 });
17051 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17052 if e == &EditorEvent::Focused {
17053 cx.emit(EditorEvent::FocusedIn)
17054 }
17055 })
17056 .detach();
17057
17058 let write_highlights =
17059 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17060 let read_highlights =
17061 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17062 let ranges = write_highlights
17063 .iter()
17064 .flat_map(|(_, ranges)| ranges.iter())
17065 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17066 .cloned()
17067 .collect();
17068
17069 this.highlight_text::<Rename>(
17070 ranges,
17071 HighlightStyle {
17072 fade_out: Some(0.6),
17073 ..Default::default()
17074 },
17075 cx,
17076 );
17077 let rename_focus_handle = rename_editor.focus_handle(cx);
17078 window.focus(&rename_focus_handle);
17079 let block_id = this.insert_blocks(
17080 [BlockProperties {
17081 style: BlockStyle::Flex,
17082 placement: BlockPlacement::Below(range.start),
17083 height: Some(1),
17084 render: Arc::new({
17085 let rename_editor = rename_editor.clone();
17086 move |cx: &mut BlockContext| {
17087 let mut text_style = cx.editor_style.text.clone();
17088 if let Some(highlight_style) = old_highlight_id
17089 .and_then(|h| h.style(&cx.editor_style.syntax))
17090 {
17091 text_style = text_style.highlight(highlight_style);
17092 }
17093 div()
17094 .block_mouse_except_scroll()
17095 .pl(cx.anchor_x)
17096 .child(EditorElement::new(
17097 &rename_editor,
17098 EditorStyle {
17099 background: cx.theme().system().transparent,
17100 local_player: cx.editor_style.local_player,
17101 text: text_style,
17102 scrollbar_width: cx.editor_style.scrollbar_width,
17103 syntax: cx.editor_style.syntax.clone(),
17104 status: cx.editor_style.status.clone(),
17105 inlay_hints_style: HighlightStyle {
17106 font_weight: Some(FontWeight::BOLD),
17107 ..make_inlay_hints_style(cx.app)
17108 },
17109 edit_prediction_styles: make_suggestion_styles(
17110 cx.app,
17111 ),
17112 ..EditorStyle::default()
17113 },
17114 ))
17115 .into_any_element()
17116 }
17117 }),
17118 priority: 0,
17119 }],
17120 Some(Autoscroll::fit()),
17121 cx,
17122 )[0];
17123 this.pending_rename = Some(RenameState {
17124 range,
17125 old_name,
17126 editor: rename_editor,
17127 block_id,
17128 });
17129 })?;
17130 }
17131
17132 Ok(())
17133 }))
17134 }
17135
17136 pub fn confirm_rename(
17137 &mut self,
17138 _: &ConfirmRename,
17139 window: &mut Window,
17140 cx: &mut Context<Self>,
17141 ) -> Option<Task<Result<()>>> {
17142 let rename = self.take_rename(false, window, cx)?;
17143 let workspace = self.workspace()?.downgrade();
17144 let (buffer, start) = self
17145 .buffer
17146 .read(cx)
17147 .text_anchor_for_position(rename.range.start, cx)?;
17148 let (end_buffer, _) = self
17149 .buffer
17150 .read(cx)
17151 .text_anchor_for_position(rename.range.end, cx)?;
17152 if buffer != end_buffer {
17153 return None;
17154 }
17155
17156 let old_name = rename.old_name;
17157 let new_name = rename.editor.read(cx).text(cx);
17158
17159 let rename = self.semantics_provider.as_ref()?.perform_rename(
17160 &buffer,
17161 start,
17162 new_name.clone(),
17163 cx,
17164 )?;
17165
17166 Some(cx.spawn_in(window, async move |editor, cx| {
17167 let project_transaction = rename.await?;
17168 Self::open_project_transaction(
17169 &editor,
17170 workspace,
17171 project_transaction,
17172 format!("Rename: {} → {}", old_name, new_name),
17173 cx,
17174 )
17175 .await?;
17176
17177 editor.update(cx, |editor, cx| {
17178 editor.refresh_document_highlights(cx);
17179 })?;
17180 Ok(())
17181 }))
17182 }
17183
17184 fn take_rename(
17185 &mut self,
17186 moving_cursor: bool,
17187 window: &mut Window,
17188 cx: &mut Context<Self>,
17189 ) -> Option<RenameState> {
17190 let rename = self.pending_rename.take()?;
17191 if rename.editor.focus_handle(cx).is_focused(window) {
17192 window.focus(&self.focus_handle);
17193 }
17194
17195 self.remove_blocks(
17196 [rename.block_id].into_iter().collect(),
17197 Some(Autoscroll::fit()),
17198 cx,
17199 );
17200 self.clear_highlights::<Rename>(cx);
17201 self.show_local_selections = true;
17202
17203 if moving_cursor {
17204 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17205 editor.selections.newest::<usize>(cx).head()
17206 });
17207
17208 // Update the selection to match the position of the selection inside
17209 // the rename editor.
17210 let snapshot = self.buffer.read(cx).read(cx);
17211 let rename_range = rename.range.to_offset(&snapshot);
17212 let cursor_in_editor = snapshot
17213 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17214 .min(rename_range.end);
17215 drop(snapshot);
17216
17217 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17218 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17219 });
17220 } else {
17221 self.refresh_document_highlights(cx);
17222 }
17223
17224 Some(rename)
17225 }
17226
17227 pub fn pending_rename(&self) -> Option<&RenameState> {
17228 self.pending_rename.as_ref()
17229 }
17230
17231 fn format(
17232 &mut self,
17233 _: &Format,
17234 window: &mut Window,
17235 cx: &mut Context<Self>,
17236 ) -> Option<Task<Result<()>>> {
17237 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17238
17239 let project = match &self.project {
17240 Some(project) => project.clone(),
17241 None => return None,
17242 };
17243
17244 Some(self.perform_format(
17245 project,
17246 FormatTrigger::Manual,
17247 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17248 window,
17249 cx,
17250 ))
17251 }
17252
17253 fn format_selections(
17254 &mut self,
17255 _: &FormatSelections,
17256 window: &mut Window,
17257 cx: &mut Context<Self>,
17258 ) -> Option<Task<Result<()>>> {
17259 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17260
17261 let project = match &self.project {
17262 Some(project) => project.clone(),
17263 None => return None,
17264 };
17265
17266 let ranges = self
17267 .selections
17268 .all_adjusted(cx)
17269 .into_iter()
17270 .map(|selection| selection.range())
17271 .collect_vec();
17272
17273 Some(self.perform_format(
17274 project,
17275 FormatTrigger::Manual,
17276 FormatTarget::Ranges(ranges),
17277 window,
17278 cx,
17279 ))
17280 }
17281
17282 fn perform_format(
17283 &mut self,
17284 project: Entity<Project>,
17285 trigger: FormatTrigger,
17286 target: FormatTarget,
17287 window: &mut Window,
17288 cx: &mut Context<Self>,
17289 ) -> Task<Result<()>> {
17290 let buffer = self.buffer.clone();
17291 let (buffers, target) = match target {
17292 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17293 FormatTarget::Ranges(selection_ranges) => {
17294 let multi_buffer = buffer.read(cx);
17295 let snapshot = multi_buffer.read(cx);
17296 let mut buffers = HashSet::default();
17297 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17298 BTreeMap::new();
17299 for selection_range in selection_ranges {
17300 for (buffer, buffer_range, _) in
17301 snapshot.range_to_buffer_ranges(selection_range)
17302 {
17303 let buffer_id = buffer.remote_id();
17304 let start = buffer.anchor_before(buffer_range.start);
17305 let end = buffer.anchor_after(buffer_range.end);
17306 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17307 buffer_id_to_ranges
17308 .entry(buffer_id)
17309 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17310 .or_insert_with(|| vec![start..end]);
17311 }
17312 }
17313 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17314 }
17315 };
17316
17317 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17318 let selections_prev = transaction_id_prev
17319 .and_then(|transaction_id_prev| {
17320 // default to selections as they were after the last edit, if we have them,
17321 // instead of how they are now.
17322 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17323 // will take you back to where you made the last edit, instead of staying where you scrolled
17324 self.selection_history
17325 .transaction(transaction_id_prev)
17326 .map(|t| t.0.clone())
17327 })
17328 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17329
17330 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17331 let format = project.update(cx, |project, cx| {
17332 project.format(buffers, target, true, trigger, cx)
17333 });
17334
17335 cx.spawn_in(window, async move |editor, cx| {
17336 let transaction = futures::select_biased! {
17337 transaction = format.log_err().fuse() => transaction,
17338 () = timeout => {
17339 log::warn!("timed out waiting for formatting");
17340 None
17341 }
17342 };
17343
17344 buffer
17345 .update(cx, |buffer, cx| {
17346 if let Some(transaction) = transaction
17347 && !buffer.is_singleton()
17348 {
17349 buffer.push_transaction(&transaction.0, cx);
17350 }
17351 cx.notify();
17352 })
17353 .ok();
17354
17355 if let Some(transaction_id_now) =
17356 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17357 {
17358 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17359 if has_new_transaction {
17360 _ = editor.update(cx, |editor, _| {
17361 editor
17362 .selection_history
17363 .insert_transaction(transaction_id_now, selections_prev);
17364 });
17365 }
17366 }
17367
17368 Ok(())
17369 })
17370 }
17371
17372 fn organize_imports(
17373 &mut self,
17374 _: &OrganizeImports,
17375 window: &mut Window,
17376 cx: &mut Context<Self>,
17377 ) -> Option<Task<Result<()>>> {
17378 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17379 let project = match &self.project {
17380 Some(project) => project.clone(),
17381 None => return None,
17382 };
17383 Some(self.perform_code_action_kind(
17384 project,
17385 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17386 window,
17387 cx,
17388 ))
17389 }
17390
17391 fn perform_code_action_kind(
17392 &mut self,
17393 project: Entity<Project>,
17394 kind: CodeActionKind,
17395 window: &mut Window,
17396 cx: &mut Context<Self>,
17397 ) -> Task<Result<()>> {
17398 let buffer = self.buffer.clone();
17399 let buffers = buffer.read(cx).all_buffers();
17400 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17401 let apply_action = project.update(cx, |project, cx| {
17402 project.apply_code_action_kind(buffers, kind, true, cx)
17403 });
17404 cx.spawn_in(window, async move |_, cx| {
17405 let transaction = futures::select_biased! {
17406 () = timeout => {
17407 log::warn!("timed out waiting for executing code action");
17408 None
17409 }
17410 transaction = apply_action.log_err().fuse() => transaction,
17411 };
17412 buffer
17413 .update(cx, |buffer, cx| {
17414 // check if we need this
17415 if let Some(transaction) = transaction
17416 && !buffer.is_singleton()
17417 {
17418 buffer.push_transaction(&transaction.0, cx);
17419 }
17420 cx.notify();
17421 })
17422 .ok();
17423 Ok(())
17424 })
17425 }
17426
17427 pub fn restart_language_server(
17428 &mut self,
17429 _: &RestartLanguageServer,
17430 _: &mut Window,
17431 cx: &mut Context<Self>,
17432 ) {
17433 if let Some(project) = self.project.clone() {
17434 self.buffer.update(cx, |multi_buffer, cx| {
17435 project.update(cx, |project, cx| {
17436 project.restart_language_servers_for_buffers(
17437 multi_buffer.all_buffers().into_iter().collect(),
17438 HashSet::default(),
17439 cx,
17440 );
17441 });
17442 })
17443 }
17444 }
17445
17446 pub fn stop_language_server(
17447 &mut self,
17448 _: &StopLanguageServer,
17449 _: &mut Window,
17450 cx: &mut Context<Self>,
17451 ) {
17452 if let Some(project) = self.project.clone() {
17453 self.buffer.update(cx, |multi_buffer, cx| {
17454 project.update(cx, |project, cx| {
17455 project.stop_language_servers_for_buffers(
17456 multi_buffer.all_buffers().into_iter().collect(),
17457 HashSet::default(),
17458 cx,
17459 );
17460 cx.emit(project::Event::RefreshInlayHints);
17461 });
17462 });
17463 }
17464 }
17465
17466 fn cancel_language_server_work(
17467 workspace: &mut Workspace,
17468 _: &actions::CancelLanguageServerWork,
17469 _: &mut Window,
17470 cx: &mut Context<Workspace>,
17471 ) {
17472 let project = workspace.project();
17473 let buffers = workspace
17474 .active_item(cx)
17475 .and_then(|item| item.act_as::<Editor>(cx))
17476 .map_or(HashSet::default(), |editor| {
17477 editor.read(cx).buffer.read(cx).all_buffers()
17478 });
17479 project.update(cx, |project, cx| {
17480 project.cancel_language_server_work_for_buffers(buffers, cx);
17481 });
17482 }
17483
17484 fn show_character_palette(
17485 &mut self,
17486 _: &ShowCharacterPalette,
17487 window: &mut Window,
17488 _: &mut Context<Self>,
17489 ) {
17490 window.show_character_palette();
17491 }
17492
17493 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17494 if !self.diagnostics_enabled() {
17495 return;
17496 }
17497
17498 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17499 let buffer = self.buffer.read(cx).snapshot(cx);
17500 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17501 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17502 let is_valid = buffer
17503 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17504 .any(|entry| {
17505 entry.diagnostic.is_primary
17506 && !entry.range.is_empty()
17507 && entry.range.start == primary_range_start
17508 && entry.diagnostic.message == active_diagnostics.active_message
17509 });
17510
17511 if !is_valid {
17512 self.dismiss_diagnostics(cx);
17513 }
17514 }
17515 }
17516
17517 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17518 match &self.active_diagnostics {
17519 ActiveDiagnostic::Group(group) => Some(group),
17520 _ => None,
17521 }
17522 }
17523
17524 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17525 if !self.diagnostics_enabled() {
17526 return;
17527 }
17528 self.dismiss_diagnostics(cx);
17529 self.active_diagnostics = ActiveDiagnostic::All;
17530 }
17531
17532 fn activate_diagnostics(
17533 &mut self,
17534 buffer_id: BufferId,
17535 diagnostic: DiagnosticEntryRef<'_, usize>,
17536 window: &mut Window,
17537 cx: &mut Context<Self>,
17538 ) {
17539 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17540 return;
17541 }
17542 self.dismiss_diagnostics(cx);
17543 let snapshot = self.snapshot(window, cx);
17544 let buffer = self.buffer.read(cx).snapshot(cx);
17545 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17546 return;
17547 };
17548
17549 let diagnostic_group = buffer
17550 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17551 .collect::<Vec<_>>();
17552
17553 let blocks =
17554 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17555
17556 let blocks = self.display_map.update(cx, |display_map, cx| {
17557 display_map.insert_blocks(blocks, cx).into_iter().collect()
17558 });
17559 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17560 active_range: buffer.anchor_before(diagnostic.range.start)
17561 ..buffer.anchor_after(diagnostic.range.end),
17562 active_message: diagnostic.diagnostic.message.clone(),
17563 group_id: diagnostic.diagnostic.group_id,
17564 blocks,
17565 });
17566 cx.notify();
17567 }
17568
17569 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17570 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17571 return;
17572 };
17573
17574 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17575 if let ActiveDiagnostic::Group(group) = prev {
17576 self.display_map.update(cx, |display_map, cx| {
17577 display_map.remove_blocks(group.blocks, cx);
17578 });
17579 cx.notify();
17580 }
17581 }
17582
17583 /// Disable inline diagnostics rendering for this editor.
17584 pub fn disable_inline_diagnostics(&mut self) {
17585 self.inline_diagnostics_enabled = false;
17586 self.inline_diagnostics_update = Task::ready(());
17587 self.inline_diagnostics.clear();
17588 }
17589
17590 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17591 self.diagnostics_enabled = false;
17592 self.dismiss_diagnostics(cx);
17593 self.inline_diagnostics_update = Task::ready(());
17594 self.inline_diagnostics.clear();
17595 }
17596
17597 pub fn disable_word_completions(&mut self) {
17598 self.word_completions_enabled = false;
17599 }
17600
17601 pub fn diagnostics_enabled(&self) -> bool {
17602 self.diagnostics_enabled && self.mode.is_full()
17603 }
17604
17605 pub fn inline_diagnostics_enabled(&self) -> bool {
17606 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17607 }
17608
17609 pub fn show_inline_diagnostics(&self) -> bool {
17610 self.show_inline_diagnostics
17611 }
17612
17613 pub fn toggle_inline_diagnostics(
17614 &mut self,
17615 _: &ToggleInlineDiagnostics,
17616 window: &mut Window,
17617 cx: &mut Context<Editor>,
17618 ) {
17619 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17620 self.refresh_inline_diagnostics(false, window, cx);
17621 }
17622
17623 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17624 self.diagnostics_max_severity = severity;
17625 self.display_map.update(cx, |display_map, _| {
17626 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17627 });
17628 }
17629
17630 pub fn toggle_diagnostics(
17631 &mut self,
17632 _: &ToggleDiagnostics,
17633 window: &mut Window,
17634 cx: &mut Context<Editor>,
17635 ) {
17636 if !self.diagnostics_enabled() {
17637 return;
17638 }
17639
17640 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17641 EditorSettings::get_global(cx)
17642 .diagnostics_max_severity
17643 .filter(|severity| severity != &DiagnosticSeverity::Off)
17644 .unwrap_or(DiagnosticSeverity::Hint)
17645 } else {
17646 DiagnosticSeverity::Off
17647 };
17648 self.set_max_diagnostics_severity(new_severity, cx);
17649 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17650 self.active_diagnostics = ActiveDiagnostic::None;
17651 self.inline_diagnostics_update = Task::ready(());
17652 self.inline_diagnostics.clear();
17653 } else {
17654 self.refresh_inline_diagnostics(false, window, cx);
17655 }
17656
17657 cx.notify();
17658 }
17659
17660 pub fn toggle_minimap(
17661 &mut self,
17662 _: &ToggleMinimap,
17663 window: &mut Window,
17664 cx: &mut Context<Editor>,
17665 ) {
17666 if self.supports_minimap(cx) {
17667 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17668 }
17669 }
17670
17671 fn refresh_inline_diagnostics(
17672 &mut self,
17673 debounce: bool,
17674 window: &mut Window,
17675 cx: &mut Context<Self>,
17676 ) {
17677 let max_severity = ProjectSettings::get_global(cx)
17678 .diagnostics
17679 .inline
17680 .max_severity
17681 .unwrap_or(self.diagnostics_max_severity);
17682
17683 if !self.inline_diagnostics_enabled()
17684 || !self.show_inline_diagnostics
17685 || max_severity == DiagnosticSeverity::Off
17686 {
17687 self.inline_diagnostics_update = Task::ready(());
17688 self.inline_diagnostics.clear();
17689 return;
17690 }
17691
17692 let debounce_ms = ProjectSettings::get_global(cx)
17693 .diagnostics
17694 .inline
17695 .update_debounce_ms;
17696 let debounce = if debounce && debounce_ms > 0 {
17697 Some(Duration::from_millis(debounce_ms))
17698 } else {
17699 None
17700 };
17701 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17702 if let Some(debounce) = debounce {
17703 cx.background_executor().timer(debounce).await;
17704 }
17705 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17706 editor
17707 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17708 .ok()
17709 }) else {
17710 return;
17711 };
17712
17713 let new_inline_diagnostics = cx
17714 .background_spawn(async move {
17715 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17716 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17717 let message = diagnostic_entry
17718 .diagnostic
17719 .message
17720 .split_once('\n')
17721 .map(|(line, _)| line)
17722 .map(SharedString::new)
17723 .unwrap_or_else(|| {
17724 SharedString::new(&*diagnostic_entry.diagnostic.message)
17725 });
17726 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17727 let (Ok(i) | Err(i)) = inline_diagnostics
17728 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17729 inline_diagnostics.insert(
17730 i,
17731 (
17732 start_anchor,
17733 InlineDiagnostic {
17734 message,
17735 group_id: diagnostic_entry.diagnostic.group_id,
17736 start: diagnostic_entry.range.start.to_point(&snapshot),
17737 is_primary: diagnostic_entry.diagnostic.is_primary,
17738 severity: diagnostic_entry.diagnostic.severity,
17739 },
17740 ),
17741 );
17742 }
17743 inline_diagnostics
17744 })
17745 .await;
17746
17747 editor
17748 .update(cx, |editor, cx| {
17749 editor.inline_diagnostics = new_inline_diagnostics;
17750 cx.notify();
17751 })
17752 .ok();
17753 });
17754 }
17755
17756 fn pull_diagnostics(
17757 &mut self,
17758 buffer_id: Option<BufferId>,
17759 window: &Window,
17760 cx: &mut Context<Self>,
17761 ) -> Option<()> {
17762 if !self.mode().is_full() {
17763 return None;
17764 }
17765 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17766 .diagnostics
17767 .lsp_pull_diagnostics;
17768 if !pull_diagnostics_settings.enabled {
17769 return None;
17770 }
17771 let project = self.project()?.downgrade();
17772 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17773 let mut buffers = self.buffer.read(cx).all_buffers();
17774 if let Some(buffer_id) = buffer_id {
17775 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17776 }
17777
17778 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17779 cx.background_executor().timer(debounce).await;
17780
17781 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17782 buffers
17783 .into_iter()
17784 .filter_map(|buffer| {
17785 project
17786 .update(cx, |project, cx| {
17787 project.lsp_store().update(cx, |lsp_store, cx| {
17788 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17789 })
17790 })
17791 .ok()
17792 })
17793 .collect::<FuturesUnordered<_>>()
17794 }) else {
17795 return;
17796 };
17797
17798 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17799 match pull_task {
17800 Ok(()) => {
17801 if editor
17802 .update_in(cx, |editor, window, cx| {
17803 editor.update_diagnostics_state(window, cx);
17804 })
17805 .is_err()
17806 {
17807 return;
17808 }
17809 }
17810 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17811 }
17812 }
17813 });
17814
17815 Some(())
17816 }
17817
17818 pub fn set_selections_from_remote(
17819 &mut self,
17820 selections: Vec<Selection<Anchor>>,
17821 pending_selection: Option<Selection<Anchor>>,
17822 window: &mut Window,
17823 cx: &mut Context<Self>,
17824 ) {
17825 let old_cursor_position = self.selections.newest_anchor().head();
17826 self.selections.change_with(cx, |s| {
17827 s.select_anchors(selections);
17828 if let Some(pending_selection) = pending_selection {
17829 s.set_pending(pending_selection, SelectMode::Character);
17830 } else {
17831 s.clear_pending();
17832 }
17833 });
17834 self.selections_did_change(
17835 false,
17836 &old_cursor_position,
17837 SelectionEffects::default(),
17838 window,
17839 cx,
17840 );
17841 }
17842
17843 pub fn transact(
17844 &mut self,
17845 window: &mut Window,
17846 cx: &mut Context<Self>,
17847 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17848 ) -> Option<TransactionId> {
17849 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17850 this.start_transaction_at(Instant::now(), window, cx);
17851 update(this, window, cx);
17852 this.end_transaction_at(Instant::now(), cx)
17853 })
17854 }
17855
17856 pub fn start_transaction_at(
17857 &mut self,
17858 now: Instant,
17859 window: &mut Window,
17860 cx: &mut Context<Self>,
17861 ) -> Option<TransactionId> {
17862 self.end_selection(window, cx);
17863 if let Some(tx_id) = self
17864 .buffer
17865 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17866 {
17867 self.selection_history
17868 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17869 cx.emit(EditorEvent::TransactionBegun {
17870 transaction_id: tx_id,
17871 });
17872 Some(tx_id)
17873 } else {
17874 None
17875 }
17876 }
17877
17878 pub fn end_transaction_at(
17879 &mut self,
17880 now: Instant,
17881 cx: &mut Context<Self>,
17882 ) -> Option<TransactionId> {
17883 if let Some(transaction_id) = self
17884 .buffer
17885 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17886 {
17887 if let Some((_, end_selections)) =
17888 self.selection_history.transaction_mut(transaction_id)
17889 {
17890 *end_selections = Some(self.selections.disjoint_anchors_arc());
17891 } else {
17892 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17893 }
17894
17895 cx.emit(EditorEvent::Edited { transaction_id });
17896 Some(transaction_id)
17897 } else {
17898 None
17899 }
17900 }
17901
17902 pub fn modify_transaction_selection_history(
17903 &mut self,
17904 transaction_id: TransactionId,
17905 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17906 ) -> bool {
17907 self.selection_history
17908 .transaction_mut(transaction_id)
17909 .map(modify)
17910 .is_some()
17911 }
17912
17913 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17914 if self.selection_mark_mode {
17915 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17916 s.move_with(|_, sel| {
17917 sel.collapse_to(sel.head(), SelectionGoal::None);
17918 });
17919 })
17920 }
17921 self.selection_mark_mode = true;
17922 cx.notify();
17923 }
17924
17925 pub fn swap_selection_ends(
17926 &mut self,
17927 _: &actions::SwapSelectionEnds,
17928 window: &mut Window,
17929 cx: &mut Context<Self>,
17930 ) {
17931 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17932 s.move_with(|_, sel| {
17933 if sel.start != sel.end {
17934 sel.reversed = !sel.reversed
17935 }
17936 });
17937 });
17938 self.request_autoscroll(Autoscroll::newest(), cx);
17939 cx.notify();
17940 }
17941
17942 pub fn toggle_focus(
17943 workspace: &mut Workspace,
17944 _: &actions::ToggleFocus,
17945 window: &mut Window,
17946 cx: &mut Context<Workspace>,
17947 ) {
17948 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17949 return;
17950 };
17951 workspace.activate_item(&item, true, true, window, cx);
17952 }
17953
17954 pub fn toggle_fold(
17955 &mut self,
17956 _: &actions::ToggleFold,
17957 window: &mut Window,
17958 cx: &mut Context<Self>,
17959 ) {
17960 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
17961 let selection = self.selections.newest::<Point>(cx);
17962
17963 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17964 let range = if selection.is_empty() {
17965 let point = selection.head().to_display_point(&display_map);
17966 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17967 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17968 .to_point(&display_map);
17969 start..end
17970 } else {
17971 selection.range()
17972 };
17973 if display_map.folds_in_range(range).next().is_some() {
17974 self.unfold_lines(&Default::default(), window, cx)
17975 } else {
17976 self.fold(&Default::default(), window, cx)
17977 }
17978 } else {
17979 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17980 let buffer_ids: HashSet<_> = self
17981 .selections
17982 .disjoint_anchor_ranges()
17983 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17984 .collect();
17985
17986 let should_unfold = buffer_ids
17987 .iter()
17988 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17989
17990 for buffer_id in buffer_ids {
17991 if should_unfold {
17992 self.unfold_buffer(buffer_id, cx);
17993 } else {
17994 self.fold_buffer(buffer_id, cx);
17995 }
17996 }
17997 }
17998 }
17999
18000 pub fn toggle_fold_recursive(
18001 &mut self,
18002 _: &actions::ToggleFoldRecursive,
18003 window: &mut Window,
18004 cx: &mut Context<Self>,
18005 ) {
18006 let selection = self.selections.newest::<Point>(cx);
18007
18008 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18009 let range = if selection.is_empty() {
18010 let point = selection.head().to_display_point(&display_map);
18011 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18012 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18013 .to_point(&display_map);
18014 start..end
18015 } else {
18016 selection.range()
18017 };
18018 if display_map.folds_in_range(range).next().is_some() {
18019 self.unfold_recursive(&Default::default(), window, cx)
18020 } else {
18021 self.fold_recursive(&Default::default(), window, cx)
18022 }
18023 }
18024
18025 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18026 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18027 let mut to_fold = Vec::new();
18028 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18029 let selections = self.selections.all_adjusted(cx);
18030
18031 for selection in selections {
18032 let range = selection.range().sorted();
18033 let buffer_start_row = range.start.row;
18034
18035 if range.start.row != range.end.row {
18036 let mut found = false;
18037 let mut row = range.start.row;
18038 while row <= range.end.row {
18039 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18040 {
18041 found = true;
18042 row = crease.range().end.row + 1;
18043 to_fold.push(crease);
18044 } else {
18045 row += 1
18046 }
18047 }
18048 if found {
18049 continue;
18050 }
18051 }
18052
18053 for row in (0..=range.start.row).rev() {
18054 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18055 && crease.range().end.row >= buffer_start_row
18056 {
18057 to_fold.push(crease);
18058 if row <= range.start.row {
18059 break;
18060 }
18061 }
18062 }
18063 }
18064
18065 self.fold_creases(to_fold, true, window, cx);
18066 } else {
18067 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18068 let buffer_ids = self
18069 .selections
18070 .disjoint_anchor_ranges()
18071 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18072 .collect::<HashSet<_>>();
18073 for buffer_id in buffer_ids {
18074 self.fold_buffer(buffer_id, cx);
18075 }
18076 }
18077 }
18078
18079 pub fn toggle_fold_all(
18080 &mut self,
18081 _: &actions::ToggleFoldAll,
18082 window: &mut Window,
18083 cx: &mut Context<Self>,
18084 ) {
18085 if self.buffer.read(cx).is_singleton() {
18086 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18087 let has_folds = display_map
18088 .folds_in_range(0..display_map.buffer_snapshot().len())
18089 .next()
18090 .is_some();
18091
18092 if has_folds {
18093 self.unfold_all(&actions::UnfoldAll, window, cx);
18094 } else {
18095 self.fold_all(&actions::FoldAll, window, cx);
18096 }
18097 } else {
18098 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18099 let should_unfold = buffer_ids
18100 .iter()
18101 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18102
18103 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18104 editor
18105 .update_in(cx, |editor, _, cx| {
18106 for buffer_id in buffer_ids {
18107 if should_unfold {
18108 editor.unfold_buffer(buffer_id, cx);
18109 } else {
18110 editor.fold_buffer(buffer_id, cx);
18111 }
18112 }
18113 })
18114 .ok();
18115 });
18116 }
18117 }
18118
18119 fn fold_at_level(
18120 &mut self,
18121 fold_at: &FoldAtLevel,
18122 window: &mut Window,
18123 cx: &mut Context<Self>,
18124 ) {
18125 if !self.buffer.read(cx).is_singleton() {
18126 return;
18127 }
18128
18129 let fold_at_level = fold_at.0;
18130 let snapshot = self.buffer.read(cx).snapshot(cx);
18131 let mut to_fold = Vec::new();
18132 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18133
18134 let row_ranges_to_keep: Vec<Range<u32>> = self
18135 .selections
18136 .all::<Point>(cx)
18137 .into_iter()
18138 .map(|sel| sel.start.row..sel.end.row)
18139 .collect();
18140
18141 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18142 while start_row < end_row {
18143 match self
18144 .snapshot(window, cx)
18145 .crease_for_buffer_row(MultiBufferRow(start_row))
18146 {
18147 Some(crease) => {
18148 let nested_start_row = crease.range().start.row + 1;
18149 let nested_end_row = crease.range().end.row;
18150
18151 if current_level < fold_at_level {
18152 stack.push((nested_start_row, nested_end_row, current_level + 1));
18153 } else if current_level == fold_at_level {
18154 // Fold iff there is no selection completely contained within the fold region
18155 if !row_ranges_to_keep.iter().any(|selection| {
18156 selection.end >= nested_start_row
18157 && selection.start <= nested_end_row
18158 }) {
18159 to_fold.push(crease);
18160 }
18161 }
18162
18163 start_row = nested_end_row + 1;
18164 }
18165 None => start_row += 1,
18166 }
18167 }
18168 }
18169
18170 self.fold_creases(to_fold, true, window, cx);
18171 }
18172
18173 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18174 if self.buffer.read(cx).is_singleton() {
18175 let mut fold_ranges = Vec::new();
18176 let snapshot = self.buffer.read(cx).snapshot(cx);
18177
18178 for row in 0..snapshot.max_row().0 {
18179 if let Some(foldable_range) = self
18180 .snapshot(window, cx)
18181 .crease_for_buffer_row(MultiBufferRow(row))
18182 {
18183 fold_ranges.push(foldable_range);
18184 }
18185 }
18186
18187 self.fold_creases(fold_ranges, true, window, cx);
18188 } else {
18189 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18190 editor
18191 .update_in(cx, |editor, _, cx| {
18192 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18193 editor.fold_buffer(buffer_id, cx);
18194 }
18195 })
18196 .ok();
18197 });
18198 }
18199 }
18200
18201 pub fn fold_function_bodies(
18202 &mut self,
18203 _: &actions::FoldFunctionBodies,
18204 window: &mut Window,
18205 cx: &mut Context<Self>,
18206 ) {
18207 let snapshot = self.buffer.read(cx).snapshot(cx);
18208
18209 let ranges = snapshot
18210 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18211 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18212 .collect::<Vec<_>>();
18213
18214 let creases = ranges
18215 .into_iter()
18216 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18217 .collect();
18218
18219 self.fold_creases(creases, true, window, cx);
18220 }
18221
18222 pub fn fold_recursive(
18223 &mut self,
18224 _: &actions::FoldRecursive,
18225 window: &mut Window,
18226 cx: &mut Context<Self>,
18227 ) {
18228 let mut to_fold = Vec::new();
18229 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18230 let selections = self.selections.all_adjusted(cx);
18231
18232 for selection in selections {
18233 let range = selection.range().sorted();
18234 let buffer_start_row = range.start.row;
18235
18236 if range.start.row != range.end.row {
18237 let mut found = false;
18238 for row in range.start.row..=range.end.row {
18239 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18240 found = true;
18241 to_fold.push(crease);
18242 }
18243 }
18244 if found {
18245 continue;
18246 }
18247 }
18248
18249 for row in (0..=range.start.row).rev() {
18250 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18251 if crease.range().end.row >= buffer_start_row {
18252 to_fold.push(crease);
18253 } else {
18254 break;
18255 }
18256 }
18257 }
18258 }
18259
18260 self.fold_creases(to_fold, true, window, cx);
18261 }
18262
18263 pub fn fold_at(
18264 &mut self,
18265 buffer_row: MultiBufferRow,
18266 window: &mut Window,
18267 cx: &mut Context<Self>,
18268 ) {
18269 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18270
18271 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18272 let autoscroll = self
18273 .selections
18274 .all::<Point>(cx)
18275 .iter()
18276 .any(|selection| crease.range().overlaps(&selection.range()));
18277
18278 self.fold_creases(vec![crease], autoscroll, window, cx);
18279 }
18280 }
18281
18282 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18283 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18284 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18285 let buffer = display_map.buffer_snapshot();
18286 let selections = self.selections.all::<Point>(cx);
18287 let ranges = selections
18288 .iter()
18289 .map(|s| {
18290 let range = s.display_range(&display_map).sorted();
18291 let mut start = range.start.to_point(&display_map);
18292 let mut end = range.end.to_point(&display_map);
18293 start.column = 0;
18294 end.column = buffer.line_len(MultiBufferRow(end.row));
18295 start..end
18296 })
18297 .collect::<Vec<_>>();
18298
18299 self.unfold_ranges(&ranges, true, true, cx);
18300 } else {
18301 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18302 let buffer_ids = self
18303 .selections
18304 .disjoint_anchor_ranges()
18305 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18306 .collect::<HashSet<_>>();
18307 for buffer_id in buffer_ids {
18308 self.unfold_buffer(buffer_id, cx);
18309 }
18310 }
18311 }
18312
18313 pub fn unfold_recursive(
18314 &mut self,
18315 _: &UnfoldRecursive,
18316 _window: &mut Window,
18317 cx: &mut Context<Self>,
18318 ) {
18319 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18320 let selections = self.selections.all::<Point>(cx);
18321 let ranges = selections
18322 .iter()
18323 .map(|s| {
18324 let mut range = s.display_range(&display_map).sorted();
18325 *range.start.column_mut() = 0;
18326 *range.end.column_mut() = display_map.line_len(range.end.row());
18327 let start = range.start.to_point(&display_map);
18328 let end = range.end.to_point(&display_map);
18329 start..end
18330 })
18331 .collect::<Vec<_>>();
18332
18333 self.unfold_ranges(&ranges, true, true, cx);
18334 }
18335
18336 pub fn unfold_at(
18337 &mut self,
18338 buffer_row: MultiBufferRow,
18339 _window: &mut Window,
18340 cx: &mut Context<Self>,
18341 ) {
18342 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18343
18344 let intersection_range = Point::new(buffer_row.0, 0)
18345 ..Point::new(
18346 buffer_row.0,
18347 display_map.buffer_snapshot().line_len(buffer_row),
18348 );
18349
18350 let autoscroll = self
18351 .selections
18352 .all::<Point>(cx)
18353 .iter()
18354 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18355
18356 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18357 }
18358
18359 pub fn unfold_all(
18360 &mut self,
18361 _: &actions::UnfoldAll,
18362 _window: &mut Window,
18363 cx: &mut Context<Self>,
18364 ) {
18365 if self.buffer.read(cx).is_singleton() {
18366 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18367 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18368 } else {
18369 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18370 editor
18371 .update(cx, |editor, cx| {
18372 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18373 editor.unfold_buffer(buffer_id, cx);
18374 }
18375 })
18376 .ok();
18377 });
18378 }
18379 }
18380
18381 pub fn fold_selected_ranges(
18382 &mut self,
18383 _: &FoldSelectedRanges,
18384 window: &mut Window,
18385 cx: &mut Context<Self>,
18386 ) {
18387 let selections = self.selections.all_adjusted(cx);
18388 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18389 let ranges = selections
18390 .into_iter()
18391 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18392 .collect::<Vec<_>>();
18393 self.fold_creases(ranges, true, window, cx);
18394 }
18395
18396 pub fn fold_ranges<T: ToOffset + Clone>(
18397 &mut self,
18398 ranges: Vec<Range<T>>,
18399 auto_scroll: bool,
18400 window: &mut Window,
18401 cx: &mut Context<Self>,
18402 ) {
18403 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18404 let ranges = ranges
18405 .into_iter()
18406 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18407 .collect::<Vec<_>>();
18408 self.fold_creases(ranges, auto_scroll, window, cx);
18409 }
18410
18411 pub fn fold_creases<T: ToOffset + Clone>(
18412 &mut self,
18413 creases: Vec<Crease<T>>,
18414 auto_scroll: bool,
18415 _window: &mut Window,
18416 cx: &mut Context<Self>,
18417 ) {
18418 if creases.is_empty() {
18419 return;
18420 }
18421
18422 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18423
18424 if auto_scroll {
18425 self.request_autoscroll(Autoscroll::fit(), cx);
18426 }
18427
18428 cx.notify();
18429
18430 self.scrollbar_marker_state.dirty = true;
18431 self.folds_did_change(cx);
18432 }
18433
18434 /// Removes any folds whose ranges intersect any of the given ranges.
18435 pub fn unfold_ranges<T: ToOffset + Clone>(
18436 &mut self,
18437 ranges: &[Range<T>],
18438 inclusive: bool,
18439 auto_scroll: bool,
18440 cx: &mut Context<Self>,
18441 ) {
18442 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18443 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18444 });
18445 self.folds_did_change(cx);
18446 }
18447
18448 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18449 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18450 return;
18451 }
18452 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18453 self.display_map.update(cx, |display_map, cx| {
18454 display_map.fold_buffers([buffer_id], cx)
18455 });
18456 cx.emit(EditorEvent::BufferFoldToggled {
18457 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18458 folded: true,
18459 });
18460 cx.notify();
18461 }
18462
18463 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18464 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18465 return;
18466 }
18467 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18468 self.display_map.update(cx, |display_map, cx| {
18469 display_map.unfold_buffers([buffer_id], cx);
18470 });
18471 cx.emit(EditorEvent::BufferFoldToggled {
18472 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18473 folded: false,
18474 });
18475 cx.notify();
18476 }
18477
18478 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18479 self.display_map.read(cx).is_buffer_folded(buffer)
18480 }
18481
18482 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18483 self.display_map.read(cx).folded_buffers()
18484 }
18485
18486 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18487 self.display_map.update(cx, |display_map, cx| {
18488 display_map.disable_header_for_buffer(buffer_id, cx);
18489 });
18490 cx.notify();
18491 }
18492
18493 /// Removes any folds with the given ranges.
18494 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18495 &mut self,
18496 ranges: &[Range<T>],
18497 type_id: TypeId,
18498 auto_scroll: bool,
18499 cx: &mut Context<Self>,
18500 ) {
18501 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18502 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18503 });
18504 self.folds_did_change(cx);
18505 }
18506
18507 fn remove_folds_with<T: ToOffset + Clone>(
18508 &mut self,
18509 ranges: &[Range<T>],
18510 auto_scroll: bool,
18511 cx: &mut Context<Self>,
18512 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18513 ) {
18514 if ranges.is_empty() {
18515 return;
18516 }
18517
18518 let mut buffers_affected = HashSet::default();
18519 let multi_buffer = self.buffer().read(cx);
18520 for range in ranges {
18521 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18522 buffers_affected.insert(buffer.read(cx).remote_id());
18523 };
18524 }
18525
18526 self.display_map.update(cx, update);
18527
18528 if auto_scroll {
18529 self.request_autoscroll(Autoscroll::fit(), cx);
18530 }
18531
18532 cx.notify();
18533 self.scrollbar_marker_state.dirty = true;
18534 self.active_indent_guides_state.dirty = true;
18535 }
18536
18537 pub fn update_renderer_widths(
18538 &mut self,
18539 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18540 cx: &mut Context<Self>,
18541 ) -> bool {
18542 self.display_map
18543 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18544 }
18545
18546 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18547 self.display_map.read(cx).fold_placeholder.clone()
18548 }
18549
18550 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18551 self.buffer.update(cx, |buffer, cx| {
18552 buffer.set_all_diff_hunks_expanded(cx);
18553 });
18554 }
18555
18556 pub fn expand_all_diff_hunks(
18557 &mut self,
18558 _: &ExpandAllDiffHunks,
18559 _window: &mut Window,
18560 cx: &mut Context<Self>,
18561 ) {
18562 self.buffer.update(cx, |buffer, cx| {
18563 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18564 });
18565 }
18566
18567 pub fn toggle_selected_diff_hunks(
18568 &mut self,
18569 _: &ToggleSelectedDiffHunks,
18570 _window: &mut Window,
18571 cx: &mut Context<Self>,
18572 ) {
18573 let ranges: Vec<_> = self
18574 .selections
18575 .disjoint_anchors()
18576 .iter()
18577 .map(|s| s.range())
18578 .collect();
18579 self.toggle_diff_hunks_in_ranges(ranges, cx);
18580 }
18581
18582 pub fn diff_hunks_in_ranges<'a>(
18583 &'a self,
18584 ranges: &'a [Range<Anchor>],
18585 buffer: &'a MultiBufferSnapshot,
18586 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18587 ranges.iter().flat_map(move |range| {
18588 let end_excerpt_id = range.end.excerpt_id;
18589 let range = range.to_point(buffer);
18590 let mut peek_end = range.end;
18591 if range.end.row < buffer.max_row().0 {
18592 peek_end = Point::new(range.end.row + 1, 0);
18593 }
18594 buffer
18595 .diff_hunks_in_range(range.start..peek_end)
18596 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18597 })
18598 }
18599
18600 pub fn has_stageable_diff_hunks_in_ranges(
18601 &self,
18602 ranges: &[Range<Anchor>],
18603 snapshot: &MultiBufferSnapshot,
18604 ) -> bool {
18605 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18606 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18607 }
18608
18609 pub fn toggle_staged_selected_diff_hunks(
18610 &mut self,
18611 _: &::git::ToggleStaged,
18612 _: &mut Window,
18613 cx: &mut Context<Self>,
18614 ) {
18615 let snapshot = self.buffer.read(cx).snapshot(cx);
18616 let ranges: Vec<_> = self
18617 .selections
18618 .disjoint_anchors()
18619 .iter()
18620 .map(|s| s.range())
18621 .collect();
18622 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18623 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18624 }
18625
18626 pub fn set_render_diff_hunk_controls(
18627 &mut self,
18628 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18629 cx: &mut Context<Self>,
18630 ) {
18631 self.render_diff_hunk_controls = render_diff_hunk_controls;
18632 cx.notify();
18633 }
18634
18635 pub fn stage_and_next(
18636 &mut self,
18637 _: &::git::StageAndNext,
18638 window: &mut Window,
18639 cx: &mut Context<Self>,
18640 ) {
18641 self.do_stage_or_unstage_and_next(true, window, cx);
18642 }
18643
18644 pub fn unstage_and_next(
18645 &mut self,
18646 _: &::git::UnstageAndNext,
18647 window: &mut Window,
18648 cx: &mut Context<Self>,
18649 ) {
18650 self.do_stage_or_unstage_and_next(false, window, cx);
18651 }
18652
18653 pub fn stage_or_unstage_diff_hunks(
18654 &mut self,
18655 stage: bool,
18656 ranges: Vec<Range<Anchor>>,
18657 cx: &mut Context<Self>,
18658 ) {
18659 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18660 cx.spawn(async move |this, cx| {
18661 task.await?;
18662 this.update(cx, |this, cx| {
18663 let snapshot = this.buffer.read(cx).snapshot(cx);
18664 let chunk_by = this
18665 .diff_hunks_in_ranges(&ranges, &snapshot)
18666 .chunk_by(|hunk| hunk.buffer_id);
18667 for (buffer_id, hunks) in &chunk_by {
18668 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18669 }
18670 })
18671 })
18672 .detach_and_log_err(cx);
18673 }
18674
18675 fn save_buffers_for_ranges_if_needed(
18676 &mut self,
18677 ranges: &[Range<Anchor>],
18678 cx: &mut Context<Editor>,
18679 ) -> Task<Result<()>> {
18680 let multibuffer = self.buffer.read(cx);
18681 let snapshot = multibuffer.read(cx);
18682 let buffer_ids: HashSet<_> = ranges
18683 .iter()
18684 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18685 .collect();
18686 drop(snapshot);
18687
18688 let mut buffers = HashSet::default();
18689 for buffer_id in buffer_ids {
18690 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18691 let buffer = buffer_entity.read(cx);
18692 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18693 {
18694 buffers.insert(buffer_entity);
18695 }
18696 }
18697 }
18698
18699 if let Some(project) = &self.project {
18700 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18701 } else {
18702 Task::ready(Ok(()))
18703 }
18704 }
18705
18706 fn do_stage_or_unstage_and_next(
18707 &mut self,
18708 stage: bool,
18709 window: &mut Window,
18710 cx: &mut Context<Self>,
18711 ) {
18712 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18713
18714 if ranges.iter().any(|range| range.start != range.end) {
18715 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18716 return;
18717 }
18718
18719 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18720 let snapshot = self.snapshot(window, cx);
18721 let position = self.selections.newest::<Point>(cx).head();
18722 let mut row = snapshot
18723 .buffer_snapshot()
18724 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18725 .find(|hunk| hunk.row_range.start.0 > position.row)
18726 .map(|hunk| hunk.row_range.start);
18727
18728 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18729 // Outside of the project diff editor, wrap around to the beginning.
18730 if !all_diff_hunks_expanded {
18731 row = row.or_else(|| {
18732 snapshot
18733 .buffer_snapshot()
18734 .diff_hunks_in_range(Point::zero()..position)
18735 .find(|hunk| hunk.row_range.end.0 < position.row)
18736 .map(|hunk| hunk.row_range.start)
18737 });
18738 }
18739
18740 if let Some(row) = row {
18741 let destination = Point::new(row.0, 0);
18742 let autoscroll = Autoscroll::center();
18743
18744 self.unfold_ranges(&[destination..destination], false, false, cx);
18745 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18746 s.select_ranges([destination..destination]);
18747 });
18748 }
18749 }
18750
18751 fn do_stage_or_unstage(
18752 &self,
18753 stage: bool,
18754 buffer_id: BufferId,
18755 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18756 cx: &mut App,
18757 ) -> Option<()> {
18758 let project = self.project()?;
18759 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18760 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18761 let buffer_snapshot = buffer.read(cx).snapshot();
18762 let file_exists = buffer_snapshot
18763 .file()
18764 .is_some_and(|file| file.disk_state().exists());
18765 diff.update(cx, |diff, cx| {
18766 diff.stage_or_unstage_hunks(
18767 stage,
18768 &hunks
18769 .map(|hunk| buffer_diff::DiffHunk {
18770 buffer_range: hunk.buffer_range,
18771 diff_base_byte_range: hunk.diff_base_byte_range,
18772 secondary_status: hunk.secondary_status,
18773 range: Point::zero()..Point::zero(), // unused
18774 })
18775 .collect::<Vec<_>>(),
18776 &buffer_snapshot,
18777 file_exists,
18778 cx,
18779 )
18780 });
18781 None
18782 }
18783
18784 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18785 let ranges: Vec<_> = self
18786 .selections
18787 .disjoint_anchors()
18788 .iter()
18789 .map(|s| s.range())
18790 .collect();
18791 self.buffer
18792 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18793 }
18794
18795 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18796 self.buffer.update(cx, |buffer, cx| {
18797 let ranges = vec![Anchor::min()..Anchor::max()];
18798 if !buffer.all_diff_hunks_expanded()
18799 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18800 {
18801 buffer.collapse_diff_hunks(ranges, cx);
18802 true
18803 } else {
18804 false
18805 }
18806 })
18807 }
18808
18809 fn toggle_diff_hunks_in_ranges(
18810 &mut self,
18811 ranges: Vec<Range<Anchor>>,
18812 cx: &mut Context<Editor>,
18813 ) {
18814 self.buffer.update(cx, |buffer, cx| {
18815 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18816 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18817 })
18818 }
18819
18820 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18821 self.buffer.update(cx, |buffer, cx| {
18822 let snapshot = buffer.snapshot(cx);
18823 let excerpt_id = range.end.excerpt_id;
18824 let point_range = range.to_point(&snapshot);
18825 let expand = !buffer.single_hunk_is_expanded(range, cx);
18826 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18827 })
18828 }
18829
18830 pub(crate) fn apply_all_diff_hunks(
18831 &mut self,
18832 _: &ApplyAllDiffHunks,
18833 window: &mut Window,
18834 cx: &mut Context<Self>,
18835 ) {
18836 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18837
18838 let buffers = self.buffer.read(cx).all_buffers();
18839 for branch_buffer in buffers {
18840 branch_buffer.update(cx, |branch_buffer, cx| {
18841 branch_buffer.merge_into_base(Vec::new(), cx);
18842 });
18843 }
18844
18845 if let Some(project) = self.project.clone() {
18846 self.save(
18847 SaveOptions {
18848 format: true,
18849 autosave: false,
18850 },
18851 project,
18852 window,
18853 cx,
18854 )
18855 .detach_and_log_err(cx);
18856 }
18857 }
18858
18859 pub(crate) fn apply_selected_diff_hunks(
18860 &mut self,
18861 _: &ApplyDiffHunk,
18862 window: &mut Window,
18863 cx: &mut Context<Self>,
18864 ) {
18865 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18866 let snapshot = self.snapshot(window, cx);
18867 let hunks = snapshot.hunks_for_ranges(
18868 self.selections
18869 .all(cx)
18870 .into_iter()
18871 .map(|selection| selection.range()),
18872 );
18873 let mut ranges_by_buffer = HashMap::default();
18874 self.transact(window, cx, |editor, _window, cx| {
18875 for hunk in hunks {
18876 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18877 ranges_by_buffer
18878 .entry(buffer.clone())
18879 .or_insert_with(Vec::new)
18880 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18881 }
18882 }
18883
18884 for (buffer, ranges) in ranges_by_buffer {
18885 buffer.update(cx, |buffer, cx| {
18886 buffer.merge_into_base(ranges, cx);
18887 });
18888 }
18889 });
18890
18891 if let Some(project) = self.project.clone() {
18892 self.save(
18893 SaveOptions {
18894 format: true,
18895 autosave: false,
18896 },
18897 project,
18898 window,
18899 cx,
18900 )
18901 .detach_and_log_err(cx);
18902 }
18903 }
18904
18905 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18906 if hovered != self.gutter_hovered {
18907 self.gutter_hovered = hovered;
18908 cx.notify();
18909 }
18910 }
18911
18912 pub fn insert_blocks(
18913 &mut self,
18914 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18915 autoscroll: Option<Autoscroll>,
18916 cx: &mut Context<Self>,
18917 ) -> Vec<CustomBlockId> {
18918 let blocks = self
18919 .display_map
18920 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18921 if let Some(autoscroll) = autoscroll {
18922 self.request_autoscroll(autoscroll, cx);
18923 }
18924 cx.notify();
18925 blocks
18926 }
18927
18928 pub fn resize_blocks(
18929 &mut self,
18930 heights: HashMap<CustomBlockId, u32>,
18931 autoscroll: Option<Autoscroll>,
18932 cx: &mut Context<Self>,
18933 ) {
18934 self.display_map
18935 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18936 if let Some(autoscroll) = autoscroll {
18937 self.request_autoscroll(autoscroll, cx);
18938 }
18939 cx.notify();
18940 }
18941
18942 pub fn replace_blocks(
18943 &mut self,
18944 renderers: HashMap<CustomBlockId, RenderBlock>,
18945 autoscroll: Option<Autoscroll>,
18946 cx: &mut Context<Self>,
18947 ) {
18948 self.display_map
18949 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18950 if let Some(autoscroll) = autoscroll {
18951 self.request_autoscroll(autoscroll, cx);
18952 }
18953 cx.notify();
18954 }
18955
18956 pub fn remove_blocks(
18957 &mut self,
18958 block_ids: HashSet<CustomBlockId>,
18959 autoscroll: Option<Autoscroll>,
18960 cx: &mut Context<Self>,
18961 ) {
18962 self.display_map.update(cx, |display_map, cx| {
18963 display_map.remove_blocks(block_ids, cx)
18964 });
18965 if let Some(autoscroll) = autoscroll {
18966 self.request_autoscroll(autoscroll, cx);
18967 }
18968 cx.notify();
18969 }
18970
18971 pub fn row_for_block(
18972 &self,
18973 block_id: CustomBlockId,
18974 cx: &mut Context<Self>,
18975 ) -> Option<DisplayRow> {
18976 self.display_map
18977 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18978 }
18979
18980 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18981 self.focused_block = Some(focused_block);
18982 }
18983
18984 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18985 self.focused_block.take()
18986 }
18987
18988 pub fn insert_creases(
18989 &mut self,
18990 creases: impl IntoIterator<Item = Crease<Anchor>>,
18991 cx: &mut Context<Self>,
18992 ) -> Vec<CreaseId> {
18993 self.display_map
18994 .update(cx, |map, cx| map.insert_creases(creases, cx))
18995 }
18996
18997 pub fn remove_creases(
18998 &mut self,
18999 ids: impl IntoIterator<Item = CreaseId>,
19000 cx: &mut Context<Self>,
19001 ) -> Vec<(CreaseId, Range<Anchor>)> {
19002 self.display_map
19003 .update(cx, |map, cx| map.remove_creases(ids, cx))
19004 }
19005
19006 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19007 self.display_map
19008 .update(cx, |map, cx| map.snapshot(cx))
19009 .longest_row()
19010 }
19011
19012 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19013 self.display_map
19014 .update(cx, |map, cx| map.snapshot(cx))
19015 .max_point()
19016 }
19017
19018 pub fn text(&self, cx: &App) -> String {
19019 self.buffer.read(cx).read(cx).text()
19020 }
19021
19022 pub fn is_empty(&self, cx: &App) -> bool {
19023 self.buffer.read(cx).read(cx).is_empty()
19024 }
19025
19026 pub fn text_option(&self, cx: &App) -> Option<String> {
19027 let text = self.text(cx);
19028 let text = text.trim();
19029
19030 if text.is_empty() {
19031 return None;
19032 }
19033
19034 Some(text.to_string())
19035 }
19036
19037 pub fn set_text(
19038 &mut self,
19039 text: impl Into<Arc<str>>,
19040 window: &mut Window,
19041 cx: &mut Context<Self>,
19042 ) {
19043 self.transact(window, cx, |this, _, cx| {
19044 this.buffer
19045 .read(cx)
19046 .as_singleton()
19047 .expect("you can only call set_text on editors for singleton buffers")
19048 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19049 });
19050 }
19051
19052 pub fn display_text(&self, cx: &mut App) -> String {
19053 self.display_map
19054 .update(cx, |map, cx| map.snapshot(cx))
19055 .text()
19056 }
19057
19058 fn create_minimap(
19059 &self,
19060 minimap_settings: MinimapSettings,
19061 window: &mut Window,
19062 cx: &mut Context<Self>,
19063 ) -> Option<Entity<Self>> {
19064 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19065 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19066 }
19067
19068 fn initialize_new_minimap(
19069 &self,
19070 minimap_settings: MinimapSettings,
19071 window: &mut Window,
19072 cx: &mut Context<Self>,
19073 ) -> Entity<Self> {
19074 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19075
19076 let mut minimap = Editor::new_internal(
19077 EditorMode::Minimap {
19078 parent: cx.weak_entity(),
19079 },
19080 self.buffer.clone(),
19081 None,
19082 Some(self.display_map.clone()),
19083 window,
19084 cx,
19085 );
19086 minimap.scroll_manager.clone_state(&self.scroll_manager);
19087 minimap.set_text_style_refinement(TextStyleRefinement {
19088 font_size: Some(MINIMAP_FONT_SIZE),
19089 font_weight: Some(MINIMAP_FONT_WEIGHT),
19090 ..Default::default()
19091 });
19092 minimap.update_minimap_configuration(minimap_settings, cx);
19093 cx.new(|_| minimap)
19094 }
19095
19096 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19097 let current_line_highlight = minimap_settings
19098 .current_line_highlight
19099 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19100 self.set_current_line_highlight(Some(current_line_highlight));
19101 }
19102
19103 pub fn minimap(&self) -> Option<&Entity<Self>> {
19104 self.minimap
19105 .as_ref()
19106 .filter(|_| self.minimap_visibility.visible())
19107 }
19108
19109 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19110 let mut wrap_guides = smallvec![];
19111
19112 if self.show_wrap_guides == Some(false) {
19113 return wrap_guides;
19114 }
19115
19116 let settings = self.buffer.read(cx).language_settings(cx);
19117 if settings.show_wrap_guides {
19118 match self.soft_wrap_mode(cx) {
19119 SoftWrap::Column(soft_wrap) => {
19120 wrap_guides.push((soft_wrap as usize, true));
19121 }
19122 SoftWrap::Bounded(soft_wrap) => {
19123 wrap_guides.push((soft_wrap as usize, true));
19124 }
19125 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19126 }
19127 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19128 }
19129
19130 wrap_guides
19131 }
19132
19133 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19134 let settings = self.buffer.read(cx).language_settings(cx);
19135 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19136 match mode {
19137 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19138 SoftWrap::None
19139 }
19140 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19141 language_settings::SoftWrap::PreferredLineLength => {
19142 SoftWrap::Column(settings.preferred_line_length)
19143 }
19144 language_settings::SoftWrap::Bounded => {
19145 SoftWrap::Bounded(settings.preferred_line_length)
19146 }
19147 }
19148 }
19149
19150 pub fn set_soft_wrap_mode(
19151 &mut self,
19152 mode: language_settings::SoftWrap,
19153
19154 cx: &mut Context<Self>,
19155 ) {
19156 self.soft_wrap_mode_override = Some(mode);
19157 cx.notify();
19158 }
19159
19160 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19161 self.hard_wrap = hard_wrap;
19162 cx.notify();
19163 }
19164
19165 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19166 self.text_style_refinement = Some(style);
19167 }
19168
19169 /// called by the Element so we know what style we were most recently rendered with.
19170 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19171 // We intentionally do not inform the display map about the minimap style
19172 // so that wrapping is not recalculated and stays consistent for the editor
19173 // and its linked minimap.
19174 if !self.mode.is_minimap() {
19175 let font = style.text.font();
19176 let font_size = style.text.font_size.to_pixels(window.rem_size());
19177 let display_map = self
19178 .placeholder_display_map
19179 .as_ref()
19180 .filter(|_| self.is_empty(cx))
19181 .unwrap_or(&self.display_map);
19182
19183 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19184 }
19185 self.style = Some(style);
19186 }
19187
19188 pub fn style(&self) -> Option<&EditorStyle> {
19189 self.style.as_ref()
19190 }
19191
19192 // Called by the element. This method is not designed to be called outside of the editor
19193 // element's layout code because it does not notify when rewrapping is computed synchronously.
19194 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19195 if self.is_empty(cx) {
19196 self.placeholder_display_map
19197 .as_ref()
19198 .map_or(false, |display_map| {
19199 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19200 })
19201 } else {
19202 self.display_map
19203 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19204 }
19205 }
19206
19207 pub fn set_soft_wrap(&mut self) {
19208 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19209 }
19210
19211 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19212 if self.soft_wrap_mode_override.is_some() {
19213 self.soft_wrap_mode_override.take();
19214 } else {
19215 let soft_wrap = match self.soft_wrap_mode(cx) {
19216 SoftWrap::GitDiff => return,
19217 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19218 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19219 language_settings::SoftWrap::None
19220 }
19221 };
19222 self.soft_wrap_mode_override = Some(soft_wrap);
19223 }
19224 cx.notify();
19225 }
19226
19227 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19228 let Some(workspace) = self.workspace() else {
19229 return;
19230 };
19231 let fs = workspace.read(cx).app_state().fs.clone();
19232 let current_show = TabBarSettings::get_global(cx).show;
19233 update_settings_file(fs, cx, move |setting, _| {
19234 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19235 });
19236 }
19237
19238 pub fn toggle_indent_guides(
19239 &mut self,
19240 _: &ToggleIndentGuides,
19241 _: &mut Window,
19242 cx: &mut Context<Self>,
19243 ) {
19244 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19245 self.buffer
19246 .read(cx)
19247 .language_settings(cx)
19248 .indent_guides
19249 .enabled
19250 });
19251 self.show_indent_guides = Some(!currently_enabled);
19252 cx.notify();
19253 }
19254
19255 fn should_show_indent_guides(&self) -> Option<bool> {
19256 self.show_indent_guides
19257 }
19258
19259 pub fn toggle_line_numbers(
19260 &mut self,
19261 _: &ToggleLineNumbers,
19262 _: &mut Window,
19263 cx: &mut Context<Self>,
19264 ) {
19265 let mut editor_settings = EditorSettings::get_global(cx).clone();
19266 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19267 EditorSettings::override_global(editor_settings, cx);
19268 }
19269
19270 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19271 if let Some(show_line_numbers) = self.show_line_numbers {
19272 return show_line_numbers;
19273 }
19274 EditorSettings::get_global(cx).gutter.line_numbers
19275 }
19276
19277 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19278 self.use_relative_line_numbers
19279 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19280 }
19281
19282 pub fn toggle_relative_line_numbers(
19283 &mut self,
19284 _: &ToggleRelativeLineNumbers,
19285 _: &mut Window,
19286 cx: &mut Context<Self>,
19287 ) {
19288 let is_relative = self.should_use_relative_line_numbers(cx);
19289 self.set_relative_line_number(Some(!is_relative), cx)
19290 }
19291
19292 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19293 self.use_relative_line_numbers = is_relative;
19294 cx.notify();
19295 }
19296
19297 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19298 self.show_gutter = show_gutter;
19299 cx.notify();
19300 }
19301
19302 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19303 self.show_scrollbars = ScrollbarAxes {
19304 horizontal: show,
19305 vertical: show,
19306 };
19307 cx.notify();
19308 }
19309
19310 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19311 self.show_scrollbars.vertical = show;
19312 cx.notify();
19313 }
19314
19315 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19316 self.show_scrollbars.horizontal = show;
19317 cx.notify();
19318 }
19319
19320 pub fn set_minimap_visibility(
19321 &mut self,
19322 minimap_visibility: MinimapVisibility,
19323 window: &mut Window,
19324 cx: &mut Context<Self>,
19325 ) {
19326 if self.minimap_visibility != minimap_visibility {
19327 if minimap_visibility.visible() && self.minimap.is_none() {
19328 let minimap_settings = EditorSettings::get_global(cx).minimap;
19329 self.minimap =
19330 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19331 }
19332 self.minimap_visibility = minimap_visibility;
19333 cx.notify();
19334 }
19335 }
19336
19337 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19338 self.set_show_scrollbars(false, cx);
19339 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19340 }
19341
19342 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19343 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19344 }
19345
19346 /// Normally the text in full mode and auto height editors is padded on the
19347 /// left side by roughly half a character width for improved hit testing.
19348 ///
19349 /// Use this method to disable this for cases where this is not wanted (e.g.
19350 /// if you want to align the editor text with some other text above or below)
19351 /// or if you want to add this padding to single-line editors.
19352 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19353 self.offset_content = offset_content;
19354 cx.notify();
19355 }
19356
19357 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19358 self.show_line_numbers = Some(show_line_numbers);
19359 cx.notify();
19360 }
19361
19362 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19363 self.disable_expand_excerpt_buttons = true;
19364 cx.notify();
19365 }
19366
19367 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19368 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19369 cx.notify();
19370 }
19371
19372 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19373 self.show_code_actions = Some(show_code_actions);
19374 cx.notify();
19375 }
19376
19377 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19378 self.show_runnables = Some(show_runnables);
19379 cx.notify();
19380 }
19381
19382 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19383 self.show_breakpoints = Some(show_breakpoints);
19384 cx.notify();
19385 }
19386
19387 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19388 if self.display_map.read(cx).masked != masked {
19389 self.display_map.update(cx, |map, _| map.masked = masked);
19390 }
19391 cx.notify()
19392 }
19393
19394 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19395 self.show_wrap_guides = Some(show_wrap_guides);
19396 cx.notify();
19397 }
19398
19399 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19400 self.show_indent_guides = Some(show_indent_guides);
19401 cx.notify();
19402 }
19403
19404 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19405 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19406 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19407 && let Some(dir) = file.abs_path(cx).parent()
19408 {
19409 return Some(dir.to_owned());
19410 }
19411 }
19412
19413 None
19414 }
19415
19416 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19417 self.active_excerpt(cx)?
19418 .1
19419 .read(cx)
19420 .file()
19421 .and_then(|f| f.as_local())
19422 }
19423
19424 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19425 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19426 let buffer = buffer.read(cx);
19427 if let Some(project_path) = buffer.project_path(cx) {
19428 let project = self.project()?.read(cx);
19429 project.absolute_path(&project_path, cx)
19430 } else {
19431 buffer
19432 .file()
19433 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19434 }
19435 })
19436 }
19437
19438 pub fn reveal_in_finder(
19439 &mut self,
19440 _: &RevealInFileManager,
19441 _window: &mut Window,
19442 cx: &mut Context<Self>,
19443 ) {
19444 if let Some(target) = self.target_file(cx) {
19445 cx.reveal_path(&target.abs_path(cx));
19446 }
19447 }
19448
19449 pub fn copy_path(
19450 &mut self,
19451 _: &zed_actions::workspace::CopyPath,
19452 _window: &mut Window,
19453 cx: &mut Context<Self>,
19454 ) {
19455 if let Some(path) = self.target_file_abs_path(cx)
19456 && let Some(path) = path.to_str()
19457 {
19458 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19459 } else {
19460 cx.propagate();
19461 }
19462 }
19463
19464 pub fn copy_relative_path(
19465 &mut self,
19466 _: &zed_actions::workspace::CopyRelativePath,
19467 _window: &mut Window,
19468 cx: &mut Context<Self>,
19469 ) {
19470 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19471 let project = self.project()?.read(cx);
19472 let path = buffer.read(cx).file()?.path();
19473 let path = path.display(project.path_style(cx));
19474 Some(path)
19475 }) {
19476 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19477 } else {
19478 cx.propagate();
19479 }
19480 }
19481
19482 /// Returns the project path for the editor's buffer, if any buffer is
19483 /// opened in the editor.
19484 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19485 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19486 buffer.read(cx).project_path(cx)
19487 } else {
19488 None
19489 }
19490 }
19491
19492 // Returns true if the editor handled a go-to-line request
19493 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19494 maybe!({
19495 let breakpoint_store = self.breakpoint_store.as_ref()?;
19496
19497 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19498 else {
19499 self.clear_row_highlights::<ActiveDebugLine>();
19500 return None;
19501 };
19502
19503 let position = active_stack_frame.position;
19504 let buffer_id = position.buffer_id?;
19505 let snapshot = self
19506 .project
19507 .as_ref()?
19508 .read(cx)
19509 .buffer_for_id(buffer_id, cx)?
19510 .read(cx)
19511 .snapshot();
19512
19513 let mut handled = false;
19514 for (id, ExcerptRange { context, .. }) in
19515 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19516 {
19517 if context.start.cmp(&position, &snapshot).is_ge()
19518 || context.end.cmp(&position, &snapshot).is_lt()
19519 {
19520 continue;
19521 }
19522 let snapshot = self.buffer.read(cx).snapshot(cx);
19523 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19524
19525 handled = true;
19526 self.clear_row_highlights::<ActiveDebugLine>();
19527
19528 self.go_to_line::<ActiveDebugLine>(
19529 multibuffer_anchor,
19530 Some(cx.theme().colors().editor_debugger_active_line_background),
19531 window,
19532 cx,
19533 );
19534
19535 cx.notify();
19536 }
19537
19538 handled.then_some(())
19539 })
19540 .is_some()
19541 }
19542
19543 pub fn copy_file_name_without_extension(
19544 &mut self,
19545 _: &CopyFileNameWithoutExtension,
19546 _: &mut Window,
19547 cx: &mut Context<Self>,
19548 ) {
19549 if let Some(file) = self.target_file(cx)
19550 && let Some(file_stem) = file.path().file_stem()
19551 {
19552 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19553 }
19554 }
19555
19556 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19557 if let Some(file) = self.target_file(cx)
19558 && let Some(name) = file.path().file_name()
19559 {
19560 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19561 }
19562 }
19563
19564 pub fn toggle_git_blame(
19565 &mut self,
19566 _: &::git::Blame,
19567 window: &mut Window,
19568 cx: &mut Context<Self>,
19569 ) {
19570 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19571
19572 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19573 self.start_git_blame(true, window, cx);
19574 }
19575
19576 cx.notify();
19577 }
19578
19579 pub fn toggle_git_blame_inline(
19580 &mut self,
19581 _: &ToggleGitBlameInline,
19582 window: &mut Window,
19583 cx: &mut Context<Self>,
19584 ) {
19585 self.toggle_git_blame_inline_internal(true, window, cx);
19586 cx.notify();
19587 }
19588
19589 pub fn open_git_blame_commit(
19590 &mut self,
19591 _: &OpenGitBlameCommit,
19592 window: &mut Window,
19593 cx: &mut Context<Self>,
19594 ) {
19595 self.open_git_blame_commit_internal(window, cx);
19596 }
19597
19598 fn open_git_blame_commit_internal(
19599 &mut self,
19600 window: &mut Window,
19601 cx: &mut Context<Self>,
19602 ) -> Option<()> {
19603 let blame = self.blame.as_ref()?;
19604 let snapshot = self.snapshot(window, cx);
19605 let cursor = self.selections.newest::<Point>(cx).head();
19606 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19607 let (_, blame_entry) = blame
19608 .update(cx, |blame, cx| {
19609 blame
19610 .blame_for_rows(
19611 &[RowInfo {
19612 buffer_id: Some(buffer.remote_id()),
19613 buffer_row: Some(point.row),
19614 ..Default::default()
19615 }],
19616 cx,
19617 )
19618 .next()
19619 })
19620 .flatten()?;
19621 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19622 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19623 let workspace = self.workspace()?.downgrade();
19624 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19625 None
19626 }
19627
19628 pub fn git_blame_inline_enabled(&self) -> bool {
19629 self.git_blame_inline_enabled
19630 }
19631
19632 pub fn toggle_selection_menu(
19633 &mut self,
19634 _: &ToggleSelectionMenu,
19635 _: &mut Window,
19636 cx: &mut Context<Self>,
19637 ) {
19638 self.show_selection_menu = self
19639 .show_selection_menu
19640 .map(|show_selections_menu| !show_selections_menu)
19641 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19642
19643 cx.notify();
19644 }
19645
19646 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19647 self.show_selection_menu
19648 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19649 }
19650
19651 fn start_git_blame(
19652 &mut self,
19653 user_triggered: bool,
19654 window: &mut Window,
19655 cx: &mut Context<Self>,
19656 ) {
19657 if let Some(project) = self.project() {
19658 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19659 && buffer.read(cx).file().is_none()
19660 {
19661 return;
19662 }
19663
19664 let focused = self.focus_handle(cx).contains_focused(window, cx);
19665
19666 let project = project.clone();
19667 let blame = cx
19668 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19669 self.blame_subscription =
19670 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19671 self.blame = Some(blame);
19672 }
19673 }
19674
19675 fn toggle_git_blame_inline_internal(
19676 &mut self,
19677 user_triggered: bool,
19678 window: &mut Window,
19679 cx: &mut Context<Self>,
19680 ) {
19681 if self.git_blame_inline_enabled {
19682 self.git_blame_inline_enabled = false;
19683 self.show_git_blame_inline = false;
19684 self.show_git_blame_inline_delay_task.take();
19685 } else {
19686 self.git_blame_inline_enabled = true;
19687 self.start_git_blame_inline(user_triggered, window, cx);
19688 }
19689
19690 cx.notify();
19691 }
19692
19693 fn start_git_blame_inline(
19694 &mut self,
19695 user_triggered: bool,
19696 window: &mut Window,
19697 cx: &mut Context<Self>,
19698 ) {
19699 self.start_git_blame(user_triggered, window, cx);
19700
19701 if ProjectSettings::get_global(cx)
19702 .git
19703 .inline_blame_delay()
19704 .is_some()
19705 {
19706 self.start_inline_blame_timer(window, cx);
19707 } else {
19708 self.show_git_blame_inline = true
19709 }
19710 }
19711
19712 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19713 self.blame.as_ref()
19714 }
19715
19716 pub fn show_git_blame_gutter(&self) -> bool {
19717 self.show_git_blame_gutter
19718 }
19719
19720 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19721 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19722 }
19723
19724 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19725 self.show_git_blame_inline
19726 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19727 && !self.newest_selection_head_on_empty_line(cx)
19728 && self.has_blame_entries(cx)
19729 }
19730
19731 fn has_blame_entries(&self, cx: &App) -> bool {
19732 self.blame()
19733 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19734 }
19735
19736 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19737 let cursor_anchor = self.selections.newest_anchor().head();
19738
19739 let snapshot = self.buffer.read(cx).snapshot(cx);
19740 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19741
19742 snapshot.line_len(buffer_row) == 0
19743 }
19744
19745 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19746 let buffer_and_selection = maybe!({
19747 let selection = self.selections.newest::<Point>(cx);
19748 let selection_range = selection.range();
19749
19750 let multi_buffer = self.buffer().read(cx);
19751 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19752 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19753
19754 let (buffer, range, _) = if selection.reversed {
19755 buffer_ranges.first()
19756 } else {
19757 buffer_ranges.last()
19758 }?;
19759
19760 let selection = text::ToPoint::to_point(&range.start, buffer).row
19761 ..text::ToPoint::to_point(&range.end, buffer).row;
19762 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19763 });
19764
19765 let Some((buffer, selection)) = buffer_and_selection else {
19766 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19767 };
19768
19769 let Some(project) = self.project() else {
19770 return Task::ready(Err(anyhow!("editor does not have project")));
19771 };
19772
19773 project.update(cx, |project, cx| {
19774 project.get_permalink_to_line(&buffer, selection, cx)
19775 })
19776 }
19777
19778 pub fn copy_permalink_to_line(
19779 &mut self,
19780 _: &CopyPermalinkToLine,
19781 window: &mut Window,
19782 cx: &mut Context<Self>,
19783 ) {
19784 let permalink_task = self.get_permalink_to_line(cx);
19785 let workspace = self.workspace();
19786
19787 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19788 Ok(permalink) => {
19789 cx.update(|_, cx| {
19790 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19791 })
19792 .ok();
19793 }
19794 Err(err) => {
19795 let message = format!("Failed to copy permalink: {err}");
19796
19797 anyhow::Result::<()>::Err(err).log_err();
19798
19799 if let Some(workspace) = workspace {
19800 workspace
19801 .update_in(cx, |workspace, _, cx| {
19802 struct CopyPermalinkToLine;
19803
19804 workspace.show_toast(
19805 Toast::new(
19806 NotificationId::unique::<CopyPermalinkToLine>(),
19807 message,
19808 ),
19809 cx,
19810 )
19811 })
19812 .ok();
19813 }
19814 }
19815 })
19816 .detach();
19817 }
19818
19819 pub fn copy_file_location(
19820 &mut self,
19821 _: &CopyFileLocation,
19822 _: &mut Window,
19823 cx: &mut Context<Self>,
19824 ) {
19825 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19826 if let Some(file) = self.target_file(cx) {
19827 let path = file.path().display(file.path_style(cx));
19828 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19829 }
19830 }
19831
19832 pub fn open_permalink_to_line(
19833 &mut self,
19834 _: &OpenPermalinkToLine,
19835 window: &mut Window,
19836 cx: &mut Context<Self>,
19837 ) {
19838 let permalink_task = self.get_permalink_to_line(cx);
19839 let workspace = self.workspace();
19840
19841 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19842 Ok(permalink) => {
19843 cx.update(|_, cx| {
19844 cx.open_url(permalink.as_ref());
19845 })
19846 .ok();
19847 }
19848 Err(err) => {
19849 let message = format!("Failed to open permalink: {err}");
19850
19851 anyhow::Result::<()>::Err(err).log_err();
19852
19853 if let Some(workspace) = workspace {
19854 workspace
19855 .update(cx, |workspace, cx| {
19856 struct OpenPermalinkToLine;
19857
19858 workspace.show_toast(
19859 Toast::new(
19860 NotificationId::unique::<OpenPermalinkToLine>(),
19861 message,
19862 ),
19863 cx,
19864 )
19865 })
19866 .ok();
19867 }
19868 }
19869 })
19870 .detach();
19871 }
19872
19873 pub fn insert_uuid_v4(
19874 &mut self,
19875 _: &InsertUuidV4,
19876 window: &mut Window,
19877 cx: &mut Context<Self>,
19878 ) {
19879 self.insert_uuid(UuidVersion::V4, window, cx);
19880 }
19881
19882 pub fn insert_uuid_v7(
19883 &mut self,
19884 _: &InsertUuidV7,
19885 window: &mut Window,
19886 cx: &mut Context<Self>,
19887 ) {
19888 self.insert_uuid(UuidVersion::V7, window, cx);
19889 }
19890
19891 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19892 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19893 self.transact(window, cx, |this, window, cx| {
19894 let edits = this
19895 .selections
19896 .all::<Point>(cx)
19897 .into_iter()
19898 .map(|selection| {
19899 let uuid = match version {
19900 UuidVersion::V4 => uuid::Uuid::new_v4(),
19901 UuidVersion::V7 => uuid::Uuid::now_v7(),
19902 };
19903
19904 (selection.range(), uuid.to_string())
19905 });
19906 this.edit(edits, cx);
19907 this.refresh_edit_prediction(true, false, window, cx);
19908 });
19909 }
19910
19911 pub fn open_selections_in_multibuffer(
19912 &mut self,
19913 _: &OpenSelectionsInMultibuffer,
19914 window: &mut Window,
19915 cx: &mut Context<Self>,
19916 ) {
19917 let multibuffer = self.buffer.read(cx);
19918
19919 let Some(buffer) = multibuffer.as_singleton() else {
19920 return;
19921 };
19922
19923 let Some(workspace) = self.workspace() else {
19924 return;
19925 };
19926
19927 let title = multibuffer.title(cx).to_string();
19928
19929 let locations = self
19930 .selections
19931 .all_anchors(cx)
19932 .iter()
19933 .map(|selection| {
19934 (
19935 buffer.clone(),
19936 (selection.start.text_anchor..selection.end.text_anchor)
19937 .to_point(buffer.read(cx)),
19938 )
19939 })
19940 .into_group_map();
19941
19942 cx.spawn_in(window, async move |_, cx| {
19943 workspace.update_in(cx, |workspace, window, cx| {
19944 Self::open_locations_in_multibuffer(
19945 workspace,
19946 locations,
19947 format!("Selections for '{title}'"),
19948 false,
19949 MultibufferSelectionMode::All,
19950 window,
19951 cx,
19952 );
19953 })
19954 })
19955 .detach();
19956 }
19957
19958 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19959 /// last highlight added will be used.
19960 ///
19961 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19962 pub fn highlight_rows<T: 'static>(
19963 &mut self,
19964 range: Range<Anchor>,
19965 color: Hsla,
19966 options: RowHighlightOptions,
19967 cx: &mut Context<Self>,
19968 ) {
19969 let snapshot = self.buffer().read(cx).snapshot(cx);
19970 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19971 let ix = row_highlights.binary_search_by(|highlight| {
19972 Ordering::Equal
19973 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19974 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19975 });
19976
19977 if let Err(mut ix) = ix {
19978 let index = post_inc(&mut self.highlight_order);
19979
19980 // If this range intersects with the preceding highlight, then merge it with
19981 // the preceding highlight. Otherwise insert a new highlight.
19982 let mut merged = false;
19983 if ix > 0 {
19984 let prev_highlight = &mut row_highlights[ix - 1];
19985 if prev_highlight
19986 .range
19987 .end
19988 .cmp(&range.start, &snapshot)
19989 .is_ge()
19990 {
19991 ix -= 1;
19992 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19993 prev_highlight.range.end = range.end;
19994 }
19995 merged = true;
19996 prev_highlight.index = index;
19997 prev_highlight.color = color;
19998 prev_highlight.options = options;
19999 }
20000 }
20001
20002 if !merged {
20003 row_highlights.insert(
20004 ix,
20005 RowHighlight {
20006 range,
20007 index,
20008 color,
20009 options,
20010 type_id: TypeId::of::<T>(),
20011 },
20012 );
20013 }
20014
20015 // If any of the following highlights intersect with this one, merge them.
20016 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20017 let highlight = &row_highlights[ix];
20018 if next_highlight
20019 .range
20020 .start
20021 .cmp(&highlight.range.end, &snapshot)
20022 .is_le()
20023 {
20024 if next_highlight
20025 .range
20026 .end
20027 .cmp(&highlight.range.end, &snapshot)
20028 .is_gt()
20029 {
20030 row_highlights[ix].range.end = next_highlight.range.end;
20031 }
20032 row_highlights.remove(ix + 1);
20033 } else {
20034 break;
20035 }
20036 }
20037 }
20038 }
20039
20040 /// Remove any highlighted row ranges of the given type that intersect the
20041 /// given ranges.
20042 pub fn remove_highlighted_rows<T: 'static>(
20043 &mut self,
20044 ranges_to_remove: Vec<Range<Anchor>>,
20045 cx: &mut Context<Self>,
20046 ) {
20047 let snapshot = self.buffer().read(cx).snapshot(cx);
20048 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20049 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20050 row_highlights.retain(|highlight| {
20051 while let Some(range_to_remove) = ranges_to_remove.peek() {
20052 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20053 Ordering::Less | Ordering::Equal => {
20054 ranges_to_remove.next();
20055 }
20056 Ordering::Greater => {
20057 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20058 Ordering::Less | Ordering::Equal => {
20059 return false;
20060 }
20061 Ordering::Greater => break,
20062 }
20063 }
20064 }
20065 }
20066
20067 true
20068 })
20069 }
20070
20071 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20072 pub fn clear_row_highlights<T: 'static>(&mut self) {
20073 self.highlighted_rows.remove(&TypeId::of::<T>());
20074 }
20075
20076 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20077 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20078 self.highlighted_rows
20079 .get(&TypeId::of::<T>())
20080 .map_or(&[] as &[_], |vec| vec.as_slice())
20081 .iter()
20082 .map(|highlight| (highlight.range.clone(), highlight.color))
20083 }
20084
20085 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20086 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20087 /// Allows to ignore certain kinds of highlights.
20088 pub fn highlighted_display_rows(
20089 &self,
20090 window: &mut Window,
20091 cx: &mut App,
20092 ) -> BTreeMap<DisplayRow, LineHighlight> {
20093 let snapshot = self.snapshot(window, cx);
20094 let mut used_highlight_orders = HashMap::default();
20095 self.highlighted_rows
20096 .iter()
20097 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20098 .fold(
20099 BTreeMap::<DisplayRow, LineHighlight>::new(),
20100 |mut unique_rows, highlight| {
20101 let start = highlight.range.start.to_display_point(&snapshot);
20102 let end = highlight.range.end.to_display_point(&snapshot);
20103 let start_row = start.row().0;
20104 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20105 && end.column() == 0
20106 {
20107 end.row().0.saturating_sub(1)
20108 } else {
20109 end.row().0
20110 };
20111 for row in start_row..=end_row {
20112 let used_index =
20113 used_highlight_orders.entry(row).or_insert(highlight.index);
20114 if highlight.index >= *used_index {
20115 *used_index = highlight.index;
20116 unique_rows.insert(
20117 DisplayRow(row),
20118 LineHighlight {
20119 include_gutter: highlight.options.include_gutter,
20120 border: None,
20121 background: highlight.color.into(),
20122 type_id: Some(highlight.type_id),
20123 },
20124 );
20125 }
20126 }
20127 unique_rows
20128 },
20129 )
20130 }
20131
20132 pub fn highlighted_display_row_for_autoscroll(
20133 &self,
20134 snapshot: &DisplaySnapshot,
20135 ) -> Option<DisplayRow> {
20136 self.highlighted_rows
20137 .values()
20138 .flat_map(|highlighted_rows| highlighted_rows.iter())
20139 .filter_map(|highlight| {
20140 if highlight.options.autoscroll {
20141 Some(highlight.range.start.to_display_point(snapshot).row())
20142 } else {
20143 None
20144 }
20145 })
20146 .min()
20147 }
20148
20149 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20150 self.highlight_background::<SearchWithinRange>(
20151 ranges,
20152 |colors| colors.colors().editor_document_highlight_read_background,
20153 cx,
20154 )
20155 }
20156
20157 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20158 self.breadcrumb_header = Some(new_header);
20159 }
20160
20161 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20162 self.clear_background_highlights::<SearchWithinRange>(cx);
20163 }
20164
20165 pub fn highlight_background<T: 'static>(
20166 &mut self,
20167 ranges: &[Range<Anchor>],
20168 color_fetcher: fn(&Theme) -> Hsla,
20169 cx: &mut Context<Self>,
20170 ) {
20171 self.background_highlights.insert(
20172 HighlightKey::Type(TypeId::of::<T>()),
20173 (color_fetcher, Arc::from(ranges)),
20174 );
20175 self.scrollbar_marker_state.dirty = true;
20176 cx.notify();
20177 }
20178
20179 pub fn highlight_background_key<T: 'static>(
20180 &mut self,
20181 key: usize,
20182 ranges: &[Range<Anchor>],
20183 color_fetcher: fn(&Theme) -> Hsla,
20184 cx: &mut Context<Self>,
20185 ) {
20186 self.background_highlights.insert(
20187 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20188 (color_fetcher, Arc::from(ranges)),
20189 );
20190 self.scrollbar_marker_state.dirty = true;
20191 cx.notify();
20192 }
20193
20194 pub fn clear_background_highlights<T: 'static>(
20195 &mut self,
20196 cx: &mut Context<Self>,
20197 ) -> Option<BackgroundHighlight> {
20198 let text_highlights = self
20199 .background_highlights
20200 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20201 if !text_highlights.1.is_empty() {
20202 self.scrollbar_marker_state.dirty = true;
20203 cx.notify();
20204 }
20205 Some(text_highlights)
20206 }
20207
20208 pub fn highlight_gutter<T: 'static>(
20209 &mut self,
20210 ranges: impl Into<Vec<Range<Anchor>>>,
20211 color_fetcher: fn(&App) -> Hsla,
20212 cx: &mut Context<Self>,
20213 ) {
20214 self.gutter_highlights
20215 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20216 cx.notify();
20217 }
20218
20219 pub fn clear_gutter_highlights<T: 'static>(
20220 &mut self,
20221 cx: &mut Context<Self>,
20222 ) -> Option<GutterHighlight> {
20223 cx.notify();
20224 self.gutter_highlights.remove(&TypeId::of::<T>())
20225 }
20226
20227 pub fn insert_gutter_highlight<T: 'static>(
20228 &mut self,
20229 range: Range<Anchor>,
20230 color_fetcher: fn(&App) -> Hsla,
20231 cx: &mut Context<Self>,
20232 ) {
20233 let snapshot = self.buffer().read(cx).snapshot(cx);
20234 let mut highlights = self
20235 .gutter_highlights
20236 .remove(&TypeId::of::<T>())
20237 .map(|(_, highlights)| highlights)
20238 .unwrap_or_default();
20239 let ix = highlights.binary_search_by(|highlight| {
20240 Ordering::Equal
20241 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20242 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20243 });
20244 if let Err(ix) = ix {
20245 highlights.insert(ix, range);
20246 }
20247 self.gutter_highlights
20248 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20249 }
20250
20251 pub fn remove_gutter_highlights<T: 'static>(
20252 &mut self,
20253 ranges_to_remove: Vec<Range<Anchor>>,
20254 cx: &mut Context<Self>,
20255 ) {
20256 let snapshot = self.buffer().read(cx).snapshot(cx);
20257 let Some((color_fetcher, mut gutter_highlights)) =
20258 self.gutter_highlights.remove(&TypeId::of::<T>())
20259 else {
20260 return;
20261 };
20262 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20263 gutter_highlights.retain(|highlight| {
20264 while let Some(range_to_remove) = ranges_to_remove.peek() {
20265 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20266 Ordering::Less | Ordering::Equal => {
20267 ranges_to_remove.next();
20268 }
20269 Ordering::Greater => {
20270 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20271 Ordering::Less | Ordering::Equal => {
20272 return false;
20273 }
20274 Ordering::Greater => break,
20275 }
20276 }
20277 }
20278 }
20279
20280 true
20281 });
20282 self.gutter_highlights
20283 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20284 }
20285
20286 #[cfg(feature = "test-support")]
20287 pub fn all_text_highlights(
20288 &self,
20289 window: &mut Window,
20290 cx: &mut Context<Self>,
20291 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20292 let snapshot = self.snapshot(window, cx);
20293 self.display_map.update(cx, |display_map, _| {
20294 display_map
20295 .all_text_highlights()
20296 .map(|highlight| {
20297 let (style, ranges) = highlight.as_ref();
20298 (
20299 *style,
20300 ranges
20301 .iter()
20302 .map(|range| range.clone().to_display_points(&snapshot))
20303 .collect(),
20304 )
20305 })
20306 .collect()
20307 })
20308 }
20309
20310 #[cfg(feature = "test-support")]
20311 pub fn all_text_background_highlights(
20312 &self,
20313 window: &mut Window,
20314 cx: &mut Context<Self>,
20315 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20316 let snapshot = self.snapshot(window, cx);
20317 let buffer = &snapshot.buffer_snapshot();
20318 let start = buffer.anchor_before(0);
20319 let end = buffer.anchor_after(buffer.len());
20320 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20321 }
20322
20323 #[cfg(any(test, feature = "test-support"))]
20324 pub fn sorted_background_highlights_in_range(
20325 &self,
20326 search_range: Range<Anchor>,
20327 display_snapshot: &DisplaySnapshot,
20328 theme: &Theme,
20329 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20330 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20331 res.sort_by(|a, b| {
20332 a.0.start
20333 .cmp(&b.0.start)
20334 .then_with(|| a.0.end.cmp(&b.0.end))
20335 .then_with(|| a.1.cmp(&b.1))
20336 });
20337 res
20338 }
20339
20340 #[cfg(feature = "test-support")]
20341 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20342 let snapshot = self.buffer().read(cx).snapshot(cx);
20343
20344 let highlights = self
20345 .background_highlights
20346 .get(&HighlightKey::Type(TypeId::of::<
20347 items::BufferSearchHighlights,
20348 >()));
20349
20350 if let Some((_color, ranges)) = highlights {
20351 ranges
20352 .iter()
20353 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20354 .collect_vec()
20355 } else {
20356 vec![]
20357 }
20358 }
20359
20360 fn document_highlights_for_position<'a>(
20361 &'a self,
20362 position: Anchor,
20363 buffer: &'a MultiBufferSnapshot,
20364 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20365 let read_highlights = self
20366 .background_highlights
20367 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20368 .map(|h| &h.1);
20369 let write_highlights = self
20370 .background_highlights
20371 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20372 .map(|h| &h.1);
20373 let left_position = position.bias_left(buffer);
20374 let right_position = position.bias_right(buffer);
20375 read_highlights
20376 .into_iter()
20377 .chain(write_highlights)
20378 .flat_map(move |ranges| {
20379 let start_ix = match ranges.binary_search_by(|probe| {
20380 let cmp = probe.end.cmp(&left_position, buffer);
20381 if cmp.is_ge() {
20382 Ordering::Greater
20383 } else {
20384 Ordering::Less
20385 }
20386 }) {
20387 Ok(i) | Err(i) => i,
20388 };
20389
20390 ranges[start_ix..]
20391 .iter()
20392 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20393 })
20394 }
20395
20396 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20397 self.background_highlights
20398 .get(&HighlightKey::Type(TypeId::of::<T>()))
20399 .is_some_and(|(_, highlights)| !highlights.is_empty())
20400 }
20401
20402 /// Returns all background highlights for a given range.
20403 ///
20404 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20405 pub fn background_highlights_in_range(
20406 &self,
20407 search_range: Range<Anchor>,
20408 display_snapshot: &DisplaySnapshot,
20409 theme: &Theme,
20410 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20411 let mut results = Vec::new();
20412 for (color_fetcher, ranges) in self.background_highlights.values() {
20413 let color = color_fetcher(theme);
20414 let start_ix = match ranges.binary_search_by(|probe| {
20415 let cmp = probe
20416 .end
20417 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20418 if cmp.is_gt() {
20419 Ordering::Greater
20420 } else {
20421 Ordering::Less
20422 }
20423 }) {
20424 Ok(i) | Err(i) => i,
20425 };
20426 for range in &ranges[start_ix..] {
20427 if range
20428 .start
20429 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20430 .is_ge()
20431 {
20432 break;
20433 }
20434
20435 let start = range.start.to_display_point(display_snapshot);
20436 let end = range.end.to_display_point(display_snapshot);
20437 results.push((start..end, color))
20438 }
20439 }
20440 results
20441 }
20442
20443 pub fn gutter_highlights_in_range(
20444 &self,
20445 search_range: Range<Anchor>,
20446 display_snapshot: &DisplaySnapshot,
20447 cx: &App,
20448 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20449 let mut results = Vec::new();
20450 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20451 let color = color_fetcher(cx);
20452 let start_ix = match ranges.binary_search_by(|probe| {
20453 let cmp = probe
20454 .end
20455 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20456 if cmp.is_gt() {
20457 Ordering::Greater
20458 } else {
20459 Ordering::Less
20460 }
20461 }) {
20462 Ok(i) | Err(i) => i,
20463 };
20464 for range in &ranges[start_ix..] {
20465 if range
20466 .start
20467 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20468 .is_ge()
20469 {
20470 break;
20471 }
20472
20473 let start = range.start.to_display_point(display_snapshot);
20474 let end = range.end.to_display_point(display_snapshot);
20475 results.push((start..end, color))
20476 }
20477 }
20478 results
20479 }
20480
20481 /// Get the text ranges corresponding to the redaction query
20482 pub fn redacted_ranges(
20483 &self,
20484 search_range: Range<Anchor>,
20485 display_snapshot: &DisplaySnapshot,
20486 cx: &App,
20487 ) -> Vec<Range<DisplayPoint>> {
20488 display_snapshot
20489 .buffer_snapshot()
20490 .redacted_ranges(search_range, |file| {
20491 if let Some(file) = file {
20492 file.is_private()
20493 && EditorSettings::get(
20494 Some(SettingsLocation {
20495 worktree_id: file.worktree_id(cx),
20496 path: file.path().as_ref(),
20497 }),
20498 cx,
20499 )
20500 .redact_private_values
20501 } else {
20502 false
20503 }
20504 })
20505 .map(|range| {
20506 range.start.to_display_point(display_snapshot)
20507 ..range.end.to_display_point(display_snapshot)
20508 })
20509 .collect()
20510 }
20511
20512 pub fn highlight_text_key<T: 'static>(
20513 &mut self,
20514 key: usize,
20515 ranges: Vec<Range<Anchor>>,
20516 style: HighlightStyle,
20517 cx: &mut Context<Self>,
20518 ) {
20519 self.display_map.update(cx, |map, _| {
20520 map.highlight_text(
20521 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20522 ranges,
20523 style,
20524 );
20525 });
20526 cx.notify();
20527 }
20528
20529 pub fn highlight_text<T: 'static>(
20530 &mut self,
20531 ranges: Vec<Range<Anchor>>,
20532 style: HighlightStyle,
20533 cx: &mut Context<Self>,
20534 ) {
20535 self.display_map.update(cx, |map, _| {
20536 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20537 });
20538 cx.notify();
20539 }
20540
20541 pub(crate) fn highlight_inlays<T: 'static>(
20542 &mut self,
20543 highlights: Vec<InlayHighlight>,
20544 style: HighlightStyle,
20545 cx: &mut Context<Self>,
20546 ) {
20547 self.display_map.update(cx, |map, _| {
20548 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20549 });
20550 cx.notify();
20551 }
20552
20553 pub fn text_highlights<'a, T: 'static>(
20554 &'a self,
20555 cx: &'a App,
20556 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20557 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20558 }
20559
20560 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20561 let cleared = self
20562 .display_map
20563 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20564 if cleared {
20565 cx.notify();
20566 }
20567 }
20568
20569 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20570 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20571 && self.focus_handle.is_focused(window)
20572 }
20573
20574 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20575 self.show_cursor_when_unfocused = is_enabled;
20576 cx.notify();
20577 }
20578
20579 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20580 cx.notify();
20581 }
20582
20583 fn on_debug_session_event(
20584 &mut self,
20585 _session: Entity<Session>,
20586 event: &SessionEvent,
20587 cx: &mut Context<Self>,
20588 ) {
20589 if let SessionEvent::InvalidateInlineValue = event {
20590 self.refresh_inline_values(cx);
20591 }
20592 }
20593
20594 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20595 let Some(project) = self.project.clone() else {
20596 return;
20597 };
20598
20599 if !self.inline_value_cache.enabled {
20600 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20601 self.splice_inlays(&inlays, Vec::new(), cx);
20602 return;
20603 }
20604
20605 let current_execution_position = self
20606 .highlighted_rows
20607 .get(&TypeId::of::<ActiveDebugLine>())
20608 .and_then(|lines| lines.last().map(|line| line.range.end));
20609
20610 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20611 let inline_values = editor
20612 .update(cx, |editor, cx| {
20613 let Some(current_execution_position) = current_execution_position else {
20614 return Some(Task::ready(Ok(Vec::new())));
20615 };
20616
20617 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20618 let snapshot = buffer.snapshot(cx);
20619
20620 let excerpt = snapshot.excerpt_containing(
20621 current_execution_position..current_execution_position,
20622 )?;
20623
20624 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20625 })?;
20626
20627 let range =
20628 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20629
20630 project.inline_values(buffer, range, cx)
20631 })
20632 .ok()
20633 .flatten()?
20634 .await
20635 .context("refreshing debugger inlays")
20636 .log_err()?;
20637
20638 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20639
20640 for (buffer_id, inline_value) in inline_values
20641 .into_iter()
20642 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20643 {
20644 buffer_inline_values
20645 .entry(buffer_id)
20646 .or_default()
20647 .push(inline_value);
20648 }
20649
20650 editor
20651 .update(cx, |editor, cx| {
20652 let snapshot = editor.buffer.read(cx).snapshot(cx);
20653 let mut new_inlays = Vec::default();
20654
20655 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20656 let buffer_id = buffer_snapshot.remote_id();
20657 buffer_inline_values
20658 .get(&buffer_id)
20659 .into_iter()
20660 .flatten()
20661 .for_each(|hint| {
20662 let inlay = Inlay::debugger(
20663 post_inc(&mut editor.next_inlay_id),
20664 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20665 hint.text(),
20666 );
20667 if !inlay.text().chars().contains(&'\n') {
20668 new_inlays.push(inlay);
20669 }
20670 });
20671 }
20672
20673 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20674 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20675
20676 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20677 })
20678 .ok()?;
20679 Some(())
20680 });
20681 }
20682
20683 fn on_buffer_event(
20684 &mut self,
20685 multibuffer: &Entity<MultiBuffer>,
20686 event: &multi_buffer::Event,
20687 window: &mut Window,
20688 cx: &mut Context<Self>,
20689 ) {
20690 match event {
20691 multi_buffer::Event::Edited {
20692 singleton_buffer_edited,
20693 edited_buffer,
20694 } => {
20695 self.scrollbar_marker_state.dirty = true;
20696 self.active_indent_guides_state.dirty = true;
20697 self.refresh_active_diagnostics(cx);
20698 self.refresh_code_actions(window, cx);
20699 self.refresh_selected_text_highlights(true, window, cx);
20700 self.refresh_single_line_folds(window, cx);
20701 refresh_matching_bracket_highlights(self, window, cx);
20702 if self.has_active_edit_prediction() {
20703 self.update_visible_edit_prediction(window, cx);
20704 }
20705 if let Some(project) = self.project.as_ref()
20706 && let Some(edited_buffer) = edited_buffer
20707 {
20708 project.update(cx, |project, cx| {
20709 self.registered_buffers
20710 .entry(edited_buffer.read(cx).remote_id())
20711 .or_insert_with(|| {
20712 project.register_buffer_with_language_servers(edited_buffer, cx)
20713 });
20714 });
20715 }
20716 cx.emit(EditorEvent::BufferEdited);
20717 cx.emit(SearchEvent::MatchesInvalidated);
20718
20719 if let Some(buffer) = edited_buffer {
20720 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20721 }
20722
20723 if *singleton_buffer_edited {
20724 if let Some(buffer) = edited_buffer
20725 && buffer.read(cx).file().is_none()
20726 {
20727 cx.emit(EditorEvent::TitleChanged);
20728 }
20729 if let Some(project) = &self.project {
20730 #[allow(clippy::mutable_key_type)]
20731 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20732 multibuffer
20733 .all_buffers()
20734 .into_iter()
20735 .filter_map(|buffer| {
20736 buffer.update(cx, |buffer, cx| {
20737 let language = buffer.language()?;
20738 let should_discard = project.update(cx, |project, cx| {
20739 project.is_local()
20740 && !project.has_language_servers_for(buffer, cx)
20741 });
20742 should_discard.not().then_some(language.clone())
20743 })
20744 })
20745 .collect::<HashSet<_>>()
20746 });
20747 if !languages_affected.is_empty() {
20748 self.refresh_inlay_hints(
20749 InlayHintRefreshReason::BufferEdited(languages_affected),
20750 cx,
20751 );
20752 }
20753 }
20754 }
20755
20756 let Some(project) = &self.project else { return };
20757 let (telemetry, is_via_ssh) = {
20758 let project = project.read(cx);
20759 let telemetry = project.client().telemetry().clone();
20760 let is_via_ssh = project.is_via_remote_server();
20761 (telemetry, is_via_ssh)
20762 };
20763 refresh_linked_ranges(self, window, cx);
20764 telemetry.log_edit_event("editor", is_via_ssh);
20765 }
20766 multi_buffer::Event::ExcerptsAdded {
20767 buffer,
20768 predecessor,
20769 excerpts,
20770 } => {
20771 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20772 let buffer_id = buffer.read(cx).remote_id();
20773 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20774 && let Some(project) = &self.project
20775 {
20776 update_uncommitted_diff_for_buffer(
20777 cx.entity(),
20778 project,
20779 [buffer.clone()],
20780 self.buffer.clone(),
20781 cx,
20782 )
20783 .detach();
20784 }
20785 if self.active_diagnostics != ActiveDiagnostic::All {
20786 self.update_lsp_data(false, Some(buffer_id), window, cx);
20787 }
20788 cx.emit(EditorEvent::ExcerptsAdded {
20789 buffer: buffer.clone(),
20790 predecessor: *predecessor,
20791 excerpts: excerpts.clone(),
20792 });
20793 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20794 }
20795 multi_buffer::Event::ExcerptsRemoved {
20796 ids,
20797 removed_buffer_ids,
20798 } => {
20799 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20800 let buffer = self.buffer.read(cx);
20801 self.registered_buffers
20802 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20803 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20804 cx.emit(EditorEvent::ExcerptsRemoved {
20805 ids: ids.clone(),
20806 removed_buffer_ids: removed_buffer_ids.clone(),
20807 });
20808 }
20809 multi_buffer::Event::ExcerptsEdited {
20810 excerpt_ids,
20811 buffer_ids,
20812 } => {
20813 self.display_map.update(cx, |map, cx| {
20814 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20815 });
20816 cx.emit(EditorEvent::ExcerptsEdited {
20817 ids: excerpt_ids.clone(),
20818 });
20819 }
20820 multi_buffer::Event::ExcerptsExpanded { ids } => {
20821 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20822 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20823 }
20824 multi_buffer::Event::Reparsed(buffer_id) => {
20825 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20826 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20827
20828 cx.emit(EditorEvent::Reparsed(*buffer_id));
20829 }
20830 multi_buffer::Event::DiffHunksToggled => {
20831 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20832 }
20833 multi_buffer::Event::LanguageChanged(buffer_id) => {
20834 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20835 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20836 cx.emit(EditorEvent::Reparsed(*buffer_id));
20837 cx.notify();
20838 }
20839 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20840 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20841 multi_buffer::Event::FileHandleChanged
20842 | multi_buffer::Event::Reloaded
20843 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20844 multi_buffer::Event::DiagnosticsUpdated => {
20845 self.update_diagnostics_state(window, cx);
20846 }
20847 _ => {}
20848 };
20849 }
20850
20851 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20852 if !self.diagnostics_enabled() {
20853 return;
20854 }
20855 self.refresh_active_diagnostics(cx);
20856 self.refresh_inline_diagnostics(true, window, cx);
20857 self.scrollbar_marker_state.dirty = true;
20858 cx.notify();
20859 }
20860
20861 pub fn start_temporary_diff_override(&mut self) {
20862 self.load_diff_task.take();
20863 self.temporary_diff_override = true;
20864 }
20865
20866 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20867 self.temporary_diff_override = false;
20868 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20869 self.buffer.update(cx, |buffer, cx| {
20870 buffer.set_all_diff_hunks_collapsed(cx);
20871 });
20872
20873 if let Some(project) = self.project.clone() {
20874 self.load_diff_task = Some(
20875 update_uncommitted_diff_for_buffer(
20876 cx.entity(),
20877 &project,
20878 self.buffer.read(cx).all_buffers(),
20879 self.buffer.clone(),
20880 cx,
20881 )
20882 .shared(),
20883 );
20884 }
20885 }
20886
20887 fn on_display_map_changed(
20888 &mut self,
20889 _: Entity<DisplayMap>,
20890 _: &mut Window,
20891 cx: &mut Context<Self>,
20892 ) {
20893 cx.notify();
20894 }
20895
20896 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20897 if self.diagnostics_enabled() {
20898 let new_severity = EditorSettings::get_global(cx)
20899 .diagnostics_max_severity
20900 .unwrap_or(DiagnosticSeverity::Hint);
20901 self.set_max_diagnostics_severity(new_severity, cx);
20902 }
20903 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20904 self.update_edit_prediction_settings(cx);
20905 self.refresh_edit_prediction(true, false, window, cx);
20906 self.refresh_inline_values(cx);
20907 self.refresh_inlay_hints(
20908 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20909 self.selections.newest_anchor().head(),
20910 &self.buffer.read(cx).snapshot(cx),
20911 cx,
20912 )),
20913 cx,
20914 );
20915
20916 let old_cursor_shape = self.cursor_shape;
20917 let old_show_breadcrumbs = self.show_breadcrumbs;
20918
20919 {
20920 let editor_settings = EditorSettings::get_global(cx);
20921 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20922 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20923 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20924 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20925 }
20926
20927 if old_cursor_shape != self.cursor_shape {
20928 cx.emit(EditorEvent::CursorShapeChanged);
20929 }
20930
20931 if old_show_breadcrumbs != self.show_breadcrumbs {
20932 cx.emit(EditorEvent::BreadcrumbsChanged);
20933 }
20934
20935 let project_settings = ProjectSettings::get_global(cx);
20936 self.serialize_dirty_buffers =
20937 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20938
20939 if self.mode.is_full() {
20940 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20941 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20942 if self.show_inline_diagnostics != show_inline_diagnostics {
20943 self.show_inline_diagnostics = show_inline_diagnostics;
20944 self.refresh_inline_diagnostics(false, window, cx);
20945 }
20946
20947 if self.git_blame_inline_enabled != inline_blame_enabled {
20948 self.toggle_git_blame_inline_internal(false, window, cx);
20949 }
20950
20951 let minimap_settings = EditorSettings::get_global(cx).minimap;
20952 if self.minimap_visibility != MinimapVisibility::Disabled {
20953 if self.minimap_visibility.settings_visibility()
20954 != minimap_settings.minimap_enabled()
20955 {
20956 self.set_minimap_visibility(
20957 MinimapVisibility::for_mode(self.mode(), cx),
20958 window,
20959 cx,
20960 );
20961 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20962 minimap_entity.update(cx, |minimap_editor, cx| {
20963 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20964 })
20965 }
20966 }
20967 }
20968
20969 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20970 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20971 }) {
20972 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20973 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20974 }
20975 self.refresh_colors(false, None, window, cx);
20976 }
20977
20978 cx.notify();
20979 }
20980
20981 pub fn set_searchable(&mut self, searchable: bool) {
20982 self.searchable = searchable;
20983 }
20984
20985 pub fn searchable(&self) -> bool {
20986 self.searchable
20987 }
20988
20989 fn open_proposed_changes_editor(
20990 &mut self,
20991 _: &OpenProposedChangesEditor,
20992 window: &mut Window,
20993 cx: &mut Context<Self>,
20994 ) {
20995 let Some(workspace) = self.workspace() else {
20996 cx.propagate();
20997 return;
20998 };
20999
21000 let selections = self.selections.all::<usize>(cx);
21001 let multi_buffer = self.buffer.read(cx);
21002 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21003 let mut new_selections_by_buffer = HashMap::default();
21004 for selection in selections {
21005 for (buffer, range, _) in
21006 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21007 {
21008 let mut range = range.to_point(buffer);
21009 range.start.column = 0;
21010 range.end.column = buffer.line_len(range.end.row);
21011 new_selections_by_buffer
21012 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21013 .or_insert(Vec::new())
21014 .push(range)
21015 }
21016 }
21017
21018 let proposed_changes_buffers = new_selections_by_buffer
21019 .into_iter()
21020 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21021 .collect::<Vec<_>>();
21022 let proposed_changes_editor = cx.new(|cx| {
21023 ProposedChangesEditor::new(
21024 "Proposed changes",
21025 proposed_changes_buffers,
21026 self.project.clone(),
21027 window,
21028 cx,
21029 )
21030 });
21031
21032 window.defer(cx, move |window, cx| {
21033 workspace.update(cx, |workspace, cx| {
21034 workspace.active_pane().update(cx, |pane, cx| {
21035 pane.add_item(
21036 Box::new(proposed_changes_editor),
21037 true,
21038 true,
21039 None,
21040 window,
21041 cx,
21042 );
21043 });
21044 });
21045 });
21046 }
21047
21048 pub fn open_excerpts_in_split(
21049 &mut self,
21050 _: &OpenExcerptsSplit,
21051 window: &mut Window,
21052 cx: &mut Context<Self>,
21053 ) {
21054 self.open_excerpts_common(None, true, window, cx)
21055 }
21056
21057 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21058 self.open_excerpts_common(None, false, window, cx)
21059 }
21060
21061 fn open_excerpts_common(
21062 &mut self,
21063 jump_data: Option<JumpData>,
21064 split: bool,
21065 window: &mut Window,
21066 cx: &mut Context<Self>,
21067 ) {
21068 let Some(workspace) = self.workspace() else {
21069 cx.propagate();
21070 return;
21071 };
21072
21073 if self.buffer.read(cx).is_singleton() {
21074 cx.propagate();
21075 return;
21076 }
21077
21078 let mut new_selections_by_buffer = HashMap::default();
21079 match &jump_data {
21080 Some(JumpData::MultiBufferPoint {
21081 excerpt_id,
21082 position,
21083 anchor,
21084 line_offset_from_top,
21085 }) => {
21086 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21087 if let Some(buffer) = multi_buffer_snapshot
21088 .buffer_id_for_excerpt(*excerpt_id)
21089 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21090 {
21091 let buffer_snapshot = buffer.read(cx).snapshot();
21092 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21093 language::ToPoint::to_point(anchor, &buffer_snapshot)
21094 } else {
21095 buffer_snapshot.clip_point(*position, Bias::Left)
21096 };
21097 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21098 new_selections_by_buffer.insert(
21099 buffer,
21100 (
21101 vec![jump_to_offset..jump_to_offset],
21102 Some(*line_offset_from_top),
21103 ),
21104 );
21105 }
21106 }
21107 Some(JumpData::MultiBufferRow {
21108 row,
21109 line_offset_from_top,
21110 }) => {
21111 let point = MultiBufferPoint::new(row.0, 0);
21112 if let Some((buffer, buffer_point, _)) =
21113 self.buffer.read(cx).point_to_buffer_point(point, cx)
21114 {
21115 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21116 new_selections_by_buffer
21117 .entry(buffer)
21118 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21119 .0
21120 .push(buffer_offset..buffer_offset)
21121 }
21122 }
21123 None => {
21124 let selections = self.selections.all::<usize>(cx);
21125 let multi_buffer = self.buffer.read(cx);
21126 for selection in selections {
21127 for (snapshot, range, _, anchor) in multi_buffer
21128 .snapshot(cx)
21129 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21130 {
21131 if let Some(anchor) = anchor {
21132 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21133 else {
21134 continue;
21135 };
21136 let offset = text::ToOffset::to_offset(
21137 &anchor.text_anchor,
21138 &buffer_handle.read(cx).snapshot(),
21139 );
21140 let range = offset..offset;
21141 new_selections_by_buffer
21142 .entry(buffer_handle)
21143 .or_insert((Vec::new(), None))
21144 .0
21145 .push(range)
21146 } else {
21147 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21148 else {
21149 continue;
21150 };
21151 new_selections_by_buffer
21152 .entry(buffer_handle)
21153 .or_insert((Vec::new(), None))
21154 .0
21155 .push(range)
21156 }
21157 }
21158 }
21159 }
21160 }
21161
21162 new_selections_by_buffer
21163 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21164
21165 if new_selections_by_buffer.is_empty() {
21166 return;
21167 }
21168
21169 // We defer the pane interaction because we ourselves are a workspace item
21170 // and activating a new item causes the pane to call a method on us reentrantly,
21171 // which panics if we're on the stack.
21172 window.defer(cx, move |window, cx| {
21173 workspace.update(cx, |workspace, cx| {
21174 let pane = if split {
21175 workspace.adjacent_pane(window, cx)
21176 } else {
21177 workspace.active_pane().clone()
21178 };
21179
21180 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21181 let editor = buffer
21182 .read(cx)
21183 .file()
21184 .is_none()
21185 .then(|| {
21186 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21187 // so `workspace.open_project_item` will never find them, always opening a new editor.
21188 // Instead, we try to activate the existing editor in the pane first.
21189 let (editor, pane_item_index) =
21190 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21191 let editor = item.downcast::<Editor>()?;
21192 let singleton_buffer =
21193 editor.read(cx).buffer().read(cx).as_singleton()?;
21194 if singleton_buffer == buffer {
21195 Some((editor, i))
21196 } else {
21197 None
21198 }
21199 })?;
21200 pane.update(cx, |pane, cx| {
21201 pane.activate_item(pane_item_index, true, true, window, cx)
21202 });
21203 Some(editor)
21204 })
21205 .flatten()
21206 .unwrap_or_else(|| {
21207 workspace.open_project_item::<Self>(
21208 pane.clone(),
21209 buffer,
21210 true,
21211 true,
21212 window,
21213 cx,
21214 )
21215 });
21216
21217 editor.update(cx, |editor, cx| {
21218 let autoscroll = match scroll_offset {
21219 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21220 None => Autoscroll::newest(),
21221 };
21222 let nav_history = editor.nav_history.take();
21223 editor.change_selections(
21224 SelectionEffects::scroll(autoscroll),
21225 window,
21226 cx,
21227 |s| {
21228 s.select_ranges(ranges);
21229 },
21230 );
21231 editor.nav_history = nav_history;
21232 });
21233 }
21234 })
21235 });
21236 }
21237
21238 // For now, don't allow opening excerpts in buffers that aren't backed by
21239 // regular project files.
21240 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21241 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21242 }
21243
21244 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21245 let snapshot = self.buffer.read(cx).read(cx);
21246 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21247 Some(
21248 ranges
21249 .iter()
21250 .map(move |range| {
21251 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21252 })
21253 .collect(),
21254 )
21255 }
21256
21257 fn selection_replacement_ranges(
21258 &self,
21259 range: Range<OffsetUtf16>,
21260 cx: &mut App,
21261 ) -> Vec<Range<OffsetUtf16>> {
21262 let selections = self.selections.all::<OffsetUtf16>(cx);
21263 let newest_selection = selections
21264 .iter()
21265 .max_by_key(|selection| selection.id)
21266 .unwrap();
21267 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21268 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21269 let snapshot = self.buffer.read(cx).read(cx);
21270 selections
21271 .into_iter()
21272 .map(|mut selection| {
21273 selection.start.0 =
21274 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21275 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21276 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21277 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21278 })
21279 .collect()
21280 }
21281
21282 fn report_editor_event(
21283 &self,
21284 reported_event: ReportEditorEvent,
21285 file_extension: Option<String>,
21286 cx: &App,
21287 ) {
21288 if cfg!(any(test, feature = "test-support")) {
21289 return;
21290 }
21291
21292 let Some(project) = &self.project else { return };
21293
21294 // If None, we are in a file without an extension
21295 let file = self
21296 .buffer
21297 .read(cx)
21298 .as_singleton()
21299 .and_then(|b| b.read(cx).file());
21300 let file_extension = file_extension.or(file
21301 .as_ref()
21302 .and_then(|file| Path::new(file.file_name(cx)).extension())
21303 .and_then(|e| e.to_str())
21304 .map(|a| a.to_string()));
21305
21306 let vim_mode = vim_enabled(cx);
21307
21308 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21309 let copilot_enabled = edit_predictions_provider
21310 == language::language_settings::EditPredictionProvider::Copilot;
21311 let copilot_enabled_for_language = self
21312 .buffer
21313 .read(cx)
21314 .language_settings(cx)
21315 .show_edit_predictions;
21316
21317 let project = project.read(cx);
21318 let event_type = reported_event.event_type();
21319
21320 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21321 telemetry::event!(
21322 event_type,
21323 type = if auto_saved {"autosave"} else {"manual"},
21324 file_extension,
21325 vim_mode,
21326 copilot_enabled,
21327 copilot_enabled_for_language,
21328 edit_predictions_provider,
21329 is_via_ssh = project.is_via_remote_server(),
21330 );
21331 } else {
21332 telemetry::event!(
21333 event_type,
21334 file_extension,
21335 vim_mode,
21336 copilot_enabled,
21337 copilot_enabled_for_language,
21338 edit_predictions_provider,
21339 is_via_ssh = project.is_via_remote_server(),
21340 );
21341 };
21342 }
21343
21344 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21345 /// with each line being an array of {text, highlight} objects.
21346 fn copy_highlight_json(
21347 &mut self,
21348 _: &CopyHighlightJson,
21349 window: &mut Window,
21350 cx: &mut Context<Self>,
21351 ) {
21352 #[derive(Serialize)]
21353 struct Chunk<'a> {
21354 text: String,
21355 highlight: Option<&'a str>,
21356 }
21357
21358 let snapshot = self.buffer.read(cx).snapshot(cx);
21359 let range = self
21360 .selected_text_range(false, window, cx)
21361 .and_then(|selection| {
21362 if selection.range.is_empty() {
21363 None
21364 } else {
21365 Some(selection.range)
21366 }
21367 })
21368 .unwrap_or_else(|| 0..snapshot.len());
21369
21370 let chunks = snapshot.chunks(range, true);
21371 let mut lines = Vec::new();
21372 let mut line: VecDeque<Chunk> = VecDeque::new();
21373
21374 let Some(style) = self.style.as_ref() else {
21375 return;
21376 };
21377
21378 for chunk in chunks {
21379 let highlight = chunk
21380 .syntax_highlight_id
21381 .and_then(|id| id.name(&style.syntax));
21382 let mut chunk_lines = chunk.text.split('\n').peekable();
21383 while let Some(text) = chunk_lines.next() {
21384 let mut merged_with_last_token = false;
21385 if let Some(last_token) = line.back_mut()
21386 && last_token.highlight == highlight
21387 {
21388 last_token.text.push_str(text);
21389 merged_with_last_token = true;
21390 }
21391
21392 if !merged_with_last_token {
21393 line.push_back(Chunk {
21394 text: text.into(),
21395 highlight,
21396 });
21397 }
21398
21399 if chunk_lines.peek().is_some() {
21400 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21401 line.pop_front();
21402 }
21403 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21404 line.pop_back();
21405 }
21406
21407 lines.push(mem::take(&mut line));
21408 }
21409 }
21410 }
21411
21412 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21413 return;
21414 };
21415 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21416 }
21417
21418 pub fn open_context_menu(
21419 &mut self,
21420 _: &OpenContextMenu,
21421 window: &mut Window,
21422 cx: &mut Context<Self>,
21423 ) {
21424 self.request_autoscroll(Autoscroll::newest(), cx);
21425 let position = self.selections.newest_display(cx).start;
21426 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21427 }
21428
21429 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21430 &self.inlay_hint_cache
21431 }
21432
21433 pub fn replay_insert_event(
21434 &mut self,
21435 text: &str,
21436 relative_utf16_range: Option<Range<isize>>,
21437 window: &mut Window,
21438 cx: &mut Context<Self>,
21439 ) {
21440 if !self.input_enabled {
21441 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21442 return;
21443 }
21444 if let Some(relative_utf16_range) = relative_utf16_range {
21445 let selections = self.selections.all::<OffsetUtf16>(cx);
21446 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21447 let new_ranges = selections.into_iter().map(|range| {
21448 let start = OffsetUtf16(
21449 range
21450 .head()
21451 .0
21452 .saturating_add_signed(relative_utf16_range.start),
21453 );
21454 let end = OffsetUtf16(
21455 range
21456 .head()
21457 .0
21458 .saturating_add_signed(relative_utf16_range.end),
21459 );
21460 start..end
21461 });
21462 s.select_ranges(new_ranges);
21463 });
21464 }
21465
21466 self.handle_input(text, window, cx);
21467 }
21468
21469 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21470 let Some(provider) = self.semantics_provider.as_ref() else {
21471 return false;
21472 };
21473
21474 let mut supports = false;
21475 self.buffer().update(cx, |this, cx| {
21476 this.for_each_buffer(|buffer| {
21477 supports |= provider.supports_inlay_hints(buffer, cx);
21478 });
21479 });
21480
21481 supports
21482 }
21483
21484 pub fn is_focused(&self, window: &Window) -> bool {
21485 self.focus_handle.is_focused(window)
21486 }
21487
21488 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21489 cx.emit(EditorEvent::Focused);
21490
21491 if let Some(descendant) = self
21492 .last_focused_descendant
21493 .take()
21494 .and_then(|descendant| descendant.upgrade())
21495 {
21496 window.focus(&descendant);
21497 } else {
21498 if let Some(blame) = self.blame.as_ref() {
21499 blame.update(cx, GitBlame::focus)
21500 }
21501
21502 self.blink_manager.update(cx, BlinkManager::enable);
21503 self.show_cursor_names(window, cx);
21504 self.buffer.update(cx, |buffer, cx| {
21505 buffer.finalize_last_transaction(cx);
21506 if self.leader_id.is_none() {
21507 buffer.set_active_selections(
21508 &self.selections.disjoint_anchors_arc(),
21509 self.selections.line_mode(),
21510 self.cursor_shape,
21511 cx,
21512 );
21513 }
21514 });
21515 }
21516 }
21517
21518 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21519 cx.emit(EditorEvent::FocusedIn)
21520 }
21521
21522 fn handle_focus_out(
21523 &mut self,
21524 event: FocusOutEvent,
21525 _window: &mut Window,
21526 cx: &mut Context<Self>,
21527 ) {
21528 if event.blurred != self.focus_handle {
21529 self.last_focused_descendant = Some(event.blurred);
21530 }
21531 self.selection_drag_state = SelectionDragState::None;
21532 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21533 }
21534
21535 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21536 self.blink_manager.update(cx, BlinkManager::disable);
21537 self.buffer
21538 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21539
21540 if let Some(blame) = self.blame.as_ref() {
21541 blame.update(cx, GitBlame::blur)
21542 }
21543 if !self.hover_state.focused(window, cx) {
21544 hide_hover(self, cx);
21545 }
21546 if !self
21547 .context_menu
21548 .borrow()
21549 .as_ref()
21550 .is_some_and(|context_menu| context_menu.focused(window, cx))
21551 {
21552 self.hide_context_menu(window, cx);
21553 }
21554 self.take_active_edit_prediction(cx);
21555 cx.emit(EditorEvent::Blurred);
21556 cx.notify();
21557 }
21558
21559 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21560 let mut pending: String = window
21561 .pending_input_keystrokes()
21562 .into_iter()
21563 .flatten()
21564 .filter_map(|keystroke| {
21565 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21566 keystroke.key_char.clone()
21567 } else {
21568 None
21569 }
21570 })
21571 .collect();
21572
21573 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21574 pending = "".to_string();
21575 }
21576
21577 let existing_pending = self
21578 .text_highlights::<PendingInput>(cx)
21579 .map(|(_, ranges)| ranges.to_vec());
21580 if existing_pending.is_none() && pending.is_empty() {
21581 return;
21582 }
21583 let transaction =
21584 self.transact(window, cx, |this, window, cx| {
21585 let selections = this.selections.all::<usize>(cx);
21586 let edits = selections
21587 .iter()
21588 .map(|selection| (selection.end..selection.end, pending.clone()));
21589 this.edit(edits, cx);
21590 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21591 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21592 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21593 }));
21594 });
21595 if let Some(existing_ranges) = existing_pending {
21596 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21597 this.edit(edits, cx);
21598 }
21599 });
21600
21601 let snapshot = self.snapshot(window, cx);
21602 let ranges = self
21603 .selections
21604 .all::<usize>(cx)
21605 .into_iter()
21606 .map(|selection| {
21607 snapshot.buffer_snapshot().anchor_after(selection.end)
21608 ..snapshot
21609 .buffer_snapshot()
21610 .anchor_before(selection.end + pending.len())
21611 })
21612 .collect();
21613
21614 if pending.is_empty() {
21615 self.clear_highlights::<PendingInput>(cx);
21616 } else {
21617 self.highlight_text::<PendingInput>(
21618 ranges,
21619 HighlightStyle {
21620 underline: Some(UnderlineStyle {
21621 thickness: px(1.),
21622 color: None,
21623 wavy: false,
21624 }),
21625 ..Default::default()
21626 },
21627 cx,
21628 );
21629 }
21630
21631 self.ime_transaction = self.ime_transaction.or(transaction);
21632 if let Some(transaction) = self.ime_transaction {
21633 self.buffer.update(cx, |buffer, cx| {
21634 buffer.group_until_transaction(transaction, cx);
21635 });
21636 }
21637
21638 if self.text_highlights::<PendingInput>(cx).is_none() {
21639 self.ime_transaction.take();
21640 }
21641 }
21642
21643 pub fn register_action_renderer(
21644 &mut self,
21645 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21646 ) -> Subscription {
21647 let id = self.next_editor_action_id.post_inc();
21648 self.editor_actions
21649 .borrow_mut()
21650 .insert(id, Box::new(listener));
21651
21652 let editor_actions = self.editor_actions.clone();
21653 Subscription::new(move || {
21654 editor_actions.borrow_mut().remove(&id);
21655 })
21656 }
21657
21658 pub fn register_action<A: Action>(
21659 &mut self,
21660 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21661 ) -> Subscription {
21662 let id = self.next_editor_action_id.post_inc();
21663 let listener = Arc::new(listener);
21664 self.editor_actions.borrow_mut().insert(
21665 id,
21666 Box::new(move |_, window, _| {
21667 let listener = listener.clone();
21668 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21669 let action = action.downcast_ref().unwrap();
21670 if phase == DispatchPhase::Bubble {
21671 listener(action, window, cx)
21672 }
21673 })
21674 }),
21675 );
21676
21677 let editor_actions = self.editor_actions.clone();
21678 Subscription::new(move || {
21679 editor_actions.borrow_mut().remove(&id);
21680 })
21681 }
21682
21683 pub fn file_header_size(&self) -> u32 {
21684 FILE_HEADER_HEIGHT
21685 }
21686
21687 pub fn restore(
21688 &mut self,
21689 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21690 window: &mut Window,
21691 cx: &mut Context<Self>,
21692 ) {
21693 let workspace = self.workspace();
21694 let project = self.project();
21695 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21696 let mut tasks = Vec::new();
21697 for (buffer_id, changes) in revert_changes {
21698 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21699 buffer.update(cx, |buffer, cx| {
21700 buffer.edit(
21701 changes
21702 .into_iter()
21703 .map(|(range, text)| (range, text.to_string())),
21704 None,
21705 cx,
21706 );
21707 });
21708
21709 if let Some(project) =
21710 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21711 {
21712 project.update(cx, |project, cx| {
21713 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21714 })
21715 }
21716 }
21717 }
21718 tasks
21719 });
21720 cx.spawn_in(window, async move |_, cx| {
21721 for (buffer, task) in save_tasks {
21722 let result = task.await;
21723 if result.is_err() {
21724 let Some(path) = buffer
21725 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21726 .ok()
21727 else {
21728 continue;
21729 };
21730 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21731 let Some(task) = cx
21732 .update_window_entity(workspace, |workspace, window, cx| {
21733 workspace
21734 .open_path_preview(path, None, false, false, false, window, cx)
21735 })
21736 .ok()
21737 else {
21738 continue;
21739 };
21740 task.await.log_err();
21741 }
21742 }
21743 }
21744 })
21745 .detach();
21746 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21747 selections.refresh()
21748 });
21749 }
21750
21751 pub fn to_pixel_point(
21752 &self,
21753 source: multi_buffer::Anchor,
21754 editor_snapshot: &EditorSnapshot,
21755 window: &mut Window,
21756 ) -> Option<gpui::Point<Pixels>> {
21757 let source_point = source.to_display_point(editor_snapshot);
21758 self.display_to_pixel_point(source_point, editor_snapshot, window)
21759 }
21760
21761 pub fn display_to_pixel_point(
21762 &self,
21763 source: DisplayPoint,
21764 editor_snapshot: &EditorSnapshot,
21765 window: &mut Window,
21766 ) -> Option<gpui::Point<Pixels>> {
21767 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21768 let text_layout_details = self.text_layout_details(window);
21769 let scroll_top = text_layout_details
21770 .scroll_anchor
21771 .scroll_position(editor_snapshot)
21772 .y;
21773
21774 if source.row().as_f64() < scroll_top.floor() {
21775 return None;
21776 }
21777 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21778 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21779 Some(gpui::Point::new(source_x, source_y))
21780 }
21781
21782 pub fn has_visible_completions_menu(&self) -> bool {
21783 !self.edit_prediction_preview_is_active()
21784 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21785 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21786 })
21787 }
21788
21789 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21790 if self.mode.is_minimap() {
21791 return;
21792 }
21793 self.addons
21794 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21795 }
21796
21797 pub fn unregister_addon<T: Addon>(&mut self) {
21798 self.addons.remove(&std::any::TypeId::of::<T>());
21799 }
21800
21801 pub fn addon<T: Addon>(&self) -> Option<&T> {
21802 let type_id = std::any::TypeId::of::<T>();
21803 self.addons
21804 .get(&type_id)
21805 .and_then(|item| item.to_any().downcast_ref::<T>())
21806 }
21807
21808 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21809 let type_id = std::any::TypeId::of::<T>();
21810 self.addons
21811 .get_mut(&type_id)
21812 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21813 }
21814
21815 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21816 let text_layout_details = self.text_layout_details(window);
21817 let style = &text_layout_details.editor_style;
21818 let font_id = window.text_system().resolve_font(&style.text.font());
21819 let font_size = style.text.font_size.to_pixels(window.rem_size());
21820 let line_height = style.text.line_height_in_pixels(window.rem_size());
21821 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21822 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21823
21824 CharacterDimensions {
21825 em_width,
21826 em_advance,
21827 line_height,
21828 }
21829 }
21830
21831 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21832 self.load_diff_task.clone()
21833 }
21834
21835 fn read_metadata_from_db(
21836 &mut self,
21837 item_id: u64,
21838 workspace_id: WorkspaceId,
21839 window: &mut Window,
21840 cx: &mut Context<Editor>,
21841 ) {
21842 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21843 && !self.mode.is_minimap()
21844 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21845 {
21846 let buffer_snapshot = OnceCell::new();
21847
21848 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21849 && !folds.is_empty()
21850 {
21851 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21852 self.fold_ranges(
21853 folds
21854 .into_iter()
21855 .map(|(start, end)| {
21856 snapshot.clip_offset(start, Bias::Left)
21857 ..snapshot.clip_offset(end, Bias::Right)
21858 })
21859 .collect(),
21860 false,
21861 window,
21862 cx,
21863 );
21864 }
21865
21866 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21867 && !selections.is_empty()
21868 {
21869 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21870 // skip adding the initial selection to selection history
21871 self.selection_history.mode = SelectionHistoryMode::Skipping;
21872 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21873 s.select_ranges(selections.into_iter().map(|(start, end)| {
21874 snapshot.clip_offset(start, Bias::Left)
21875 ..snapshot.clip_offset(end, Bias::Right)
21876 }));
21877 });
21878 self.selection_history.mode = SelectionHistoryMode::Normal;
21879 };
21880 }
21881
21882 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21883 }
21884
21885 fn update_lsp_data(
21886 &mut self,
21887 ignore_cache: bool,
21888 for_buffer: Option<BufferId>,
21889 window: &mut Window,
21890 cx: &mut Context<'_, Self>,
21891 ) {
21892 self.pull_diagnostics(for_buffer, window, cx);
21893 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21894 }
21895}
21896
21897fn edit_for_markdown_paste<'a>(
21898 buffer: &MultiBufferSnapshot,
21899 range: Range<usize>,
21900 to_insert: &'a str,
21901 url: Option<url::Url>,
21902) -> (Range<usize>, Cow<'a, str>) {
21903 if url.is_none() {
21904 return (range, Cow::Borrowed(to_insert));
21905 };
21906
21907 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
21908
21909 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
21910 Cow::Borrowed(to_insert)
21911 } else {
21912 Cow::Owned(format!("[{old_text}]({to_insert})"))
21913 };
21914 (range, new_text)
21915}
21916
21917fn vim_enabled(cx: &App) -> bool {
21918 vim_mode_setting::VimModeSetting::try_get(cx)
21919 .map(|vim_mode| vim_mode.0)
21920 .unwrap_or(false)
21921}
21922
21923fn process_completion_for_edit(
21924 completion: &Completion,
21925 intent: CompletionIntent,
21926 buffer: &Entity<Buffer>,
21927 cursor_position: &text::Anchor,
21928 cx: &mut Context<Editor>,
21929) -> CompletionEdit {
21930 let buffer = buffer.read(cx);
21931 let buffer_snapshot = buffer.snapshot();
21932 let (snippet, new_text) = if completion.is_snippet() {
21933 let mut snippet_source = completion.new_text.clone();
21934 // Workaround for typescript language server issues so that methods don't expand within
21935 // strings and functions with type expressions. The previous point is used because the query
21936 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21937 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
21938 let previous_point = if previous_point.column > 0 {
21939 cursor_position.to_previous_offset(&buffer_snapshot)
21940 } else {
21941 cursor_position.to_offset(&buffer_snapshot)
21942 };
21943 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21944 && scope.prefers_label_for_snippet_in_completion()
21945 && let Some(label) = completion.label()
21946 && matches!(
21947 completion.kind(),
21948 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21949 )
21950 {
21951 snippet_source = label;
21952 }
21953 match Snippet::parse(&snippet_source).log_err() {
21954 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21955 None => (None, completion.new_text.clone()),
21956 }
21957 } else {
21958 (None, completion.new_text.clone())
21959 };
21960
21961 let mut range_to_replace = {
21962 let replace_range = &completion.replace_range;
21963 if let CompletionSource::Lsp {
21964 insert_range: Some(insert_range),
21965 ..
21966 } = &completion.source
21967 {
21968 debug_assert_eq!(
21969 insert_range.start, replace_range.start,
21970 "insert_range and replace_range should start at the same position"
21971 );
21972 debug_assert!(
21973 insert_range
21974 .start
21975 .cmp(cursor_position, &buffer_snapshot)
21976 .is_le(),
21977 "insert_range should start before or at cursor position"
21978 );
21979 debug_assert!(
21980 replace_range
21981 .start
21982 .cmp(cursor_position, &buffer_snapshot)
21983 .is_le(),
21984 "replace_range should start before or at cursor position"
21985 );
21986
21987 let should_replace = match intent {
21988 CompletionIntent::CompleteWithInsert => false,
21989 CompletionIntent::CompleteWithReplace => true,
21990 CompletionIntent::Complete | CompletionIntent::Compose => {
21991 let insert_mode =
21992 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21993 .completions
21994 .lsp_insert_mode;
21995 match insert_mode {
21996 LspInsertMode::Insert => false,
21997 LspInsertMode::Replace => true,
21998 LspInsertMode::ReplaceSubsequence => {
21999 let mut text_to_replace = buffer.chars_for_range(
22000 buffer.anchor_before(replace_range.start)
22001 ..buffer.anchor_after(replace_range.end),
22002 );
22003 let mut current_needle = text_to_replace.next();
22004 for haystack_ch in completion.label.text.chars() {
22005 if let Some(needle_ch) = current_needle
22006 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22007 {
22008 current_needle = text_to_replace.next();
22009 }
22010 }
22011 current_needle.is_none()
22012 }
22013 LspInsertMode::ReplaceSuffix => {
22014 if replace_range
22015 .end
22016 .cmp(cursor_position, &buffer_snapshot)
22017 .is_gt()
22018 {
22019 let range_after_cursor = *cursor_position..replace_range.end;
22020 let text_after_cursor = buffer
22021 .text_for_range(
22022 buffer.anchor_before(range_after_cursor.start)
22023 ..buffer.anchor_after(range_after_cursor.end),
22024 )
22025 .collect::<String>()
22026 .to_ascii_lowercase();
22027 completion
22028 .label
22029 .text
22030 .to_ascii_lowercase()
22031 .ends_with(&text_after_cursor)
22032 } else {
22033 true
22034 }
22035 }
22036 }
22037 }
22038 };
22039
22040 if should_replace {
22041 replace_range.clone()
22042 } else {
22043 insert_range.clone()
22044 }
22045 } else {
22046 replace_range.clone()
22047 }
22048 };
22049
22050 if range_to_replace
22051 .end
22052 .cmp(cursor_position, &buffer_snapshot)
22053 .is_lt()
22054 {
22055 range_to_replace.end = *cursor_position;
22056 }
22057
22058 CompletionEdit {
22059 new_text,
22060 replace_range: range_to_replace.to_offset(buffer),
22061 snippet,
22062 }
22063}
22064
22065struct CompletionEdit {
22066 new_text: String,
22067 replace_range: Range<usize>,
22068 snippet: Option<Snippet>,
22069}
22070
22071fn insert_extra_newline_brackets(
22072 buffer: &MultiBufferSnapshot,
22073 range: Range<usize>,
22074 language: &language::LanguageScope,
22075) -> bool {
22076 let leading_whitespace_len = buffer
22077 .reversed_chars_at(range.start)
22078 .take_while(|c| c.is_whitespace() && *c != '\n')
22079 .map(|c| c.len_utf8())
22080 .sum::<usize>();
22081 let trailing_whitespace_len = buffer
22082 .chars_at(range.end)
22083 .take_while(|c| c.is_whitespace() && *c != '\n')
22084 .map(|c| c.len_utf8())
22085 .sum::<usize>();
22086 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22087
22088 language.brackets().any(|(pair, enabled)| {
22089 let pair_start = pair.start.trim_end();
22090 let pair_end = pair.end.trim_start();
22091
22092 enabled
22093 && pair.newline
22094 && buffer.contains_str_at(range.end, pair_end)
22095 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22096 })
22097}
22098
22099fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22100 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22101 [(buffer, range, _)] => (*buffer, range.clone()),
22102 _ => return false,
22103 };
22104 let pair = {
22105 let mut result: Option<BracketMatch> = None;
22106
22107 for pair in buffer
22108 .all_bracket_ranges(range.clone())
22109 .filter(move |pair| {
22110 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22111 })
22112 {
22113 let len = pair.close_range.end - pair.open_range.start;
22114
22115 if let Some(existing) = &result {
22116 let existing_len = existing.close_range.end - existing.open_range.start;
22117 if len > existing_len {
22118 continue;
22119 }
22120 }
22121
22122 result = Some(pair);
22123 }
22124
22125 result
22126 };
22127 let Some(pair) = pair else {
22128 return false;
22129 };
22130 pair.newline_only
22131 && buffer
22132 .chars_for_range(pair.open_range.end..range.start)
22133 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22134 .all(|c| c.is_whitespace() && c != '\n')
22135}
22136
22137fn update_uncommitted_diff_for_buffer(
22138 editor: Entity<Editor>,
22139 project: &Entity<Project>,
22140 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22141 buffer: Entity<MultiBuffer>,
22142 cx: &mut App,
22143) -> Task<()> {
22144 let mut tasks = Vec::new();
22145 project.update(cx, |project, cx| {
22146 for buffer in buffers {
22147 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22148 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22149 }
22150 }
22151 });
22152 cx.spawn(async move |cx| {
22153 let diffs = future::join_all(tasks).await;
22154 if editor
22155 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22156 .unwrap_or(false)
22157 {
22158 return;
22159 }
22160
22161 buffer
22162 .update(cx, |buffer, cx| {
22163 for diff in diffs.into_iter().flatten() {
22164 buffer.add_diff(diff, cx);
22165 }
22166 })
22167 .ok();
22168 })
22169}
22170
22171fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22172 let tab_size = tab_size.get() as usize;
22173 let mut width = offset;
22174
22175 for ch in text.chars() {
22176 width += if ch == '\t' {
22177 tab_size - (width % tab_size)
22178 } else {
22179 1
22180 };
22181 }
22182
22183 width - offset
22184}
22185
22186#[cfg(test)]
22187mod tests {
22188 use super::*;
22189
22190 #[test]
22191 fn test_string_size_with_expanded_tabs() {
22192 let nz = |val| NonZeroU32::new(val).unwrap();
22193 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22194 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22195 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22196 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22197 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22198 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22199 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22200 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22201 }
22202}
22203
22204/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22205struct WordBreakingTokenizer<'a> {
22206 input: &'a str,
22207}
22208
22209impl<'a> WordBreakingTokenizer<'a> {
22210 fn new(input: &'a str) -> Self {
22211 Self { input }
22212 }
22213}
22214
22215fn is_char_ideographic(ch: char) -> bool {
22216 use unicode_script::Script::*;
22217 use unicode_script::UnicodeScript;
22218 matches!(ch.script(), Han | Tangut | Yi)
22219}
22220
22221fn is_grapheme_ideographic(text: &str) -> bool {
22222 text.chars().any(is_char_ideographic)
22223}
22224
22225fn is_grapheme_whitespace(text: &str) -> bool {
22226 text.chars().any(|x| x.is_whitespace())
22227}
22228
22229fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22230 text.chars()
22231 .next()
22232 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22233}
22234
22235#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22236enum WordBreakToken<'a> {
22237 Word { token: &'a str, grapheme_len: usize },
22238 InlineWhitespace { token: &'a str, grapheme_len: usize },
22239 Newline,
22240}
22241
22242impl<'a> Iterator for WordBreakingTokenizer<'a> {
22243 /// Yields a span, the count of graphemes in the token, and whether it was
22244 /// whitespace. Note that it also breaks at word boundaries.
22245 type Item = WordBreakToken<'a>;
22246
22247 fn next(&mut self) -> Option<Self::Item> {
22248 use unicode_segmentation::UnicodeSegmentation;
22249 if self.input.is_empty() {
22250 return None;
22251 }
22252
22253 let mut iter = self.input.graphemes(true).peekable();
22254 let mut offset = 0;
22255 let mut grapheme_len = 0;
22256 if let Some(first_grapheme) = iter.next() {
22257 let is_newline = first_grapheme == "\n";
22258 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22259 offset += first_grapheme.len();
22260 grapheme_len += 1;
22261 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22262 if let Some(grapheme) = iter.peek().copied()
22263 && should_stay_with_preceding_ideograph(grapheme)
22264 {
22265 offset += grapheme.len();
22266 grapheme_len += 1;
22267 }
22268 } else {
22269 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22270 let mut next_word_bound = words.peek().copied();
22271 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22272 next_word_bound = words.next();
22273 }
22274 while let Some(grapheme) = iter.peek().copied() {
22275 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22276 break;
22277 };
22278 if is_grapheme_whitespace(grapheme) != is_whitespace
22279 || (grapheme == "\n") != is_newline
22280 {
22281 break;
22282 };
22283 offset += grapheme.len();
22284 grapheme_len += 1;
22285 iter.next();
22286 }
22287 }
22288 let token = &self.input[..offset];
22289 self.input = &self.input[offset..];
22290 if token == "\n" {
22291 Some(WordBreakToken::Newline)
22292 } else if is_whitespace {
22293 Some(WordBreakToken::InlineWhitespace {
22294 token,
22295 grapheme_len,
22296 })
22297 } else {
22298 Some(WordBreakToken::Word {
22299 token,
22300 grapheme_len,
22301 })
22302 }
22303 } else {
22304 None
22305 }
22306 }
22307}
22308
22309#[test]
22310fn test_word_breaking_tokenizer() {
22311 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22312 ("", &[]),
22313 (" ", &[whitespace(" ", 2)]),
22314 ("Ʒ", &[word("Ʒ", 1)]),
22315 ("Ǽ", &[word("Ǽ", 1)]),
22316 ("⋑", &[word("⋑", 1)]),
22317 ("⋑⋑", &[word("⋑⋑", 2)]),
22318 (
22319 "原理,进而",
22320 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22321 ),
22322 (
22323 "hello world",
22324 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22325 ),
22326 (
22327 "hello, world",
22328 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22329 ),
22330 (
22331 " hello world",
22332 &[
22333 whitespace(" ", 2),
22334 word("hello", 5),
22335 whitespace(" ", 1),
22336 word("world", 5),
22337 ],
22338 ),
22339 (
22340 "这是什么 \n 钢笔",
22341 &[
22342 word("这", 1),
22343 word("是", 1),
22344 word("什", 1),
22345 word("么", 1),
22346 whitespace(" ", 1),
22347 newline(),
22348 whitespace(" ", 1),
22349 word("钢", 1),
22350 word("笔", 1),
22351 ],
22352 ),
22353 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22354 ];
22355
22356 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22357 WordBreakToken::Word {
22358 token,
22359 grapheme_len,
22360 }
22361 }
22362
22363 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22364 WordBreakToken::InlineWhitespace {
22365 token,
22366 grapheme_len,
22367 }
22368 }
22369
22370 fn newline() -> WordBreakToken<'static> {
22371 WordBreakToken::Newline
22372 }
22373
22374 for (input, result) in tests {
22375 assert_eq!(
22376 WordBreakingTokenizer::new(input)
22377 .collect::<Vec<_>>()
22378 .as_slice(),
22379 *result,
22380 );
22381 }
22382}
22383
22384fn wrap_with_prefix(
22385 first_line_prefix: String,
22386 subsequent_lines_prefix: String,
22387 unwrapped_text: String,
22388 wrap_column: usize,
22389 tab_size: NonZeroU32,
22390 preserve_existing_whitespace: bool,
22391) -> String {
22392 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22393 let subsequent_lines_prefix_len =
22394 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22395 let mut wrapped_text = String::new();
22396 let mut current_line = first_line_prefix;
22397 let mut is_first_line = true;
22398
22399 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22400 let mut current_line_len = first_line_prefix_len;
22401 let mut in_whitespace = false;
22402 for token in tokenizer {
22403 let have_preceding_whitespace = in_whitespace;
22404 match token {
22405 WordBreakToken::Word {
22406 token,
22407 grapheme_len,
22408 } => {
22409 in_whitespace = false;
22410 let current_prefix_len = if is_first_line {
22411 first_line_prefix_len
22412 } else {
22413 subsequent_lines_prefix_len
22414 };
22415 if current_line_len + grapheme_len > wrap_column
22416 && current_line_len != current_prefix_len
22417 {
22418 wrapped_text.push_str(current_line.trim_end());
22419 wrapped_text.push('\n');
22420 is_first_line = false;
22421 current_line = subsequent_lines_prefix.clone();
22422 current_line_len = subsequent_lines_prefix_len;
22423 }
22424 current_line.push_str(token);
22425 current_line_len += grapheme_len;
22426 }
22427 WordBreakToken::InlineWhitespace {
22428 mut token,
22429 mut grapheme_len,
22430 } => {
22431 in_whitespace = true;
22432 if have_preceding_whitespace && !preserve_existing_whitespace {
22433 continue;
22434 }
22435 if !preserve_existing_whitespace {
22436 // Keep a single whitespace grapheme as-is
22437 if let Some(first) =
22438 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22439 {
22440 token = first;
22441 } else {
22442 token = " ";
22443 }
22444 grapheme_len = 1;
22445 }
22446 let current_prefix_len = if is_first_line {
22447 first_line_prefix_len
22448 } else {
22449 subsequent_lines_prefix_len
22450 };
22451 if current_line_len + grapheme_len > wrap_column {
22452 wrapped_text.push_str(current_line.trim_end());
22453 wrapped_text.push('\n');
22454 is_first_line = false;
22455 current_line = subsequent_lines_prefix.clone();
22456 current_line_len = subsequent_lines_prefix_len;
22457 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22458 current_line.push_str(token);
22459 current_line_len += grapheme_len;
22460 }
22461 }
22462 WordBreakToken::Newline => {
22463 in_whitespace = true;
22464 let current_prefix_len = if is_first_line {
22465 first_line_prefix_len
22466 } else {
22467 subsequent_lines_prefix_len
22468 };
22469 if preserve_existing_whitespace {
22470 wrapped_text.push_str(current_line.trim_end());
22471 wrapped_text.push('\n');
22472 is_first_line = false;
22473 current_line = subsequent_lines_prefix.clone();
22474 current_line_len = subsequent_lines_prefix_len;
22475 } else if have_preceding_whitespace {
22476 continue;
22477 } else if current_line_len + 1 > wrap_column
22478 && current_line_len != current_prefix_len
22479 {
22480 wrapped_text.push_str(current_line.trim_end());
22481 wrapped_text.push('\n');
22482 is_first_line = false;
22483 current_line = subsequent_lines_prefix.clone();
22484 current_line_len = subsequent_lines_prefix_len;
22485 } else if current_line_len != current_prefix_len {
22486 current_line.push(' ');
22487 current_line_len += 1;
22488 }
22489 }
22490 }
22491 }
22492
22493 if !current_line.is_empty() {
22494 wrapped_text.push_str(¤t_line);
22495 }
22496 wrapped_text
22497}
22498
22499#[test]
22500fn test_wrap_with_prefix() {
22501 assert_eq!(
22502 wrap_with_prefix(
22503 "# ".to_string(),
22504 "# ".to_string(),
22505 "abcdefg".to_string(),
22506 4,
22507 NonZeroU32::new(4).unwrap(),
22508 false,
22509 ),
22510 "# abcdefg"
22511 );
22512 assert_eq!(
22513 wrap_with_prefix(
22514 "".to_string(),
22515 "".to_string(),
22516 "\thello world".to_string(),
22517 8,
22518 NonZeroU32::new(4).unwrap(),
22519 false,
22520 ),
22521 "hello\nworld"
22522 );
22523 assert_eq!(
22524 wrap_with_prefix(
22525 "// ".to_string(),
22526 "// ".to_string(),
22527 "xx \nyy zz aa bb cc".to_string(),
22528 12,
22529 NonZeroU32::new(4).unwrap(),
22530 false,
22531 ),
22532 "// xx yy zz\n// aa bb cc"
22533 );
22534 assert_eq!(
22535 wrap_with_prefix(
22536 String::new(),
22537 String::new(),
22538 "这是什么 \n 钢笔".to_string(),
22539 3,
22540 NonZeroU32::new(4).unwrap(),
22541 false,
22542 ),
22543 "这是什\n么 钢\n笔"
22544 );
22545 assert_eq!(
22546 wrap_with_prefix(
22547 String::new(),
22548 String::new(),
22549 format!("foo{}bar", '\u{2009}'), // thin space
22550 80,
22551 NonZeroU32::new(4).unwrap(),
22552 false,
22553 ),
22554 format!("foo{}bar", '\u{2009}')
22555 );
22556}
22557
22558pub trait CollaborationHub {
22559 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22560 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22561 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22562}
22563
22564impl CollaborationHub for Entity<Project> {
22565 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22566 self.read(cx).collaborators()
22567 }
22568
22569 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22570 self.read(cx).user_store().read(cx).participant_indices()
22571 }
22572
22573 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22574 let this = self.read(cx);
22575 let user_ids = this.collaborators().values().map(|c| c.user_id);
22576 this.user_store().read(cx).participant_names(user_ids, cx)
22577 }
22578}
22579
22580pub trait SemanticsProvider {
22581 fn hover(
22582 &self,
22583 buffer: &Entity<Buffer>,
22584 position: text::Anchor,
22585 cx: &mut App,
22586 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22587
22588 fn inline_values(
22589 &self,
22590 buffer_handle: Entity<Buffer>,
22591 range: Range<text::Anchor>,
22592 cx: &mut App,
22593 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22594
22595 fn inlay_hints(
22596 &self,
22597 buffer_handle: Entity<Buffer>,
22598 range: Range<text::Anchor>,
22599 cx: &mut App,
22600 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22601
22602 fn resolve_inlay_hint(
22603 &self,
22604 hint: InlayHint,
22605 buffer_handle: Entity<Buffer>,
22606 server_id: LanguageServerId,
22607 cx: &mut App,
22608 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22609
22610 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22611
22612 fn document_highlights(
22613 &self,
22614 buffer: &Entity<Buffer>,
22615 position: text::Anchor,
22616 cx: &mut App,
22617 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22618
22619 fn definitions(
22620 &self,
22621 buffer: &Entity<Buffer>,
22622 position: text::Anchor,
22623 kind: GotoDefinitionKind,
22624 cx: &mut App,
22625 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22626
22627 fn range_for_rename(
22628 &self,
22629 buffer: &Entity<Buffer>,
22630 position: text::Anchor,
22631 cx: &mut App,
22632 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22633
22634 fn perform_rename(
22635 &self,
22636 buffer: &Entity<Buffer>,
22637 position: text::Anchor,
22638 new_name: String,
22639 cx: &mut App,
22640 ) -> Option<Task<Result<ProjectTransaction>>>;
22641}
22642
22643pub trait CompletionProvider {
22644 fn completions(
22645 &self,
22646 excerpt_id: ExcerptId,
22647 buffer: &Entity<Buffer>,
22648 buffer_position: text::Anchor,
22649 trigger: CompletionContext,
22650 window: &mut Window,
22651 cx: &mut Context<Editor>,
22652 ) -> Task<Result<Vec<CompletionResponse>>>;
22653
22654 fn resolve_completions(
22655 &self,
22656 _buffer: Entity<Buffer>,
22657 _completion_indices: Vec<usize>,
22658 _completions: Rc<RefCell<Box<[Completion]>>>,
22659 _cx: &mut Context<Editor>,
22660 ) -> Task<Result<bool>> {
22661 Task::ready(Ok(false))
22662 }
22663
22664 fn apply_additional_edits_for_completion(
22665 &self,
22666 _buffer: Entity<Buffer>,
22667 _completions: Rc<RefCell<Box<[Completion]>>>,
22668 _completion_index: usize,
22669 _push_to_history: bool,
22670 _cx: &mut Context<Editor>,
22671 ) -> Task<Result<Option<language::Transaction>>> {
22672 Task::ready(Ok(None))
22673 }
22674
22675 fn is_completion_trigger(
22676 &self,
22677 buffer: &Entity<Buffer>,
22678 position: language::Anchor,
22679 text: &str,
22680 trigger_in_words: bool,
22681 menu_is_open: bool,
22682 cx: &mut Context<Editor>,
22683 ) -> bool;
22684
22685 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22686
22687 fn sort_completions(&self) -> bool {
22688 true
22689 }
22690
22691 fn filter_completions(&self) -> bool {
22692 true
22693 }
22694}
22695
22696pub trait CodeActionProvider {
22697 fn id(&self) -> Arc<str>;
22698
22699 fn code_actions(
22700 &self,
22701 buffer: &Entity<Buffer>,
22702 range: Range<text::Anchor>,
22703 window: &mut Window,
22704 cx: &mut App,
22705 ) -> Task<Result<Vec<CodeAction>>>;
22706
22707 fn apply_code_action(
22708 &self,
22709 buffer_handle: Entity<Buffer>,
22710 action: CodeAction,
22711 excerpt_id: ExcerptId,
22712 push_to_history: bool,
22713 window: &mut Window,
22714 cx: &mut App,
22715 ) -> Task<Result<ProjectTransaction>>;
22716}
22717
22718impl CodeActionProvider for Entity<Project> {
22719 fn id(&self) -> Arc<str> {
22720 "project".into()
22721 }
22722
22723 fn code_actions(
22724 &self,
22725 buffer: &Entity<Buffer>,
22726 range: Range<text::Anchor>,
22727 _window: &mut Window,
22728 cx: &mut App,
22729 ) -> Task<Result<Vec<CodeAction>>> {
22730 self.update(cx, |project, cx| {
22731 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22732 let code_actions = project.code_actions(buffer, range, None, cx);
22733 cx.background_spawn(async move {
22734 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22735 Ok(code_lens_actions
22736 .context("code lens fetch")?
22737 .into_iter()
22738 .flatten()
22739 .chain(
22740 code_actions
22741 .context("code action fetch")?
22742 .into_iter()
22743 .flatten(),
22744 )
22745 .collect())
22746 })
22747 })
22748 }
22749
22750 fn apply_code_action(
22751 &self,
22752 buffer_handle: Entity<Buffer>,
22753 action: CodeAction,
22754 _excerpt_id: ExcerptId,
22755 push_to_history: bool,
22756 _window: &mut Window,
22757 cx: &mut App,
22758 ) -> Task<Result<ProjectTransaction>> {
22759 self.update(cx, |project, cx| {
22760 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22761 })
22762 }
22763}
22764
22765fn snippet_completions(
22766 project: &Project,
22767 buffer: &Entity<Buffer>,
22768 buffer_position: text::Anchor,
22769 cx: &mut App,
22770) -> Task<Result<CompletionResponse>> {
22771 let languages = buffer.read(cx).languages_at(buffer_position);
22772 let snippet_store = project.snippets().read(cx);
22773
22774 let scopes: Vec<_> = languages
22775 .iter()
22776 .filter_map(|language| {
22777 let language_name = language.lsp_id();
22778 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22779
22780 if snippets.is_empty() {
22781 None
22782 } else {
22783 Some((language.default_scope(), snippets))
22784 }
22785 })
22786 .collect();
22787
22788 if scopes.is_empty() {
22789 return Task::ready(Ok(CompletionResponse {
22790 completions: vec![],
22791 display_options: CompletionDisplayOptions::default(),
22792 is_incomplete: false,
22793 }));
22794 }
22795
22796 let snapshot = buffer.read(cx).text_snapshot();
22797 let executor = cx.background_executor().clone();
22798
22799 cx.background_spawn(async move {
22800 let mut is_incomplete = false;
22801 let mut completions: Vec<Completion> = Vec::new();
22802 for (scope, snippets) in scopes.into_iter() {
22803 let classifier =
22804 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22805
22806 const MAX_WORD_PREFIX_LEN: usize = 128;
22807 let last_word: String = snapshot
22808 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22809 .take(MAX_WORD_PREFIX_LEN)
22810 .take_while(|c| classifier.is_word(*c))
22811 .collect::<String>()
22812 .chars()
22813 .rev()
22814 .collect();
22815
22816 if last_word.is_empty() {
22817 return Ok(CompletionResponse {
22818 completions: vec![],
22819 display_options: CompletionDisplayOptions::default(),
22820 is_incomplete: true,
22821 });
22822 }
22823
22824 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22825 let to_lsp = |point: &text::Anchor| {
22826 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22827 point_to_lsp(end)
22828 };
22829 let lsp_end = to_lsp(&buffer_position);
22830
22831 let candidates = snippets
22832 .iter()
22833 .enumerate()
22834 .flat_map(|(ix, snippet)| {
22835 snippet
22836 .prefix
22837 .iter()
22838 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22839 })
22840 .collect::<Vec<StringMatchCandidate>>();
22841
22842 const MAX_RESULTS: usize = 100;
22843 let mut matches = fuzzy::match_strings(
22844 &candidates,
22845 &last_word,
22846 last_word.chars().any(|c| c.is_uppercase()),
22847 true,
22848 MAX_RESULTS,
22849 &Default::default(),
22850 executor.clone(),
22851 )
22852 .await;
22853
22854 if matches.len() >= MAX_RESULTS {
22855 is_incomplete = true;
22856 }
22857
22858 // Remove all candidates where the query's start does not match the start of any word in the candidate
22859 if let Some(query_start) = last_word.chars().next() {
22860 matches.retain(|string_match| {
22861 split_words(&string_match.string).any(|word| {
22862 // Check that the first codepoint of the word as lowercase matches the first
22863 // codepoint of the query as lowercase
22864 word.chars()
22865 .flat_map(|codepoint| codepoint.to_lowercase())
22866 .zip(query_start.to_lowercase())
22867 .all(|(word_cp, query_cp)| word_cp == query_cp)
22868 })
22869 });
22870 }
22871
22872 let matched_strings = matches
22873 .into_iter()
22874 .map(|m| m.string)
22875 .collect::<HashSet<_>>();
22876
22877 completions.extend(snippets.iter().filter_map(|snippet| {
22878 let matching_prefix = snippet
22879 .prefix
22880 .iter()
22881 .find(|prefix| matched_strings.contains(*prefix))?;
22882 let start = as_offset - last_word.len();
22883 let start = snapshot.anchor_before(start);
22884 let range = start..buffer_position;
22885 let lsp_start = to_lsp(&start);
22886 let lsp_range = lsp::Range {
22887 start: lsp_start,
22888 end: lsp_end,
22889 };
22890 Some(Completion {
22891 replace_range: range,
22892 new_text: snippet.body.clone(),
22893 source: CompletionSource::Lsp {
22894 insert_range: None,
22895 server_id: LanguageServerId(usize::MAX),
22896 resolved: true,
22897 lsp_completion: Box::new(lsp::CompletionItem {
22898 label: snippet.prefix.first().unwrap().clone(),
22899 kind: Some(CompletionItemKind::SNIPPET),
22900 label_details: snippet.description.as_ref().map(|description| {
22901 lsp::CompletionItemLabelDetails {
22902 detail: Some(description.clone()),
22903 description: None,
22904 }
22905 }),
22906 insert_text_format: Some(InsertTextFormat::SNIPPET),
22907 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22908 lsp::InsertReplaceEdit {
22909 new_text: snippet.body.clone(),
22910 insert: lsp_range,
22911 replace: lsp_range,
22912 },
22913 )),
22914 filter_text: Some(snippet.body.clone()),
22915 sort_text: Some(char::MAX.to_string()),
22916 ..lsp::CompletionItem::default()
22917 }),
22918 lsp_defaults: None,
22919 },
22920 label: CodeLabel {
22921 text: matching_prefix.clone(),
22922 runs: Vec::new(),
22923 filter_range: 0..matching_prefix.len(),
22924 },
22925 icon_path: None,
22926 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22927 single_line: snippet.name.clone().into(),
22928 plain_text: snippet
22929 .description
22930 .clone()
22931 .map(|description| description.into()),
22932 }),
22933 insert_text_mode: None,
22934 confirm: None,
22935 })
22936 }))
22937 }
22938
22939 Ok(CompletionResponse {
22940 completions,
22941 display_options: CompletionDisplayOptions::default(),
22942 is_incomplete,
22943 })
22944 })
22945}
22946
22947impl CompletionProvider for Entity<Project> {
22948 fn completions(
22949 &self,
22950 _excerpt_id: ExcerptId,
22951 buffer: &Entity<Buffer>,
22952 buffer_position: text::Anchor,
22953 options: CompletionContext,
22954 _window: &mut Window,
22955 cx: &mut Context<Editor>,
22956 ) -> Task<Result<Vec<CompletionResponse>>> {
22957 self.update(cx, |project, cx| {
22958 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22959 let project_completions = project.completions(buffer, buffer_position, options, cx);
22960 cx.background_spawn(async move {
22961 let mut responses = project_completions.await?;
22962 let snippets = snippets.await?;
22963 if !snippets.completions.is_empty() {
22964 responses.push(snippets);
22965 }
22966 Ok(responses)
22967 })
22968 })
22969 }
22970
22971 fn resolve_completions(
22972 &self,
22973 buffer: Entity<Buffer>,
22974 completion_indices: Vec<usize>,
22975 completions: Rc<RefCell<Box<[Completion]>>>,
22976 cx: &mut Context<Editor>,
22977 ) -> Task<Result<bool>> {
22978 self.update(cx, |project, cx| {
22979 project.lsp_store().update(cx, |lsp_store, cx| {
22980 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22981 })
22982 })
22983 }
22984
22985 fn apply_additional_edits_for_completion(
22986 &self,
22987 buffer: Entity<Buffer>,
22988 completions: Rc<RefCell<Box<[Completion]>>>,
22989 completion_index: usize,
22990 push_to_history: bool,
22991 cx: &mut Context<Editor>,
22992 ) -> Task<Result<Option<language::Transaction>>> {
22993 self.update(cx, |project, cx| {
22994 project.lsp_store().update(cx, |lsp_store, cx| {
22995 lsp_store.apply_additional_edits_for_completion(
22996 buffer,
22997 completions,
22998 completion_index,
22999 push_to_history,
23000 cx,
23001 )
23002 })
23003 })
23004 }
23005
23006 fn is_completion_trigger(
23007 &self,
23008 buffer: &Entity<Buffer>,
23009 position: language::Anchor,
23010 text: &str,
23011 trigger_in_words: bool,
23012 menu_is_open: bool,
23013 cx: &mut Context<Editor>,
23014 ) -> bool {
23015 let mut chars = text.chars();
23016 let char = if let Some(char) = chars.next() {
23017 char
23018 } else {
23019 return false;
23020 };
23021 if chars.next().is_some() {
23022 return false;
23023 }
23024
23025 let buffer = buffer.read(cx);
23026 let snapshot = buffer.snapshot();
23027 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23028 return false;
23029 }
23030 let classifier = snapshot
23031 .char_classifier_at(position)
23032 .scope_context(Some(CharScopeContext::Completion));
23033 if trigger_in_words && classifier.is_word(char) {
23034 return true;
23035 }
23036
23037 buffer.completion_triggers().contains(text)
23038 }
23039}
23040
23041impl SemanticsProvider for Entity<Project> {
23042 fn hover(
23043 &self,
23044 buffer: &Entity<Buffer>,
23045 position: text::Anchor,
23046 cx: &mut App,
23047 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23048 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23049 }
23050
23051 fn document_highlights(
23052 &self,
23053 buffer: &Entity<Buffer>,
23054 position: text::Anchor,
23055 cx: &mut App,
23056 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23057 Some(self.update(cx, |project, cx| {
23058 project.document_highlights(buffer, position, cx)
23059 }))
23060 }
23061
23062 fn definitions(
23063 &self,
23064 buffer: &Entity<Buffer>,
23065 position: text::Anchor,
23066 kind: GotoDefinitionKind,
23067 cx: &mut App,
23068 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23069 Some(self.update(cx, |project, cx| match kind {
23070 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23071 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23072 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23073 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23074 }))
23075 }
23076
23077 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23078 self.update(cx, |project, cx| {
23079 if project
23080 .active_debug_session(cx)
23081 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23082 {
23083 return true;
23084 }
23085
23086 buffer.update(cx, |buffer, cx| {
23087 project.any_language_server_supports_inlay_hints(buffer, cx)
23088 })
23089 })
23090 }
23091
23092 fn inline_values(
23093 &self,
23094 buffer_handle: Entity<Buffer>,
23095 range: Range<text::Anchor>,
23096 cx: &mut App,
23097 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23098 self.update(cx, |project, cx| {
23099 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23100
23101 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23102 })
23103 }
23104
23105 fn inlay_hints(
23106 &self,
23107 buffer_handle: Entity<Buffer>,
23108 range: Range<text::Anchor>,
23109 cx: &mut App,
23110 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23111 Some(self.update(cx, |project, cx| {
23112 project.inlay_hints(buffer_handle, range, cx)
23113 }))
23114 }
23115
23116 fn resolve_inlay_hint(
23117 &self,
23118 hint: InlayHint,
23119 buffer_handle: Entity<Buffer>,
23120 server_id: LanguageServerId,
23121 cx: &mut App,
23122 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23123 Some(self.update(cx, |project, cx| {
23124 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23125 }))
23126 }
23127
23128 fn range_for_rename(
23129 &self,
23130 buffer: &Entity<Buffer>,
23131 position: text::Anchor,
23132 cx: &mut App,
23133 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23134 Some(self.update(cx, |project, cx| {
23135 let buffer = buffer.clone();
23136 let task = project.prepare_rename(buffer.clone(), position, cx);
23137 cx.spawn(async move |_, cx| {
23138 Ok(match task.await? {
23139 PrepareRenameResponse::Success(range) => Some(range),
23140 PrepareRenameResponse::InvalidPosition => None,
23141 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23142 // Fallback on using TreeSitter info to determine identifier range
23143 buffer.read_with(cx, |buffer, _| {
23144 let snapshot = buffer.snapshot();
23145 let (range, kind) = snapshot.surrounding_word(position, None);
23146 if kind != Some(CharKind::Word) {
23147 return None;
23148 }
23149 Some(
23150 snapshot.anchor_before(range.start)
23151 ..snapshot.anchor_after(range.end),
23152 )
23153 })?
23154 }
23155 })
23156 })
23157 }))
23158 }
23159
23160 fn perform_rename(
23161 &self,
23162 buffer: &Entity<Buffer>,
23163 position: text::Anchor,
23164 new_name: String,
23165 cx: &mut App,
23166 ) -> Option<Task<Result<ProjectTransaction>>> {
23167 Some(self.update(cx, |project, cx| {
23168 project.perform_rename(buffer.clone(), position, new_name, cx)
23169 }))
23170 }
23171}
23172
23173fn inlay_hint_settings(
23174 location: Anchor,
23175 snapshot: &MultiBufferSnapshot,
23176 cx: &mut Context<Editor>,
23177) -> InlayHintSettings {
23178 let file = snapshot.file_at(location);
23179 let language = snapshot.language_at(location).map(|l| l.name());
23180 language_settings(language, file, cx).inlay_hints
23181}
23182
23183fn consume_contiguous_rows(
23184 contiguous_row_selections: &mut Vec<Selection<Point>>,
23185 selection: &Selection<Point>,
23186 display_map: &DisplaySnapshot,
23187 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23188) -> (MultiBufferRow, MultiBufferRow) {
23189 contiguous_row_selections.push(selection.clone());
23190 let start_row = starting_row(selection, display_map);
23191 let mut end_row = ending_row(selection, display_map);
23192
23193 while let Some(next_selection) = selections.peek() {
23194 if next_selection.start.row <= end_row.0 {
23195 end_row = ending_row(next_selection, display_map);
23196 contiguous_row_selections.push(selections.next().unwrap().clone());
23197 } else {
23198 break;
23199 }
23200 }
23201 (start_row, end_row)
23202}
23203
23204fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23205 if selection.start.column > 0 {
23206 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23207 } else {
23208 MultiBufferRow(selection.start.row)
23209 }
23210}
23211
23212fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23213 if next_selection.end.column > 0 || next_selection.is_empty() {
23214 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23215 } else {
23216 MultiBufferRow(next_selection.end.row)
23217 }
23218}
23219
23220impl EditorSnapshot {
23221 pub fn remote_selections_in_range<'a>(
23222 &'a self,
23223 range: &'a Range<Anchor>,
23224 collaboration_hub: &dyn CollaborationHub,
23225 cx: &'a App,
23226 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23227 let participant_names = collaboration_hub.user_names(cx);
23228 let participant_indices = collaboration_hub.user_participant_indices(cx);
23229 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23230 let collaborators_by_replica_id = collaborators_by_peer_id
23231 .values()
23232 .map(|collaborator| (collaborator.replica_id, collaborator))
23233 .collect::<HashMap<_, _>>();
23234 self.buffer_snapshot()
23235 .selections_in_range(range, false)
23236 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23237 if replica_id == AGENT_REPLICA_ID {
23238 Some(RemoteSelection {
23239 replica_id,
23240 selection,
23241 cursor_shape,
23242 line_mode,
23243 collaborator_id: CollaboratorId::Agent,
23244 user_name: Some("Agent".into()),
23245 color: cx.theme().players().agent(),
23246 })
23247 } else {
23248 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23249 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23250 let user_name = participant_names.get(&collaborator.user_id).cloned();
23251 Some(RemoteSelection {
23252 replica_id,
23253 selection,
23254 cursor_shape,
23255 line_mode,
23256 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23257 user_name,
23258 color: if let Some(index) = participant_index {
23259 cx.theme().players().color_for_participant(index.0)
23260 } else {
23261 cx.theme().players().absent()
23262 },
23263 })
23264 }
23265 })
23266 }
23267
23268 pub fn hunks_for_ranges(
23269 &self,
23270 ranges: impl IntoIterator<Item = Range<Point>>,
23271 ) -> Vec<MultiBufferDiffHunk> {
23272 let mut hunks = Vec::new();
23273 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23274 HashMap::default();
23275 for query_range in ranges {
23276 let query_rows =
23277 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23278 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23279 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23280 ) {
23281 // Include deleted hunks that are adjacent to the query range, because
23282 // otherwise they would be missed.
23283 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23284 if hunk.status().is_deleted() {
23285 intersects_range |= hunk.row_range.start == query_rows.end;
23286 intersects_range |= hunk.row_range.end == query_rows.start;
23287 }
23288 if intersects_range {
23289 if !processed_buffer_rows
23290 .entry(hunk.buffer_id)
23291 .or_default()
23292 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23293 {
23294 continue;
23295 }
23296 hunks.push(hunk);
23297 }
23298 }
23299 }
23300
23301 hunks
23302 }
23303
23304 fn display_diff_hunks_for_rows<'a>(
23305 &'a self,
23306 display_rows: Range<DisplayRow>,
23307 folded_buffers: &'a HashSet<BufferId>,
23308 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23309 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23310 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23311
23312 self.buffer_snapshot()
23313 .diff_hunks_in_range(buffer_start..buffer_end)
23314 .filter_map(|hunk| {
23315 if folded_buffers.contains(&hunk.buffer_id) {
23316 return None;
23317 }
23318
23319 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23320 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23321
23322 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23323 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23324
23325 let display_hunk = if hunk_display_start.column() != 0 {
23326 DisplayDiffHunk::Folded {
23327 display_row: hunk_display_start.row(),
23328 }
23329 } else {
23330 let mut end_row = hunk_display_end.row();
23331 if hunk_display_end.column() > 0 {
23332 end_row.0 += 1;
23333 }
23334 let is_created_file = hunk.is_created_file();
23335 DisplayDiffHunk::Unfolded {
23336 status: hunk.status(),
23337 diff_base_byte_range: hunk.diff_base_byte_range,
23338 display_row_range: hunk_display_start.row()..end_row,
23339 multi_buffer_range: Anchor::range_in_buffer(
23340 hunk.excerpt_id,
23341 hunk.buffer_id,
23342 hunk.buffer_range,
23343 ),
23344 is_created_file,
23345 }
23346 };
23347
23348 Some(display_hunk)
23349 })
23350 }
23351
23352 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23353 self.display_snapshot
23354 .buffer_snapshot()
23355 .language_at(position)
23356 }
23357
23358 pub fn is_focused(&self) -> bool {
23359 self.is_focused
23360 }
23361
23362 pub fn placeholder_text(&self) -> Option<String> {
23363 self.placeholder_display_snapshot
23364 .as_ref()
23365 .map(|display_map| display_map.text())
23366 }
23367
23368 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23369 self.scroll_anchor.scroll_position(&self.display_snapshot)
23370 }
23371
23372 fn gutter_dimensions(
23373 &self,
23374 font_id: FontId,
23375 font_size: Pixels,
23376 max_line_number_width: Pixels,
23377 cx: &App,
23378 ) -> Option<GutterDimensions> {
23379 if !self.show_gutter {
23380 return None;
23381 }
23382
23383 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23384 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23385
23386 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23387 matches!(
23388 ProjectSettings::get_global(cx).git.git_gutter,
23389 GitGutterSetting::TrackedFiles
23390 )
23391 });
23392 let gutter_settings = EditorSettings::get_global(cx).gutter;
23393 let show_line_numbers = self
23394 .show_line_numbers
23395 .unwrap_or(gutter_settings.line_numbers);
23396 let line_gutter_width = if show_line_numbers {
23397 // Avoid flicker-like gutter resizes when the line number gains another digit by
23398 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23399 let min_width_for_number_on_gutter =
23400 ch_advance * gutter_settings.min_line_number_digits as f32;
23401 max_line_number_width.max(min_width_for_number_on_gutter)
23402 } else {
23403 0.0.into()
23404 };
23405
23406 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23407 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23408
23409 let git_blame_entries_width =
23410 self.git_blame_gutter_max_author_length
23411 .map(|max_author_length| {
23412 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23413 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23414
23415 /// The number of characters to dedicate to gaps and margins.
23416 const SPACING_WIDTH: usize = 4;
23417
23418 let max_char_count = max_author_length.min(renderer.max_author_length())
23419 + ::git::SHORT_SHA_LENGTH
23420 + MAX_RELATIVE_TIMESTAMP.len()
23421 + SPACING_WIDTH;
23422
23423 ch_advance * max_char_count
23424 });
23425
23426 let is_singleton = self.buffer_snapshot().is_singleton();
23427
23428 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23429 left_padding += if !is_singleton {
23430 ch_width * 4.0
23431 } else if show_runnables || show_breakpoints {
23432 ch_width * 3.0
23433 } else if show_git_gutter && show_line_numbers {
23434 ch_width * 2.0
23435 } else if show_git_gutter || show_line_numbers {
23436 ch_width
23437 } else {
23438 px(0.)
23439 };
23440
23441 let shows_folds = is_singleton && gutter_settings.folds;
23442
23443 let right_padding = if shows_folds && show_line_numbers {
23444 ch_width * 4.0
23445 } else if shows_folds || (!is_singleton && show_line_numbers) {
23446 ch_width * 3.0
23447 } else if show_line_numbers {
23448 ch_width
23449 } else {
23450 px(0.)
23451 };
23452
23453 Some(GutterDimensions {
23454 left_padding,
23455 right_padding,
23456 width: line_gutter_width + left_padding + right_padding,
23457 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23458 git_blame_entries_width,
23459 })
23460 }
23461
23462 pub fn render_crease_toggle(
23463 &self,
23464 buffer_row: MultiBufferRow,
23465 row_contains_cursor: bool,
23466 editor: Entity<Editor>,
23467 window: &mut Window,
23468 cx: &mut App,
23469 ) -> Option<AnyElement> {
23470 let folded = self.is_line_folded(buffer_row);
23471 let mut is_foldable = false;
23472
23473 if let Some(crease) = self
23474 .crease_snapshot
23475 .query_row(buffer_row, self.buffer_snapshot())
23476 {
23477 is_foldable = true;
23478 match crease {
23479 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23480 if let Some(render_toggle) = render_toggle {
23481 let toggle_callback =
23482 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23483 if folded {
23484 editor.update(cx, |editor, cx| {
23485 editor.fold_at(buffer_row, window, cx)
23486 });
23487 } else {
23488 editor.update(cx, |editor, cx| {
23489 editor.unfold_at(buffer_row, window, cx)
23490 });
23491 }
23492 });
23493 return Some((render_toggle)(
23494 buffer_row,
23495 folded,
23496 toggle_callback,
23497 window,
23498 cx,
23499 ));
23500 }
23501 }
23502 }
23503 }
23504
23505 is_foldable |= self.starts_indent(buffer_row);
23506
23507 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23508 Some(
23509 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23510 .toggle_state(folded)
23511 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23512 if folded {
23513 this.unfold_at(buffer_row, window, cx);
23514 } else {
23515 this.fold_at(buffer_row, window, cx);
23516 }
23517 }))
23518 .into_any_element(),
23519 )
23520 } else {
23521 None
23522 }
23523 }
23524
23525 pub fn render_crease_trailer(
23526 &self,
23527 buffer_row: MultiBufferRow,
23528 window: &mut Window,
23529 cx: &mut App,
23530 ) -> Option<AnyElement> {
23531 let folded = self.is_line_folded(buffer_row);
23532 if let Crease::Inline { render_trailer, .. } = self
23533 .crease_snapshot
23534 .query_row(buffer_row, self.buffer_snapshot())?
23535 {
23536 let render_trailer = render_trailer.as_ref()?;
23537 Some(render_trailer(buffer_row, folded, window, cx))
23538 } else {
23539 None
23540 }
23541 }
23542}
23543
23544impl Deref for EditorSnapshot {
23545 type Target = DisplaySnapshot;
23546
23547 fn deref(&self) -> &Self::Target {
23548 &self.display_snapshot
23549 }
23550}
23551
23552#[derive(Clone, Debug, PartialEq, Eq)]
23553pub enum EditorEvent {
23554 InputIgnored {
23555 text: Arc<str>,
23556 },
23557 InputHandled {
23558 utf16_range_to_replace: Option<Range<isize>>,
23559 text: Arc<str>,
23560 },
23561 ExcerptsAdded {
23562 buffer: Entity<Buffer>,
23563 predecessor: ExcerptId,
23564 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23565 },
23566 ExcerptsRemoved {
23567 ids: Vec<ExcerptId>,
23568 removed_buffer_ids: Vec<BufferId>,
23569 },
23570 BufferFoldToggled {
23571 ids: Vec<ExcerptId>,
23572 folded: bool,
23573 },
23574 ExcerptsEdited {
23575 ids: Vec<ExcerptId>,
23576 },
23577 ExcerptsExpanded {
23578 ids: Vec<ExcerptId>,
23579 },
23580 BufferEdited,
23581 Edited {
23582 transaction_id: clock::Lamport,
23583 },
23584 Reparsed(BufferId),
23585 Focused,
23586 FocusedIn,
23587 Blurred,
23588 DirtyChanged,
23589 Saved,
23590 TitleChanged,
23591 SelectionsChanged {
23592 local: bool,
23593 },
23594 ScrollPositionChanged {
23595 local: bool,
23596 autoscroll: bool,
23597 },
23598 TransactionUndone {
23599 transaction_id: clock::Lamport,
23600 },
23601 TransactionBegun {
23602 transaction_id: clock::Lamport,
23603 },
23604 CursorShapeChanged,
23605 BreadcrumbsChanged,
23606 PushedToNavHistory {
23607 anchor: Anchor,
23608 is_deactivate: bool,
23609 },
23610}
23611
23612impl EventEmitter<EditorEvent> for Editor {}
23613
23614impl Focusable for Editor {
23615 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23616 self.focus_handle.clone()
23617 }
23618}
23619
23620impl Render for Editor {
23621 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23622 let settings = ThemeSettings::get_global(cx);
23623
23624 let mut text_style = match self.mode {
23625 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23626 color: cx.theme().colors().editor_foreground,
23627 font_family: settings.ui_font.family.clone(),
23628 font_features: settings.ui_font.features.clone(),
23629 font_fallbacks: settings.ui_font.fallbacks.clone(),
23630 font_size: rems(0.875).into(),
23631 font_weight: settings.ui_font.weight,
23632 line_height: relative(settings.buffer_line_height.value()),
23633 ..Default::default()
23634 },
23635 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23636 color: cx.theme().colors().editor_foreground,
23637 font_family: settings.buffer_font.family.clone(),
23638 font_features: settings.buffer_font.features.clone(),
23639 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23640 font_size: settings.buffer_font_size(cx).into(),
23641 font_weight: settings.buffer_font.weight,
23642 line_height: relative(settings.buffer_line_height.value()),
23643 ..Default::default()
23644 },
23645 };
23646 if let Some(text_style_refinement) = &self.text_style_refinement {
23647 text_style.refine(text_style_refinement)
23648 }
23649
23650 let background = match self.mode {
23651 EditorMode::SingleLine => cx.theme().system().transparent,
23652 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23653 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23654 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23655 };
23656
23657 EditorElement::new(
23658 &cx.entity(),
23659 EditorStyle {
23660 background,
23661 border: cx.theme().colors().border,
23662 local_player: cx.theme().players().local(),
23663 text: text_style,
23664 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23665 syntax: cx.theme().syntax().clone(),
23666 status: cx.theme().status().clone(),
23667 inlay_hints_style: make_inlay_hints_style(cx),
23668 edit_prediction_styles: make_suggestion_styles(cx),
23669 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23670 show_underlines: self.diagnostics_enabled(),
23671 },
23672 )
23673 }
23674}
23675
23676impl EntityInputHandler for Editor {
23677 fn text_for_range(
23678 &mut self,
23679 range_utf16: Range<usize>,
23680 adjusted_range: &mut Option<Range<usize>>,
23681 _: &mut Window,
23682 cx: &mut Context<Self>,
23683 ) -> Option<String> {
23684 let snapshot = self.buffer.read(cx).read(cx);
23685 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23686 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23687 if (start.0..end.0) != range_utf16 {
23688 adjusted_range.replace(start.0..end.0);
23689 }
23690 Some(snapshot.text_for_range(start..end).collect())
23691 }
23692
23693 fn selected_text_range(
23694 &mut self,
23695 ignore_disabled_input: bool,
23696 _: &mut Window,
23697 cx: &mut Context<Self>,
23698 ) -> Option<UTF16Selection> {
23699 // Prevent the IME menu from appearing when holding down an alphabetic key
23700 // while input is disabled.
23701 if !ignore_disabled_input && !self.input_enabled {
23702 return None;
23703 }
23704
23705 let selection = self.selections.newest::<OffsetUtf16>(cx);
23706 let range = selection.range();
23707
23708 Some(UTF16Selection {
23709 range: range.start.0..range.end.0,
23710 reversed: selection.reversed,
23711 })
23712 }
23713
23714 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23715 let snapshot = self.buffer.read(cx).read(cx);
23716 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23717 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23718 }
23719
23720 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23721 self.clear_highlights::<InputComposition>(cx);
23722 self.ime_transaction.take();
23723 }
23724
23725 fn replace_text_in_range(
23726 &mut self,
23727 range_utf16: Option<Range<usize>>,
23728 text: &str,
23729 window: &mut Window,
23730 cx: &mut Context<Self>,
23731 ) {
23732 if !self.input_enabled {
23733 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23734 return;
23735 }
23736
23737 self.transact(window, cx, |this, window, cx| {
23738 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23739 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23740 Some(this.selection_replacement_ranges(range_utf16, cx))
23741 } else {
23742 this.marked_text_ranges(cx)
23743 };
23744
23745 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23746 let newest_selection_id = this.selections.newest_anchor().id;
23747 this.selections
23748 .all::<OffsetUtf16>(cx)
23749 .iter()
23750 .zip(ranges_to_replace.iter())
23751 .find_map(|(selection, range)| {
23752 if selection.id == newest_selection_id {
23753 Some(
23754 (range.start.0 as isize - selection.head().0 as isize)
23755 ..(range.end.0 as isize - selection.head().0 as isize),
23756 )
23757 } else {
23758 None
23759 }
23760 })
23761 });
23762
23763 cx.emit(EditorEvent::InputHandled {
23764 utf16_range_to_replace: range_to_replace,
23765 text: text.into(),
23766 });
23767
23768 if let Some(new_selected_ranges) = new_selected_ranges {
23769 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23770 selections.select_ranges(new_selected_ranges)
23771 });
23772 this.backspace(&Default::default(), window, cx);
23773 }
23774
23775 this.handle_input(text, window, cx);
23776 });
23777
23778 if let Some(transaction) = self.ime_transaction {
23779 self.buffer.update(cx, |buffer, cx| {
23780 buffer.group_until_transaction(transaction, cx);
23781 });
23782 }
23783
23784 self.unmark_text(window, cx);
23785 }
23786
23787 fn replace_and_mark_text_in_range(
23788 &mut self,
23789 range_utf16: Option<Range<usize>>,
23790 text: &str,
23791 new_selected_range_utf16: Option<Range<usize>>,
23792 window: &mut Window,
23793 cx: &mut Context<Self>,
23794 ) {
23795 if !self.input_enabled {
23796 return;
23797 }
23798
23799 let transaction = self.transact(window, cx, |this, window, cx| {
23800 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23801 let snapshot = this.buffer.read(cx).read(cx);
23802 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23803 for marked_range in &mut marked_ranges {
23804 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23805 marked_range.start.0 += relative_range_utf16.start;
23806 marked_range.start =
23807 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23808 marked_range.end =
23809 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23810 }
23811 }
23812 Some(marked_ranges)
23813 } else if let Some(range_utf16) = range_utf16 {
23814 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23815 Some(this.selection_replacement_ranges(range_utf16, cx))
23816 } else {
23817 None
23818 };
23819
23820 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23821 let newest_selection_id = this.selections.newest_anchor().id;
23822 this.selections
23823 .all::<OffsetUtf16>(cx)
23824 .iter()
23825 .zip(ranges_to_replace.iter())
23826 .find_map(|(selection, range)| {
23827 if selection.id == newest_selection_id {
23828 Some(
23829 (range.start.0 as isize - selection.head().0 as isize)
23830 ..(range.end.0 as isize - selection.head().0 as isize),
23831 )
23832 } else {
23833 None
23834 }
23835 })
23836 });
23837
23838 cx.emit(EditorEvent::InputHandled {
23839 utf16_range_to_replace: range_to_replace,
23840 text: text.into(),
23841 });
23842
23843 if let Some(ranges) = ranges_to_replace {
23844 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23845 s.select_ranges(ranges)
23846 });
23847 }
23848
23849 let marked_ranges = {
23850 let snapshot = this.buffer.read(cx).read(cx);
23851 this.selections
23852 .disjoint_anchors_arc()
23853 .iter()
23854 .map(|selection| {
23855 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23856 })
23857 .collect::<Vec<_>>()
23858 };
23859
23860 if text.is_empty() {
23861 this.unmark_text(window, cx);
23862 } else {
23863 this.highlight_text::<InputComposition>(
23864 marked_ranges.clone(),
23865 HighlightStyle {
23866 underline: Some(UnderlineStyle {
23867 thickness: px(1.),
23868 color: None,
23869 wavy: false,
23870 }),
23871 ..Default::default()
23872 },
23873 cx,
23874 );
23875 }
23876
23877 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23878 let use_autoclose = this.use_autoclose;
23879 let use_auto_surround = this.use_auto_surround;
23880 this.set_use_autoclose(false);
23881 this.set_use_auto_surround(false);
23882 this.handle_input(text, window, cx);
23883 this.set_use_autoclose(use_autoclose);
23884 this.set_use_auto_surround(use_auto_surround);
23885
23886 if let Some(new_selected_range) = new_selected_range_utf16 {
23887 let snapshot = this.buffer.read(cx).read(cx);
23888 let new_selected_ranges = marked_ranges
23889 .into_iter()
23890 .map(|marked_range| {
23891 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23892 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23893 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23894 snapshot.clip_offset_utf16(new_start, Bias::Left)
23895 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23896 })
23897 .collect::<Vec<_>>();
23898
23899 drop(snapshot);
23900 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23901 selections.select_ranges(new_selected_ranges)
23902 });
23903 }
23904 });
23905
23906 self.ime_transaction = self.ime_transaction.or(transaction);
23907 if let Some(transaction) = self.ime_transaction {
23908 self.buffer.update(cx, |buffer, cx| {
23909 buffer.group_until_transaction(transaction, cx);
23910 });
23911 }
23912
23913 if self.text_highlights::<InputComposition>(cx).is_none() {
23914 self.ime_transaction.take();
23915 }
23916 }
23917
23918 fn bounds_for_range(
23919 &mut self,
23920 range_utf16: Range<usize>,
23921 element_bounds: gpui::Bounds<Pixels>,
23922 window: &mut Window,
23923 cx: &mut Context<Self>,
23924 ) -> Option<gpui::Bounds<Pixels>> {
23925 let text_layout_details = self.text_layout_details(window);
23926 let CharacterDimensions {
23927 em_width,
23928 em_advance,
23929 line_height,
23930 } = self.character_dimensions(window);
23931
23932 let snapshot = self.snapshot(window, cx);
23933 let scroll_position = snapshot.scroll_position();
23934 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
23935
23936 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23937 let x = Pixels::from(
23938 ScrollOffset::from(
23939 snapshot.x_for_display_point(start, &text_layout_details)
23940 + self.gutter_dimensions.full_width(),
23941 ) - scroll_left,
23942 );
23943 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
23944
23945 Some(Bounds {
23946 origin: element_bounds.origin + point(x, y),
23947 size: size(em_width, line_height),
23948 })
23949 }
23950
23951 fn character_index_for_point(
23952 &mut self,
23953 point: gpui::Point<Pixels>,
23954 _window: &mut Window,
23955 _cx: &mut Context<Self>,
23956 ) -> Option<usize> {
23957 let position_map = self.last_position_map.as_ref()?;
23958 if !position_map.text_hitbox.contains(&point) {
23959 return None;
23960 }
23961 let display_point = position_map.point_for_position(point).previous_valid;
23962 let anchor = position_map
23963 .snapshot
23964 .display_point_to_anchor(display_point, Bias::Left);
23965 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
23966 Some(utf16_offset.0)
23967 }
23968}
23969
23970trait SelectionExt {
23971 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23972 fn spanned_rows(
23973 &self,
23974 include_end_if_at_line_start: bool,
23975 map: &DisplaySnapshot,
23976 ) -> Range<MultiBufferRow>;
23977}
23978
23979impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23980 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23981 let start = self
23982 .start
23983 .to_point(map.buffer_snapshot())
23984 .to_display_point(map);
23985 let end = self
23986 .end
23987 .to_point(map.buffer_snapshot())
23988 .to_display_point(map);
23989 if self.reversed {
23990 end..start
23991 } else {
23992 start..end
23993 }
23994 }
23995
23996 fn spanned_rows(
23997 &self,
23998 include_end_if_at_line_start: bool,
23999 map: &DisplaySnapshot,
24000 ) -> Range<MultiBufferRow> {
24001 let start = self.start.to_point(map.buffer_snapshot());
24002 let mut end = self.end.to_point(map.buffer_snapshot());
24003 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24004 end.row -= 1;
24005 }
24006
24007 let buffer_start = map.prev_line_boundary(start).0;
24008 let buffer_end = map.next_line_boundary(end).0;
24009 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24010 }
24011}
24012
24013impl<T: InvalidationRegion> InvalidationStack<T> {
24014 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24015 where
24016 S: Clone + ToOffset,
24017 {
24018 while let Some(region) = self.last() {
24019 let all_selections_inside_invalidation_ranges =
24020 if selections.len() == region.ranges().len() {
24021 selections
24022 .iter()
24023 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24024 .all(|(selection, invalidation_range)| {
24025 let head = selection.head().to_offset(buffer);
24026 invalidation_range.start <= head && invalidation_range.end >= head
24027 })
24028 } else {
24029 false
24030 };
24031
24032 if all_selections_inside_invalidation_ranges {
24033 break;
24034 } else {
24035 self.pop();
24036 }
24037 }
24038 }
24039}
24040
24041impl<T> Default for InvalidationStack<T> {
24042 fn default() -> Self {
24043 Self(Default::default())
24044 }
24045}
24046
24047impl<T> Deref for InvalidationStack<T> {
24048 type Target = Vec<T>;
24049
24050 fn deref(&self) -> &Self::Target {
24051 &self.0
24052 }
24053}
24054
24055impl<T> DerefMut for InvalidationStack<T> {
24056 fn deref_mut(&mut self) -> &mut Self::Target {
24057 &mut self.0
24058 }
24059}
24060
24061impl InvalidationRegion for SnippetState {
24062 fn ranges(&self) -> &[Range<Anchor>] {
24063 &self.ranges[self.active_index]
24064 }
24065}
24066
24067fn edit_prediction_edit_text(
24068 current_snapshot: &BufferSnapshot,
24069 edits: &[(Range<Anchor>, String)],
24070 edit_preview: &EditPreview,
24071 include_deletions: bool,
24072 cx: &App,
24073) -> HighlightedText {
24074 let edits = edits
24075 .iter()
24076 .map(|(anchor, text)| {
24077 (
24078 anchor.start.text_anchor..anchor.end.text_anchor,
24079 text.clone(),
24080 )
24081 })
24082 .collect::<Vec<_>>();
24083
24084 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24085}
24086
24087fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24088 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24089 // Just show the raw edit text with basic styling
24090 let mut text = String::new();
24091 let mut highlights = Vec::new();
24092
24093 let insertion_highlight_style = HighlightStyle {
24094 color: Some(cx.theme().colors().text),
24095 ..Default::default()
24096 };
24097
24098 for (_, edit_text) in edits {
24099 let start_offset = text.len();
24100 text.push_str(edit_text);
24101 let end_offset = text.len();
24102
24103 if start_offset < end_offset {
24104 highlights.push((start_offset..end_offset, insertion_highlight_style));
24105 }
24106 }
24107
24108 HighlightedText {
24109 text: text.into(),
24110 highlights,
24111 }
24112}
24113
24114pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24115 match severity {
24116 lsp::DiagnosticSeverity::ERROR => colors.error,
24117 lsp::DiagnosticSeverity::WARNING => colors.warning,
24118 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24119 lsp::DiagnosticSeverity::HINT => colors.info,
24120 _ => colors.ignored,
24121 }
24122}
24123
24124pub fn styled_runs_for_code_label<'a>(
24125 label: &'a CodeLabel,
24126 syntax_theme: &'a theme::SyntaxTheme,
24127) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24128 let fade_out = HighlightStyle {
24129 fade_out: Some(0.35),
24130 ..Default::default()
24131 };
24132
24133 let mut prev_end = label.filter_range.end;
24134 label
24135 .runs
24136 .iter()
24137 .enumerate()
24138 .flat_map(move |(ix, (range, highlight_id))| {
24139 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24140 style
24141 } else {
24142 return Default::default();
24143 };
24144 let muted_style = style.highlight(fade_out);
24145
24146 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24147 if range.start >= label.filter_range.end {
24148 if range.start > prev_end {
24149 runs.push((prev_end..range.start, fade_out));
24150 }
24151 runs.push((range.clone(), muted_style));
24152 } else if range.end <= label.filter_range.end {
24153 runs.push((range.clone(), style));
24154 } else {
24155 runs.push((range.start..label.filter_range.end, style));
24156 runs.push((label.filter_range.end..range.end, muted_style));
24157 }
24158 prev_end = cmp::max(prev_end, range.end);
24159
24160 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24161 runs.push((prev_end..label.text.len(), fade_out));
24162 }
24163
24164 runs
24165 })
24166}
24167
24168pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24169 let mut prev_index = 0;
24170 let mut prev_codepoint: Option<char> = None;
24171 text.char_indices()
24172 .chain([(text.len(), '\0')])
24173 .filter_map(move |(index, codepoint)| {
24174 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24175 let is_boundary = index == text.len()
24176 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24177 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24178 if is_boundary {
24179 let chunk = &text[prev_index..index];
24180 prev_index = index;
24181 Some(chunk)
24182 } else {
24183 None
24184 }
24185 })
24186}
24187
24188pub trait RangeToAnchorExt: Sized {
24189 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24190
24191 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24192 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24193 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24194 }
24195}
24196
24197impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24198 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24199 let start_offset = self.start.to_offset(snapshot);
24200 let end_offset = self.end.to_offset(snapshot);
24201 if start_offset == end_offset {
24202 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24203 } else {
24204 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24205 }
24206 }
24207}
24208
24209pub trait RowExt {
24210 fn as_f64(&self) -> f64;
24211
24212 fn next_row(&self) -> Self;
24213
24214 fn previous_row(&self) -> Self;
24215
24216 fn minus(&self, other: Self) -> u32;
24217}
24218
24219impl RowExt for DisplayRow {
24220 fn as_f64(&self) -> f64 {
24221 self.0 as _
24222 }
24223
24224 fn next_row(&self) -> Self {
24225 Self(self.0 + 1)
24226 }
24227
24228 fn previous_row(&self) -> Self {
24229 Self(self.0.saturating_sub(1))
24230 }
24231
24232 fn minus(&self, other: Self) -> u32 {
24233 self.0 - other.0
24234 }
24235}
24236
24237impl RowExt for MultiBufferRow {
24238 fn as_f64(&self) -> f64 {
24239 self.0 as _
24240 }
24241
24242 fn next_row(&self) -> Self {
24243 Self(self.0 + 1)
24244 }
24245
24246 fn previous_row(&self) -> Self {
24247 Self(self.0.saturating_sub(1))
24248 }
24249
24250 fn minus(&self, other: Self) -> u32 {
24251 self.0 - other.0
24252 }
24253}
24254
24255trait RowRangeExt {
24256 type Row;
24257
24258 fn len(&self) -> usize;
24259
24260 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24261}
24262
24263impl RowRangeExt for Range<MultiBufferRow> {
24264 type Row = MultiBufferRow;
24265
24266 fn len(&self) -> usize {
24267 (self.end.0 - self.start.0) as usize
24268 }
24269
24270 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24271 (self.start.0..self.end.0).map(MultiBufferRow)
24272 }
24273}
24274
24275impl RowRangeExt for Range<DisplayRow> {
24276 type Row = DisplayRow;
24277
24278 fn len(&self) -> usize {
24279 (self.end.0 - self.start.0) as usize
24280 }
24281
24282 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24283 (self.start.0..self.end.0).map(DisplayRow)
24284 }
24285}
24286
24287/// If select range has more than one line, we
24288/// just point the cursor to range.start.
24289fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24290 if range.start.row == range.end.row {
24291 range
24292 } else {
24293 range.start..range.start
24294 }
24295}
24296pub struct KillRing(ClipboardItem);
24297impl Global for KillRing {}
24298
24299const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24300
24301enum BreakpointPromptEditAction {
24302 Log,
24303 Condition,
24304 HitCondition,
24305}
24306
24307struct BreakpointPromptEditor {
24308 pub(crate) prompt: Entity<Editor>,
24309 editor: WeakEntity<Editor>,
24310 breakpoint_anchor: Anchor,
24311 breakpoint: Breakpoint,
24312 edit_action: BreakpointPromptEditAction,
24313 block_ids: HashSet<CustomBlockId>,
24314 editor_margins: Arc<Mutex<EditorMargins>>,
24315 _subscriptions: Vec<Subscription>,
24316}
24317
24318impl BreakpointPromptEditor {
24319 const MAX_LINES: u8 = 4;
24320
24321 fn new(
24322 editor: WeakEntity<Editor>,
24323 breakpoint_anchor: Anchor,
24324 breakpoint: Breakpoint,
24325 edit_action: BreakpointPromptEditAction,
24326 window: &mut Window,
24327 cx: &mut Context<Self>,
24328 ) -> Self {
24329 let base_text = match edit_action {
24330 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24331 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24332 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24333 }
24334 .map(|msg| msg.to_string())
24335 .unwrap_or_default();
24336
24337 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24338 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24339
24340 let prompt = cx.new(|cx| {
24341 let mut prompt = Editor::new(
24342 EditorMode::AutoHeight {
24343 min_lines: 1,
24344 max_lines: Some(Self::MAX_LINES as usize),
24345 },
24346 buffer,
24347 None,
24348 window,
24349 cx,
24350 );
24351 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24352 prompt.set_show_cursor_when_unfocused(false, cx);
24353 prompt.set_placeholder_text(
24354 match edit_action {
24355 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24356 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24357 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24358 },
24359 window,
24360 cx,
24361 );
24362
24363 prompt
24364 });
24365
24366 Self {
24367 prompt,
24368 editor,
24369 breakpoint_anchor,
24370 breakpoint,
24371 edit_action,
24372 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24373 block_ids: Default::default(),
24374 _subscriptions: vec![],
24375 }
24376 }
24377
24378 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24379 self.block_ids.extend(block_ids)
24380 }
24381
24382 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24383 if let Some(editor) = self.editor.upgrade() {
24384 let message = self
24385 .prompt
24386 .read(cx)
24387 .buffer
24388 .read(cx)
24389 .as_singleton()
24390 .expect("A multi buffer in breakpoint prompt isn't possible")
24391 .read(cx)
24392 .as_rope()
24393 .to_string();
24394
24395 editor.update(cx, |editor, cx| {
24396 editor.edit_breakpoint_at_anchor(
24397 self.breakpoint_anchor,
24398 self.breakpoint.clone(),
24399 match self.edit_action {
24400 BreakpointPromptEditAction::Log => {
24401 BreakpointEditAction::EditLogMessage(message.into())
24402 }
24403 BreakpointPromptEditAction::Condition => {
24404 BreakpointEditAction::EditCondition(message.into())
24405 }
24406 BreakpointPromptEditAction::HitCondition => {
24407 BreakpointEditAction::EditHitCondition(message.into())
24408 }
24409 },
24410 cx,
24411 );
24412
24413 editor.remove_blocks(self.block_ids.clone(), None, cx);
24414 cx.focus_self(window);
24415 });
24416 }
24417 }
24418
24419 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24420 self.editor
24421 .update(cx, |editor, cx| {
24422 editor.remove_blocks(self.block_ids.clone(), None, cx);
24423 window.focus(&editor.focus_handle);
24424 })
24425 .log_err();
24426 }
24427
24428 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24429 let settings = ThemeSettings::get_global(cx);
24430 let text_style = TextStyle {
24431 color: if self.prompt.read(cx).read_only(cx) {
24432 cx.theme().colors().text_disabled
24433 } else {
24434 cx.theme().colors().text
24435 },
24436 font_family: settings.buffer_font.family.clone(),
24437 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24438 font_size: settings.buffer_font_size(cx).into(),
24439 font_weight: settings.buffer_font.weight,
24440 line_height: relative(settings.buffer_line_height.value()),
24441 ..Default::default()
24442 };
24443 EditorElement::new(
24444 &self.prompt,
24445 EditorStyle {
24446 background: cx.theme().colors().editor_background,
24447 local_player: cx.theme().players().local(),
24448 text: text_style,
24449 ..Default::default()
24450 },
24451 )
24452 }
24453}
24454
24455impl Render for BreakpointPromptEditor {
24456 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24457 let editor_margins = *self.editor_margins.lock();
24458 let gutter_dimensions = editor_margins.gutter;
24459 h_flex()
24460 .key_context("Editor")
24461 .bg(cx.theme().colors().editor_background)
24462 .border_y_1()
24463 .border_color(cx.theme().status().info_border)
24464 .size_full()
24465 .py(window.line_height() / 2.5)
24466 .on_action(cx.listener(Self::confirm))
24467 .on_action(cx.listener(Self::cancel))
24468 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24469 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24470 }
24471}
24472
24473impl Focusable for BreakpointPromptEditor {
24474 fn focus_handle(&self, cx: &App) -> FocusHandle {
24475 self.prompt.focus_handle(cx)
24476 }
24477}
24478
24479fn all_edits_insertions_or_deletions(
24480 edits: &Vec<(Range<Anchor>, String)>,
24481 snapshot: &MultiBufferSnapshot,
24482) -> bool {
24483 let mut all_insertions = true;
24484 let mut all_deletions = true;
24485
24486 for (range, new_text) in edits.iter() {
24487 let range_is_empty = range.to_offset(snapshot).is_empty();
24488 let text_is_empty = new_text.is_empty();
24489
24490 if range_is_empty != text_is_empty {
24491 if range_is_empty {
24492 all_deletions = false;
24493 } else {
24494 all_insertions = false;
24495 }
24496 } else {
24497 return false;
24498 }
24499
24500 if !all_insertions && !all_deletions {
24501 return false;
24502 }
24503 }
24504 all_insertions || all_deletions
24505}
24506
24507struct MissingEditPredictionKeybindingTooltip;
24508
24509impl Render for MissingEditPredictionKeybindingTooltip {
24510 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24511 ui::tooltip_container(cx, |container, cx| {
24512 container
24513 .flex_shrink_0()
24514 .max_w_80()
24515 .min_h(rems_from_px(124.))
24516 .justify_between()
24517 .child(
24518 v_flex()
24519 .flex_1()
24520 .text_ui_sm(cx)
24521 .child(Label::new("Conflict with Accept Keybinding"))
24522 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24523 )
24524 .child(
24525 h_flex()
24526 .pb_1()
24527 .gap_1()
24528 .items_end()
24529 .w_full()
24530 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24531 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24532 }))
24533 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24534 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24535 })),
24536 )
24537 })
24538 }
24539}
24540
24541#[derive(Debug, Clone, Copy, PartialEq)]
24542pub struct LineHighlight {
24543 pub background: Background,
24544 pub border: Option<gpui::Hsla>,
24545 pub include_gutter: bool,
24546 pub type_id: Option<TypeId>,
24547}
24548
24549struct LineManipulationResult {
24550 pub new_text: String,
24551 pub line_count_before: usize,
24552 pub line_count_after: usize,
24553}
24554
24555fn render_diff_hunk_controls(
24556 row: u32,
24557 status: &DiffHunkStatus,
24558 hunk_range: Range<Anchor>,
24559 is_created_file: bool,
24560 line_height: Pixels,
24561 editor: &Entity<Editor>,
24562 _window: &mut Window,
24563 cx: &mut App,
24564) -> AnyElement {
24565 h_flex()
24566 .h(line_height)
24567 .mr_1()
24568 .gap_1()
24569 .px_0p5()
24570 .pb_1()
24571 .border_x_1()
24572 .border_b_1()
24573 .border_color(cx.theme().colors().border_variant)
24574 .rounded_b_lg()
24575 .bg(cx.theme().colors().editor_background)
24576 .gap_1()
24577 .block_mouse_except_scroll()
24578 .shadow_md()
24579 .child(if status.has_secondary_hunk() {
24580 Button::new(("stage", row as u64), "Stage")
24581 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24582 .tooltip({
24583 let focus_handle = editor.focus_handle(cx);
24584 move |window, cx| {
24585 Tooltip::for_action_in(
24586 "Stage Hunk",
24587 &::git::ToggleStaged,
24588 &focus_handle,
24589 window,
24590 cx,
24591 )
24592 }
24593 })
24594 .on_click({
24595 let editor = editor.clone();
24596 move |_event, _window, cx| {
24597 editor.update(cx, |editor, cx| {
24598 editor.stage_or_unstage_diff_hunks(
24599 true,
24600 vec![hunk_range.start..hunk_range.start],
24601 cx,
24602 );
24603 });
24604 }
24605 })
24606 } else {
24607 Button::new(("unstage", row as u64), "Unstage")
24608 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24609 .tooltip({
24610 let focus_handle = editor.focus_handle(cx);
24611 move |window, cx| {
24612 Tooltip::for_action_in(
24613 "Unstage Hunk",
24614 &::git::ToggleStaged,
24615 &focus_handle,
24616 window,
24617 cx,
24618 )
24619 }
24620 })
24621 .on_click({
24622 let editor = editor.clone();
24623 move |_event, _window, cx| {
24624 editor.update(cx, |editor, cx| {
24625 editor.stage_or_unstage_diff_hunks(
24626 false,
24627 vec![hunk_range.start..hunk_range.start],
24628 cx,
24629 );
24630 });
24631 }
24632 })
24633 })
24634 .child(
24635 Button::new(("restore", row as u64), "Restore")
24636 .tooltip({
24637 let focus_handle = editor.focus_handle(cx);
24638 move |window, cx| {
24639 Tooltip::for_action_in(
24640 "Restore Hunk",
24641 &::git::Restore,
24642 &focus_handle,
24643 window,
24644 cx,
24645 )
24646 }
24647 })
24648 .on_click({
24649 let editor = editor.clone();
24650 move |_event, window, cx| {
24651 editor.update(cx, |editor, cx| {
24652 let snapshot = editor.snapshot(window, cx);
24653 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24654 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24655 });
24656 }
24657 })
24658 .disabled(is_created_file),
24659 )
24660 .when(
24661 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24662 |el| {
24663 el.child(
24664 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24665 .shape(IconButtonShape::Square)
24666 .icon_size(IconSize::Small)
24667 // .disabled(!has_multiple_hunks)
24668 .tooltip({
24669 let focus_handle = editor.focus_handle(cx);
24670 move |window, cx| {
24671 Tooltip::for_action_in(
24672 "Next Hunk",
24673 &GoToHunk,
24674 &focus_handle,
24675 window,
24676 cx,
24677 )
24678 }
24679 })
24680 .on_click({
24681 let editor = editor.clone();
24682 move |_event, window, cx| {
24683 editor.update(cx, |editor, cx| {
24684 let snapshot = editor.snapshot(window, cx);
24685 let position =
24686 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24687 editor.go_to_hunk_before_or_after_position(
24688 &snapshot,
24689 position,
24690 Direction::Next,
24691 window,
24692 cx,
24693 );
24694 editor.expand_selected_diff_hunks(cx);
24695 });
24696 }
24697 }),
24698 )
24699 .child(
24700 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24701 .shape(IconButtonShape::Square)
24702 .icon_size(IconSize::Small)
24703 // .disabled(!has_multiple_hunks)
24704 .tooltip({
24705 let focus_handle = editor.focus_handle(cx);
24706 move |window, cx| {
24707 Tooltip::for_action_in(
24708 "Previous Hunk",
24709 &GoToPreviousHunk,
24710 &focus_handle,
24711 window,
24712 cx,
24713 )
24714 }
24715 })
24716 .on_click({
24717 let editor = editor.clone();
24718 move |_event, window, cx| {
24719 editor.update(cx, |editor, cx| {
24720 let snapshot = editor.snapshot(window, cx);
24721 let point =
24722 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24723 editor.go_to_hunk_before_or_after_position(
24724 &snapshot,
24725 point,
24726 Direction::Prev,
24727 window,
24728 cx,
24729 );
24730 editor.expand_selected_diff_hunks(cx);
24731 });
24732 }
24733 }),
24734 )
24735 },
24736 )
24737 .into_any_element()
24738}
24739
24740pub fn multibuffer_context_lines(cx: &App) -> u32 {
24741 EditorSettings::try_get(cx)
24742 .map(|settings| settings.excerpt_context_lines)
24743 .unwrap_or(2)
24744 .min(32)
24745}