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()?)?;
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
11691 let mut text = buffer.text_for_range(start..end).collect::<String>();
11692
11693 let insert_location = if upwards {
11694 // When duplicating upward, we need to insert before the current line.
11695 // If we're on the last line and it doesn't end with a newline,
11696 // we need to add a newline before the duplicated content.
11697 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11698 && buffer.max_point().column > 0
11699 && !text.ends_with('\n');
11700
11701 if needs_leading_newline {
11702 text.insert(0, '\n');
11703 end
11704 } else {
11705 text.push('\n');
11706 Point::new(rows.end.0, 0)
11707 }
11708 } else {
11709 text.push('\n');
11710 start
11711 };
11712 edits.push((insert_location..insert_location, text));
11713 } else {
11714 // duplicate character-wise
11715 let start = selection.start;
11716 let end = selection.end;
11717 let text = buffer.text_for_range(start..end).collect::<String>();
11718 edits.push((selection.end..selection.end, text));
11719 }
11720 }
11721
11722 self.transact(window, cx, |this, _, cx| {
11723 this.buffer.update(cx, |buffer, cx| {
11724 buffer.edit(edits, None, cx);
11725 });
11726
11727 this.request_autoscroll(Autoscroll::fit(), cx);
11728 });
11729 }
11730
11731 pub fn duplicate_line_up(
11732 &mut self,
11733 _: &DuplicateLineUp,
11734 window: &mut Window,
11735 cx: &mut Context<Self>,
11736 ) {
11737 self.duplicate(true, true, window, cx);
11738 }
11739
11740 pub fn duplicate_line_down(
11741 &mut self,
11742 _: &DuplicateLineDown,
11743 window: &mut Window,
11744 cx: &mut Context<Self>,
11745 ) {
11746 self.duplicate(false, true, window, cx);
11747 }
11748
11749 pub fn duplicate_selection(
11750 &mut self,
11751 _: &DuplicateSelection,
11752 window: &mut Window,
11753 cx: &mut Context<Self>,
11754 ) {
11755 self.duplicate(false, false, window, cx);
11756 }
11757
11758 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11759 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11760 if self.mode.is_single_line() {
11761 cx.propagate();
11762 return;
11763 }
11764
11765 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11766 let buffer = self.buffer.read(cx).snapshot(cx);
11767
11768 let mut edits = Vec::new();
11769 let mut unfold_ranges = Vec::new();
11770 let mut refold_creases = Vec::new();
11771
11772 let selections = self.selections.all::<Point>(cx);
11773 let mut selections = selections.iter().peekable();
11774 let mut contiguous_row_selections = Vec::new();
11775 let mut new_selections = Vec::new();
11776
11777 while let Some(selection) = selections.next() {
11778 // Find all the selections that span a contiguous row range
11779 let (start_row, end_row) = consume_contiguous_rows(
11780 &mut contiguous_row_selections,
11781 selection,
11782 &display_map,
11783 &mut selections,
11784 );
11785
11786 // Move the text spanned by the row range to be before the line preceding the row range
11787 if start_row.0 > 0 {
11788 let range_to_move = Point::new(
11789 start_row.previous_row().0,
11790 buffer.line_len(start_row.previous_row()),
11791 )
11792 ..Point::new(
11793 end_row.previous_row().0,
11794 buffer.line_len(end_row.previous_row()),
11795 );
11796 let insertion_point = display_map
11797 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11798 .0;
11799
11800 // Don't move lines across excerpts
11801 if buffer
11802 .excerpt_containing(insertion_point..range_to_move.end)
11803 .is_some()
11804 {
11805 let text = buffer
11806 .text_for_range(range_to_move.clone())
11807 .flat_map(|s| s.chars())
11808 .skip(1)
11809 .chain(['\n'])
11810 .collect::<String>();
11811
11812 edits.push((
11813 buffer.anchor_after(range_to_move.start)
11814 ..buffer.anchor_before(range_to_move.end),
11815 String::new(),
11816 ));
11817 let insertion_anchor = buffer.anchor_after(insertion_point);
11818 edits.push((insertion_anchor..insertion_anchor, text));
11819
11820 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11821
11822 // Move selections up
11823 new_selections.extend(contiguous_row_selections.drain(..).map(
11824 |mut selection| {
11825 selection.start.row -= row_delta;
11826 selection.end.row -= row_delta;
11827 selection
11828 },
11829 ));
11830
11831 // Move folds up
11832 unfold_ranges.push(range_to_move.clone());
11833 for fold in display_map.folds_in_range(
11834 buffer.anchor_before(range_to_move.start)
11835 ..buffer.anchor_after(range_to_move.end),
11836 ) {
11837 let mut start = fold.range.start.to_point(&buffer);
11838 let mut end = fold.range.end.to_point(&buffer);
11839 start.row -= row_delta;
11840 end.row -= row_delta;
11841 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11842 }
11843 }
11844 }
11845
11846 // If we didn't move line(s), preserve the existing selections
11847 new_selections.append(&mut contiguous_row_selections);
11848 }
11849
11850 self.transact(window, cx, |this, window, cx| {
11851 this.unfold_ranges(&unfold_ranges, true, true, cx);
11852 this.buffer.update(cx, |buffer, cx| {
11853 for (range, text) in edits {
11854 buffer.edit([(range, text)], None, cx);
11855 }
11856 });
11857 this.fold_creases(refold_creases, true, window, cx);
11858 this.change_selections(Default::default(), window, cx, |s| {
11859 s.select(new_selections);
11860 })
11861 });
11862 }
11863
11864 pub fn move_line_down(
11865 &mut self,
11866 _: &MoveLineDown,
11867 window: &mut Window,
11868 cx: &mut Context<Self>,
11869 ) {
11870 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11871 if self.mode.is_single_line() {
11872 cx.propagate();
11873 return;
11874 }
11875
11876 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11877 let buffer = self.buffer.read(cx).snapshot(cx);
11878
11879 let mut edits = Vec::new();
11880 let mut unfold_ranges = Vec::new();
11881 let mut refold_creases = Vec::new();
11882
11883 let selections = self.selections.all::<Point>(cx);
11884 let mut selections = selections.iter().peekable();
11885 let mut contiguous_row_selections = Vec::new();
11886 let mut new_selections = Vec::new();
11887
11888 while let Some(selection) = selections.next() {
11889 // Find all the selections that span a contiguous row range
11890 let (start_row, end_row) = consume_contiguous_rows(
11891 &mut contiguous_row_selections,
11892 selection,
11893 &display_map,
11894 &mut selections,
11895 );
11896
11897 // Move the text spanned by the row range to be after the last line of the row range
11898 if end_row.0 <= buffer.max_point().row {
11899 let range_to_move =
11900 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11901 let insertion_point = display_map
11902 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11903 .0;
11904
11905 // Don't move lines across excerpt boundaries
11906 if buffer
11907 .excerpt_containing(range_to_move.start..insertion_point)
11908 .is_some()
11909 {
11910 let mut text = String::from("\n");
11911 text.extend(buffer.text_for_range(range_to_move.clone()));
11912 text.pop(); // Drop trailing newline
11913 edits.push((
11914 buffer.anchor_after(range_to_move.start)
11915 ..buffer.anchor_before(range_to_move.end),
11916 String::new(),
11917 ));
11918 let insertion_anchor = buffer.anchor_after(insertion_point);
11919 edits.push((insertion_anchor..insertion_anchor, text));
11920
11921 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11922
11923 // Move selections down
11924 new_selections.extend(contiguous_row_selections.drain(..).map(
11925 |mut selection| {
11926 selection.start.row += row_delta;
11927 selection.end.row += row_delta;
11928 selection
11929 },
11930 ));
11931
11932 // Move folds down
11933 unfold_ranges.push(range_to_move.clone());
11934 for fold in display_map.folds_in_range(
11935 buffer.anchor_before(range_to_move.start)
11936 ..buffer.anchor_after(range_to_move.end),
11937 ) {
11938 let mut start = fold.range.start.to_point(&buffer);
11939 let mut end = fold.range.end.to_point(&buffer);
11940 start.row += row_delta;
11941 end.row += row_delta;
11942 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11943 }
11944 }
11945 }
11946
11947 // If we didn't move line(s), preserve the existing selections
11948 new_selections.append(&mut contiguous_row_selections);
11949 }
11950
11951 self.transact(window, cx, |this, window, cx| {
11952 this.unfold_ranges(&unfold_ranges, true, true, cx);
11953 this.buffer.update(cx, |buffer, cx| {
11954 for (range, text) in edits {
11955 buffer.edit([(range, text)], None, cx);
11956 }
11957 });
11958 this.fold_creases(refold_creases, true, window, cx);
11959 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11960 });
11961 }
11962
11963 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11964 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11965 let text_layout_details = &self.text_layout_details(window);
11966 self.transact(window, cx, |this, window, cx| {
11967 let edits = this.change_selections(Default::default(), window, cx, |s| {
11968 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11969 s.move_with(|display_map, selection| {
11970 if !selection.is_empty() {
11971 return;
11972 }
11973
11974 let mut head = selection.head();
11975 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11976 if head.column() == display_map.line_len(head.row()) {
11977 transpose_offset = display_map
11978 .buffer_snapshot()
11979 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11980 }
11981
11982 if transpose_offset == 0 {
11983 return;
11984 }
11985
11986 *head.column_mut() += 1;
11987 head = display_map.clip_point(head, Bias::Right);
11988 let goal = SelectionGoal::HorizontalPosition(
11989 display_map
11990 .x_for_display_point(head, text_layout_details)
11991 .into(),
11992 );
11993 selection.collapse_to(head, goal);
11994
11995 let transpose_start = display_map
11996 .buffer_snapshot()
11997 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11998 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11999 let transpose_end = display_map
12000 .buffer_snapshot()
12001 .clip_offset(transpose_offset + 1, Bias::Right);
12002 if let Some(ch) = display_map
12003 .buffer_snapshot()
12004 .chars_at(transpose_start)
12005 .next()
12006 {
12007 edits.push((transpose_start..transpose_offset, String::new()));
12008 edits.push((transpose_end..transpose_end, ch.to_string()));
12009 }
12010 }
12011 });
12012 edits
12013 });
12014 this.buffer
12015 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12016 let selections = this.selections.all::<usize>(cx);
12017 this.change_selections(Default::default(), window, cx, |s| {
12018 s.select(selections);
12019 });
12020 });
12021 }
12022
12023 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12024 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12025 if self.mode.is_single_line() {
12026 cx.propagate();
12027 return;
12028 }
12029
12030 self.rewrap_impl(RewrapOptions::default(), cx)
12031 }
12032
12033 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12034 let buffer = self.buffer.read(cx).snapshot(cx);
12035 let selections = self.selections.all::<Point>(cx);
12036
12037 #[derive(Clone, Debug, PartialEq)]
12038 enum CommentFormat {
12039 /// single line comment, with prefix for line
12040 Line(String),
12041 /// single line within a block comment, with prefix for line
12042 BlockLine(String),
12043 /// a single line of a block comment that includes the initial delimiter
12044 BlockCommentWithStart(BlockCommentConfig),
12045 /// a single line of a block comment that includes the ending delimiter
12046 BlockCommentWithEnd(BlockCommentConfig),
12047 }
12048
12049 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12050 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12051 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12052 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12053 .peekable();
12054
12055 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12056 row
12057 } else {
12058 return Vec::new();
12059 };
12060
12061 let language_settings = buffer.language_settings_at(selection.head(), cx);
12062 let language_scope = buffer.language_scope_at(selection.head());
12063
12064 let indent_and_prefix_for_row =
12065 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12066 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12067 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12068 &language_scope
12069 {
12070 let indent_end = Point::new(row, indent.len);
12071 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12072 let line_text_after_indent = buffer
12073 .text_for_range(indent_end..line_end)
12074 .collect::<String>();
12075
12076 let is_within_comment_override = buffer
12077 .language_scope_at(indent_end)
12078 .is_some_and(|scope| scope.override_name() == Some("comment"));
12079 let comment_delimiters = if is_within_comment_override {
12080 // we are within a comment syntax node, but we don't
12081 // yet know what kind of comment: block, doc or line
12082 match (
12083 language_scope.documentation_comment(),
12084 language_scope.block_comment(),
12085 ) {
12086 (Some(config), _) | (_, Some(config))
12087 if buffer.contains_str_at(indent_end, &config.start) =>
12088 {
12089 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12090 }
12091 (Some(config), _) | (_, Some(config))
12092 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12093 {
12094 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12095 }
12096 (Some(config), _) | (_, Some(config))
12097 if buffer.contains_str_at(indent_end, &config.prefix) =>
12098 {
12099 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12100 }
12101 (_, _) => language_scope
12102 .line_comment_prefixes()
12103 .iter()
12104 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12105 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12106 }
12107 } else {
12108 // we not in an overridden comment node, but we may
12109 // be within a non-overridden line comment node
12110 language_scope
12111 .line_comment_prefixes()
12112 .iter()
12113 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12114 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12115 };
12116
12117 let rewrap_prefix = language_scope
12118 .rewrap_prefixes()
12119 .iter()
12120 .find_map(|prefix_regex| {
12121 prefix_regex.find(&line_text_after_indent).map(|mat| {
12122 if mat.start() == 0 {
12123 Some(mat.as_str().to_string())
12124 } else {
12125 None
12126 }
12127 })
12128 })
12129 .flatten();
12130 (comment_delimiters, rewrap_prefix)
12131 } else {
12132 (None, None)
12133 };
12134 (indent, comment_prefix, rewrap_prefix)
12135 };
12136
12137 let mut ranges = Vec::new();
12138 let from_empty_selection = selection.is_empty();
12139
12140 let mut current_range_start = first_row;
12141 let mut prev_row = first_row;
12142 let (
12143 mut current_range_indent,
12144 mut current_range_comment_delimiters,
12145 mut current_range_rewrap_prefix,
12146 ) = indent_and_prefix_for_row(first_row);
12147
12148 for row in non_blank_rows_iter.skip(1) {
12149 let has_paragraph_break = row > prev_row + 1;
12150
12151 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12152 indent_and_prefix_for_row(row);
12153
12154 let has_indent_change = row_indent != current_range_indent;
12155 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12156
12157 let has_boundary_change = has_comment_change
12158 || row_rewrap_prefix.is_some()
12159 || (has_indent_change && current_range_comment_delimiters.is_some());
12160
12161 if has_paragraph_break || has_boundary_change {
12162 ranges.push((
12163 language_settings.clone(),
12164 Point::new(current_range_start, 0)
12165 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12166 current_range_indent,
12167 current_range_comment_delimiters.clone(),
12168 current_range_rewrap_prefix.clone(),
12169 from_empty_selection,
12170 ));
12171 current_range_start = row;
12172 current_range_indent = row_indent;
12173 current_range_comment_delimiters = row_comment_delimiters;
12174 current_range_rewrap_prefix = row_rewrap_prefix;
12175 }
12176 prev_row = row;
12177 }
12178
12179 ranges.push((
12180 language_settings.clone(),
12181 Point::new(current_range_start, 0)
12182 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12183 current_range_indent,
12184 current_range_comment_delimiters,
12185 current_range_rewrap_prefix,
12186 from_empty_selection,
12187 ));
12188
12189 ranges
12190 });
12191
12192 let mut edits = Vec::new();
12193 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12194
12195 for (
12196 language_settings,
12197 wrap_range,
12198 mut indent_size,
12199 comment_prefix,
12200 rewrap_prefix,
12201 from_empty_selection,
12202 ) in wrap_ranges
12203 {
12204 let mut start_row = wrap_range.start.row;
12205 let mut end_row = wrap_range.end.row;
12206
12207 // Skip selections that overlap with a range that has already been rewrapped.
12208 let selection_range = start_row..end_row;
12209 if rewrapped_row_ranges
12210 .iter()
12211 .any(|range| range.overlaps(&selection_range))
12212 {
12213 continue;
12214 }
12215
12216 let tab_size = language_settings.tab_size;
12217
12218 let (line_prefix, inside_comment) = match &comment_prefix {
12219 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12220 (Some(prefix.as_str()), true)
12221 }
12222 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12223 (Some(prefix.as_ref()), true)
12224 }
12225 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12226 start: _,
12227 end: _,
12228 prefix,
12229 tab_size,
12230 })) => {
12231 indent_size.len += tab_size;
12232 (Some(prefix.as_ref()), true)
12233 }
12234 None => (None, false),
12235 };
12236 let indent_prefix = indent_size.chars().collect::<String>();
12237 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12238
12239 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12240 RewrapBehavior::InComments => inside_comment,
12241 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12242 RewrapBehavior::Anywhere => true,
12243 };
12244
12245 let should_rewrap = options.override_language_settings
12246 || allow_rewrap_based_on_language
12247 || self.hard_wrap.is_some();
12248 if !should_rewrap {
12249 continue;
12250 }
12251
12252 if from_empty_selection {
12253 'expand_upwards: while start_row > 0 {
12254 let prev_row = start_row - 1;
12255 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12256 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12257 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12258 {
12259 start_row = prev_row;
12260 } else {
12261 break 'expand_upwards;
12262 }
12263 }
12264
12265 'expand_downwards: while end_row < buffer.max_point().row {
12266 let next_row = end_row + 1;
12267 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12268 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12269 && !buffer.is_line_blank(MultiBufferRow(next_row))
12270 {
12271 end_row = next_row;
12272 } else {
12273 break 'expand_downwards;
12274 }
12275 }
12276 }
12277
12278 let start = Point::new(start_row, 0);
12279 let start_offset = ToOffset::to_offset(&start, &buffer);
12280 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12281 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12282 let mut first_line_delimiter = None;
12283 let mut last_line_delimiter = None;
12284 let Some(lines_without_prefixes) = selection_text
12285 .lines()
12286 .enumerate()
12287 .map(|(ix, line)| {
12288 let line_trimmed = line.trim_start();
12289 if rewrap_prefix.is_some() && ix > 0 {
12290 Ok(line_trimmed)
12291 } else if let Some(
12292 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12293 start,
12294 prefix,
12295 end,
12296 tab_size,
12297 })
12298 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12299 start,
12300 prefix,
12301 end,
12302 tab_size,
12303 }),
12304 ) = &comment_prefix
12305 {
12306 let line_trimmed = line_trimmed
12307 .strip_prefix(start.as_ref())
12308 .map(|s| {
12309 let mut indent_size = indent_size;
12310 indent_size.len -= tab_size;
12311 let indent_prefix: String = indent_size.chars().collect();
12312 first_line_delimiter = Some((indent_prefix, start));
12313 s.trim_start()
12314 })
12315 .unwrap_or(line_trimmed);
12316 let line_trimmed = line_trimmed
12317 .strip_suffix(end.as_ref())
12318 .map(|s| {
12319 last_line_delimiter = Some(end);
12320 s.trim_end()
12321 })
12322 .unwrap_or(line_trimmed);
12323 let line_trimmed = line_trimmed
12324 .strip_prefix(prefix.as_ref())
12325 .unwrap_or(line_trimmed);
12326 Ok(line_trimmed)
12327 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12328 line_trimmed.strip_prefix(prefix).with_context(|| {
12329 format!("line did not start with prefix {prefix:?}: {line:?}")
12330 })
12331 } else {
12332 line_trimmed
12333 .strip_prefix(&line_prefix.trim_start())
12334 .with_context(|| {
12335 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12336 })
12337 }
12338 })
12339 .collect::<Result<Vec<_>, _>>()
12340 .log_err()
12341 else {
12342 continue;
12343 };
12344
12345 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12346 buffer
12347 .language_settings_at(Point::new(start_row, 0), cx)
12348 .preferred_line_length as usize
12349 });
12350
12351 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12352 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12353 } else {
12354 line_prefix.clone()
12355 };
12356
12357 let wrapped_text = {
12358 let mut wrapped_text = wrap_with_prefix(
12359 line_prefix,
12360 subsequent_lines_prefix,
12361 lines_without_prefixes.join("\n"),
12362 wrap_column,
12363 tab_size,
12364 options.preserve_existing_whitespace,
12365 );
12366
12367 if let Some((indent, delimiter)) = first_line_delimiter {
12368 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12369 }
12370 if let Some(last_line) = last_line_delimiter {
12371 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12372 }
12373
12374 wrapped_text
12375 };
12376
12377 // TODO: should always use char-based diff while still supporting cursor behavior that
12378 // matches vim.
12379 let mut diff_options = DiffOptions::default();
12380 if options.override_language_settings {
12381 diff_options.max_word_diff_len = 0;
12382 diff_options.max_word_diff_line_count = 0;
12383 } else {
12384 diff_options.max_word_diff_len = usize::MAX;
12385 diff_options.max_word_diff_line_count = usize::MAX;
12386 }
12387
12388 for (old_range, new_text) in
12389 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12390 {
12391 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12392 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12393 edits.push((edit_start..edit_end, new_text));
12394 }
12395
12396 rewrapped_row_ranges.push(start_row..=end_row);
12397 }
12398
12399 self.buffer
12400 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12401 }
12402
12403 pub fn cut_common(
12404 &mut self,
12405 cut_no_selection_line: bool,
12406 window: &mut Window,
12407 cx: &mut Context<Self>,
12408 ) -> ClipboardItem {
12409 let mut text = String::new();
12410 let buffer = self.buffer.read(cx).snapshot(cx);
12411 let mut selections = self.selections.all::<Point>(cx);
12412 let mut clipboard_selections = Vec::with_capacity(selections.len());
12413 {
12414 let max_point = buffer.max_point();
12415 let mut is_first = true;
12416 for selection in &mut selections {
12417 let is_entire_line =
12418 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12419 if is_entire_line {
12420 selection.start = Point::new(selection.start.row, 0);
12421 if !selection.is_empty() && selection.end.column == 0 {
12422 selection.end = cmp::min(max_point, selection.end);
12423 } else {
12424 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12425 }
12426 selection.goal = SelectionGoal::None;
12427 }
12428 if is_first {
12429 is_first = false;
12430 } else {
12431 text += "\n";
12432 }
12433 let mut len = 0;
12434 for chunk in buffer.text_for_range(selection.start..selection.end) {
12435 text.push_str(chunk);
12436 len += chunk.len();
12437 }
12438 clipboard_selections.push(ClipboardSelection {
12439 len,
12440 is_entire_line,
12441 first_line_indent: buffer
12442 .indent_size_for_line(MultiBufferRow(selection.start.row))
12443 .len,
12444 });
12445 }
12446 }
12447
12448 self.transact(window, cx, |this, window, cx| {
12449 this.change_selections(Default::default(), window, cx, |s| {
12450 s.select(selections);
12451 });
12452 this.insert("", window, cx);
12453 });
12454 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12455 }
12456
12457 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12458 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12459 let item = self.cut_common(true, window, cx);
12460 cx.write_to_clipboard(item);
12461 }
12462
12463 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12464 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12465 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12466 s.move_with(|snapshot, sel| {
12467 if sel.is_empty() {
12468 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12469 }
12470 if sel.is_empty() {
12471 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12472 }
12473 });
12474 });
12475 let item = self.cut_common(false, window, cx);
12476 cx.set_global(KillRing(item))
12477 }
12478
12479 pub fn kill_ring_yank(
12480 &mut self,
12481 _: &KillRingYank,
12482 window: &mut Window,
12483 cx: &mut Context<Self>,
12484 ) {
12485 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12486 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12487 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12488 (kill_ring.text().to_string(), kill_ring.metadata_json())
12489 } else {
12490 return;
12491 }
12492 } else {
12493 return;
12494 };
12495 self.do_paste(&text, metadata, false, window, cx);
12496 }
12497
12498 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12499 self.do_copy(true, cx);
12500 }
12501
12502 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12503 self.do_copy(false, cx);
12504 }
12505
12506 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12507 let selections = self.selections.all::<Point>(cx);
12508 let buffer = self.buffer.read(cx).read(cx);
12509 let mut text = String::new();
12510
12511 let mut clipboard_selections = Vec::with_capacity(selections.len());
12512 {
12513 let max_point = buffer.max_point();
12514 let mut is_first = true;
12515 for selection in &selections {
12516 let mut start = selection.start;
12517 let mut end = selection.end;
12518 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12519 let mut add_trailing_newline = false;
12520 if is_entire_line {
12521 start = Point::new(start.row, 0);
12522 let next_line_start = Point::new(end.row + 1, 0);
12523 if next_line_start <= max_point {
12524 end = next_line_start;
12525 } else {
12526 // We're on the last line without a trailing newline.
12527 // Copy to the end of the line and add a newline afterwards.
12528 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12529 add_trailing_newline = true;
12530 }
12531 }
12532
12533 let mut trimmed_selections = Vec::new();
12534 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12535 let row = MultiBufferRow(start.row);
12536 let first_indent = buffer.indent_size_for_line(row);
12537 if first_indent.len == 0 || start.column > first_indent.len {
12538 trimmed_selections.push(start..end);
12539 } else {
12540 trimmed_selections.push(
12541 Point::new(row.0, first_indent.len)
12542 ..Point::new(row.0, buffer.line_len(row)),
12543 );
12544 for row in start.row + 1..=end.row {
12545 let mut line_len = buffer.line_len(MultiBufferRow(row));
12546 if row == end.row {
12547 line_len = end.column;
12548 }
12549 if line_len == 0 {
12550 trimmed_selections
12551 .push(Point::new(row, 0)..Point::new(row, line_len));
12552 continue;
12553 }
12554 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12555 if row_indent_size.len >= first_indent.len {
12556 trimmed_selections.push(
12557 Point::new(row, first_indent.len)..Point::new(row, line_len),
12558 );
12559 } else {
12560 trimmed_selections.clear();
12561 trimmed_selections.push(start..end);
12562 break;
12563 }
12564 }
12565 }
12566 } else {
12567 trimmed_selections.push(start..end);
12568 }
12569
12570 for trimmed_range in trimmed_selections {
12571 if is_first {
12572 is_first = false;
12573 } else {
12574 text += "\n";
12575 }
12576 let mut len = 0;
12577 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12578 text.push_str(chunk);
12579 len += chunk.len();
12580 }
12581 if add_trailing_newline {
12582 text.push('\n');
12583 len += 1;
12584 }
12585 clipboard_selections.push(ClipboardSelection {
12586 len,
12587 is_entire_line,
12588 first_line_indent: buffer
12589 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12590 .len,
12591 });
12592 }
12593 }
12594 }
12595
12596 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12597 text,
12598 clipboard_selections,
12599 ));
12600 }
12601
12602 pub fn do_paste(
12603 &mut self,
12604 text: &String,
12605 clipboard_selections: Option<Vec<ClipboardSelection>>,
12606 handle_entire_lines: bool,
12607 window: &mut Window,
12608 cx: &mut Context<Self>,
12609 ) {
12610 if self.read_only(cx) {
12611 return;
12612 }
12613
12614 let clipboard_text = Cow::Borrowed(text.as_str());
12615
12616 self.transact(window, cx, |this, window, cx| {
12617 let had_active_edit_prediction = this.has_active_edit_prediction();
12618 let old_selections = this.selections.all::<usize>(cx);
12619 let cursor_offset = this.selections.last::<usize>(cx).head();
12620
12621 if let Some(mut clipboard_selections) = clipboard_selections {
12622 let all_selections_were_entire_line =
12623 clipboard_selections.iter().all(|s| s.is_entire_line);
12624 let first_selection_indent_column =
12625 clipboard_selections.first().map(|s| s.first_line_indent);
12626 if clipboard_selections.len() != old_selections.len() {
12627 clipboard_selections.drain(..);
12628 }
12629 let mut auto_indent_on_paste = true;
12630
12631 this.buffer.update(cx, |buffer, cx| {
12632 let snapshot = buffer.read(cx);
12633 auto_indent_on_paste = snapshot
12634 .language_settings_at(cursor_offset, cx)
12635 .auto_indent_on_paste;
12636
12637 let mut start_offset = 0;
12638 let mut edits = Vec::new();
12639 let mut original_indent_columns = Vec::new();
12640 for (ix, selection) in old_selections.iter().enumerate() {
12641 let to_insert;
12642 let entire_line;
12643 let original_indent_column;
12644 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12645 let end_offset = start_offset + clipboard_selection.len;
12646 to_insert = &clipboard_text[start_offset..end_offset];
12647 entire_line = clipboard_selection.is_entire_line;
12648 start_offset = end_offset + 1;
12649 original_indent_column = Some(clipboard_selection.first_line_indent);
12650 } else {
12651 to_insert = &*clipboard_text;
12652 entire_line = all_selections_were_entire_line;
12653 original_indent_column = first_selection_indent_column
12654 }
12655
12656 let (range, to_insert) =
12657 if selection.is_empty() && handle_entire_lines && entire_line {
12658 // If the corresponding selection was empty when this slice of the
12659 // clipboard text was written, then the entire line containing the
12660 // selection was copied. If this selection is also currently empty,
12661 // then paste the line before the current line of the buffer.
12662 let column = selection.start.to_point(&snapshot).column as usize;
12663 let line_start = selection.start - column;
12664 (line_start..line_start, Cow::Borrowed(to_insert))
12665 } else {
12666 let language = snapshot.language_at(selection.head());
12667 let range = selection.range();
12668 if let Some(language) = language
12669 && language.name() == "Markdown".into()
12670 {
12671 edit_for_markdown_paste(
12672 &snapshot,
12673 range,
12674 to_insert,
12675 url::Url::parse(to_insert).ok(),
12676 )
12677 } else {
12678 (range, Cow::Borrowed(to_insert))
12679 }
12680 };
12681
12682 edits.push((range, to_insert));
12683 original_indent_columns.push(original_indent_column);
12684 }
12685 drop(snapshot);
12686
12687 buffer.edit(
12688 edits,
12689 if auto_indent_on_paste {
12690 Some(AutoindentMode::Block {
12691 original_indent_columns,
12692 })
12693 } else {
12694 None
12695 },
12696 cx,
12697 );
12698 });
12699
12700 let selections = this.selections.all::<usize>(cx);
12701 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12702 } else {
12703 let url = url::Url::parse(&clipboard_text).ok();
12704
12705 let auto_indent_mode = if !clipboard_text.is_empty() {
12706 Some(AutoindentMode::Block {
12707 original_indent_columns: Vec::new(),
12708 })
12709 } else {
12710 None
12711 };
12712
12713 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12714 let snapshot = buffer.snapshot(cx);
12715
12716 let anchors = old_selections
12717 .iter()
12718 .map(|s| {
12719 let anchor = snapshot.anchor_after(s.head());
12720 s.map(|_| anchor)
12721 })
12722 .collect::<Vec<_>>();
12723
12724 let mut edits = Vec::new();
12725
12726 for selection in old_selections.iter() {
12727 let language = snapshot.language_at(selection.head());
12728 let range = selection.range();
12729
12730 let (edit_range, edit_text) = if let Some(language) = language
12731 && language.name() == "Markdown".into()
12732 {
12733 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12734 } else {
12735 (range, clipboard_text.clone())
12736 };
12737
12738 edits.push((edit_range, edit_text));
12739 }
12740
12741 drop(snapshot);
12742 buffer.edit(edits, auto_indent_mode, cx);
12743
12744 anchors
12745 });
12746
12747 this.change_selections(Default::default(), window, cx, |s| {
12748 s.select_anchors(selection_anchors);
12749 });
12750 }
12751
12752 let trigger_in_words =
12753 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12754
12755 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12756 });
12757 }
12758
12759 pub fn diff_clipboard_with_selection(
12760 &mut self,
12761 _: &DiffClipboardWithSelection,
12762 window: &mut Window,
12763 cx: &mut Context<Self>,
12764 ) {
12765 let selections = self.selections.all::<usize>(cx);
12766
12767 if selections.is_empty() {
12768 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12769 return;
12770 };
12771
12772 let clipboard_text = match cx.read_from_clipboard() {
12773 Some(item) => match item.entries().first() {
12774 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12775 _ => None,
12776 },
12777 None => None,
12778 };
12779
12780 let Some(clipboard_text) = clipboard_text else {
12781 log::warn!("Clipboard doesn't contain text.");
12782 return;
12783 };
12784
12785 window.dispatch_action(
12786 Box::new(DiffClipboardWithSelectionData {
12787 clipboard_text,
12788 editor: cx.entity(),
12789 }),
12790 cx,
12791 );
12792 }
12793
12794 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12795 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12796 if let Some(item) = cx.read_from_clipboard() {
12797 let entries = item.entries();
12798
12799 match entries.first() {
12800 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12801 // of all the pasted entries.
12802 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12803 .do_paste(
12804 clipboard_string.text(),
12805 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12806 true,
12807 window,
12808 cx,
12809 ),
12810 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12811 }
12812 }
12813 }
12814
12815 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12816 if self.read_only(cx) {
12817 return;
12818 }
12819
12820 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12821
12822 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12823 if let Some((selections, _)) =
12824 self.selection_history.transaction(transaction_id).cloned()
12825 {
12826 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12827 s.select_anchors(selections.to_vec());
12828 });
12829 } else {
12830 log::error!(
12831 "No entry in selection_history found for undo. \
12832 This may correspond to a bug where undo does not update the selection. \
12833 If this is occurring, please add details to \
12834 https://github.com/zed-industries/zed/issues/22692"
12835 );
12836 }
12837 self.request_autoscroll(Autoscroll::fit(), cx);
12838 self.unmark_text(window, cx);
12839 self.refresh_edit_prediction(true, false, window, cx);
12840 cx.emit(EditorEvent::Edited { transaction_id });
12841 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12842 }
12843 }
12844
12845 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12846 if self.read_only(cx) {
12847 return;
12848 }
12849
12850 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12851
12852 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12853 if let Some((_, Some(selections))) =
12854 self.selection_history.transaction(transaction_id).cloned()
12855 {
12856 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12857 s.select_anchors(selections.to_vec());
12858 });
12859 } else {
12860 log::error!(
12861 "No entry in selection_history found for redo. \
12862 This may correspond to a bug where undo does not update the selection. \
12863 If this is occurring, please add details to \
12864 https://github.com/zed-industries/zed/issues/22692"
12865 );
12866 }
12867 self.request_autoscroll(Autoscroll::fit(), cx);
12868 self.unmark_text(window, cx);
12869 self.refresh_edit_prediction(true, false, window, cx);
12870 cx.emit(EditorEvent::Edited { transaction_id });
12871 }
12872 }
12873
12874 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12875 self.buffer
12876 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12877 }
12878
12879 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12880 self.buffer
12881 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12882 }
12883
12884 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12885 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12886 self.change_selections(Default::default(), window, cx, |s| {
12887 s.move_with(|map, selection| {
12888 let cursor = if selection.is_empty() {
12889 movement::left(map, selection.start)
12890 } else {
12891 selection.start
12892 };
12893 selection.collapse_to(cursor, SelectionGoal::None);
12894 });
12895 })
12896 }
12897
12898 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12899 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12900 self.change_selections(Default::default(), window, cx, |s| {
12901 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12902 })
12903 }
12904
12905 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12906 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12907 self.change_selections(Default::default(), window, cx, |s| {
12908 s.move_with(|map, selection| {
12909 let cursor = if selection.is_empty() {
12910 movement::right(map, selection.end)
12911 } else {
12912 selection.end
12913 };
12914 selection.collapse_to(cursor, SelectionGoal::None)
12915 });
12916 })
12917 }
12918
12919 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12920 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12921 self.change_selections(Default::default(), window, cx, |s| {
12922 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12923 });
12924 }
12925
12926 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12927 if self.take_rename(true, window, cx).is_some() {
12928 return;
12929 }
12930
12931 if self.mode.is_single_line() {
12932 cx.propagate();
12933 return;
12934 }
12935
12936 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12937
12938 let text_layout_details = &self.text_layout_details(window);
12939 let selection_count = self.selections.count();
12940 let first_selection = self.selections.first_anchor();
12941
12942 self.change_selections(Default::default(), window, cx, |s| {
12943 s.move_with(|map, selection| {
12944 if !selection.is_empty() {
12945 selection.goal = SelectionGoal::None;
12946 }
12947 let (cursor, goal) = movement::up(
12948 map,
12949 selection.start,
12950 selection.goal,
12951 false,
12952 text_layout_details,
12953 );
12954 selection.collapse_to(cursor, goal);
12955 });
12956 });
12957
12958 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12959 {
12960 cx.propagate();
12961 }
12962 }
12963
12964 pub fn move_up_by_lines(
12965 &mut self,
12966 action: &MoveUpByLines,
12967 window: &mut Window,
12968 cx: &mut Context<Self>,
12969 ) {
12970 if self.take_rename(true, window, cx).is_some() {
12971 return;
12972 }
12973
12974 if self.mode.is_single_line() {
12975 cx.propagate();
12976 return;
12977 }
12978
12979 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12980
12981 let text_layout_details = &self.text_layout_details(window);
12982
12983 self.change_selections(Default::default(), window, cx, |s| {
12984 s.move_with(|map, selection| {
12985 if !selection.is_empty() {
12986 selection.goal = SelectionGoal::None;
12987 }
12988 let (cursor, goal) = movement::up_by_rows(
12989 map,
12990 selection.start,
12991 action.lines,
12992 selection.goal,
12993 false,
12994 text_layout_details,
12995 );
12996 selection.collapse_to(cursor, goal);
12997 });
12998 })
12999 }
13000
13001 pub fn move_down_by_lines(
13002 &mut self,
13003 action: &MoveDownByLines,
13004 window: &mut Window,
13005 cx: &mut Context<Self>,
13006 ) {
13007 if self.take_rename(true, window, cx).is_some() {
13008 return;
13009 }
13010
13011 if self.mode.is_single_line() {
13012 cx.propagate();
13013 return;
13014 }
13015
13016 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13017
13018 let text_layout_details = &self.text_layout_details(window);
13019
13020 self.change_selections(Default::default(), window, cx, |s| {
13021 s.move_with(|map, selection| {
13022 if !selection.is_empty() {
13023 selection.goal = SelectionGoal::None;
13024 }
13025 let (cursor, goal) = movement::down_by_rows(
13026 map,
13027 selection.start,
13028 action.lines,
13029 selection.goal,
13030 false,
13031 text_layout_details,
13032 );
13033 selection.collapse_to(cursor, goal);
13034 });
13035 })
13036 }
13037
13038 pub fn select_down_by_lines(
13039 &mut self,
13040 action: &SelectDownByLines,
13041 window: &mut Window,
13042 cx: &mut Context<Self>,
13043 ) {
13044 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13045 let text_layout_details = &self.text_layout_details(window);
13046 self.change_selections(Default::default(), window, cx, |s| {
13047 s.move_heads_with(|map, head, goal| {
13048 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13049 })
13050 })
13051 }
13052
13053 pub fn select_up_by_lines(
13054 &mut self,
13055 action: &SelectUpByLines,
13056 window: &mut Window,
13057 cx: &mut Context<Self>,
13058 ) {
13059 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13060 let text_layout_details = &self.text_layout_details(window);
13061 self.change_selections(Default::default(), window, cx, |s| {
13062 s.move_heads_with(|map, head, goal| {
13063 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13064 })
13065 })
13066 }
13067
13068 pub fn select_page_up(
13069 &mut self,
13070 _: &SelectPageUp,
13071 window: &mut Window,
13072 cx: &mut Context<Self>,
13073 ) {
13074 let Some(row_count) = self.visible_row_count() else {
13075 return;
13076 };
13077
13078 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13079
13080 let text_layout_details = &self.text_layout_details(window);
13081
13082 self.change_selections(Default::default(), window, cx, |s| {
13083 s.move_heads_with(|map, head, goal| {
13084 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13085 })
13086 })
13087 }
13088
13089 pub fn move_page_up(
13090 &mut self,
13091 action: &MovePageUp,
13092 window: &mut Window,
13093 cx: &mut Context<Self>,
13094 ) {
13095 if self.take_rename(true, window, cx).is_some() {
13096 return;
13097 }
13098
13099 if self
13100 .context_menu
13101 .borrow_mut()
13102 .as_mut()
13103 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13104 .unwrap_or(false)
13105 {
13106 return;
13107 }
13108
13109 if matches!(self.mode, EditorMode::SingleLine) {
13110 cx.propagate();
13111 return;
13112 }
13113
13114 let Some(row_count) = self.visible_row_count() else {
13115 return;
13116 };
13117
13118 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13119
13120 let effects = if action.center_cursor {
13121 SelectionEffects::scroll(Autoscroll::center())
13122 } else {
13123 SelectionEffects::default()
13124 };
13125
13126 let text_layout_details = &self.text_layout_details(window);
13127
13128 self.change_selections(effects, window, cx, |s| {
13129 s.move_with(|map, selection| {
13130 if !selection.is_empty() {
13131 selection.goal = SelectionGoal::None;
13132 }
13133 let (cursor, goal) = movement::up_by_rows(
13134 map,
13135 selection.end,
13136 row_count,
13137 selection.goal,
13138 false,
13139 text_layout_details,
13140 );
13141 selection.collapse_to(cursor, goal);
13142 });
13143 });
13144 }
13145
13146 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13148 let text_layout_details = &self.text_layout_details(window);
13149 self.change_selections(Default::default(), window, cx, |s| {
13150 s.move_heads_with(|map, head, goal| {
13151 movement::up(map, head, goal, false, text_layout_details)
13152 })
13153 })
13154 }
13155
13156 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13157 self.take_rename(true, window, cx);
13158
13159 if self.mode.is_single_line() {
13160 cx.propagate();
13161 return;
13162 }
13163
13164 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13165
13166 let text_layout_details = &self.text_layout_details(window);
13167 let selection_count = self.selections.count();
13168 let first_selection = self.selections.first_anchor();
13169
13170 self.change_selections(Default::default(), window, cx, |s| {
13171 s.move_with(|map, selection| {
13172 if !selection.is_empty() {
13173 selection.goal = SelectionGoal::None;
13174 }
13175 let (cursor, goal) = movement::down(
13176 map,
13177 selection.end,
13178 selection.goal,
13179 false,
13180 text_layout_details,
13181 );
13182 selection.collapse_to(cursor, goal);
13183 });
13184 });
13185
13186 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13187 {
13188 cx.propagate();
13189 }
13190 }
13191
13192 pub fn select_page_down(
13193 &mut self,
13194 _: &SelectPageDown,
13195 window: &mut Window,
13196 cx: &mut Context<Self>,
13197 ) {
13198 let Some(row_count) = self.visible_row_count() else {
13199 return;
13200 };
13201
13202 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13203
13204 let text_layout_details = &self.text_layout_details(window);
13205
13206 self.change_selections(Default::default(), window, cx, |s| {
13207 s.move_heads_with(|map, head, goal| {
13208 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13209 })
13210 })
13211 }
13212
13213 pub fn move_page_down(
13214 &mut self,
13215 action: &MovePageDown,
13216 window: &mut Window,
13217 cx: &mut Context<Self>,
13218 ) {
13219 if self.take_rename(true, window, cx).is_some() {
13220 return;
13221 }
13222
13223 if self
13224 .context_menu
13225 .borrow_mut()
13226 .as_mut()
13227 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13228 .unwrap_or(false)
13229 {
13230 return;
13231 }
13232
13233 if matches!(self.mode, EditorMode::SingleLine) {
13234 cx.propagate();
13235 return;
13236 }
13237
13238 let Some(row_count) = self.visible_row_count() else {
13239 return;
13240 };
13241
13242 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13243
13244 let effects = if action.center_cursor {
13245 SelectionEffects::scroll(Autoscroll::center())
13246 } else {
13247 SelectionEffects::default()
13248 };
13249
13250 let text_layout_details = &self.text_layout_details(window);
13251 self.change_selections(effects, window, cx, |s| {
13252 s.move_with(|map, selection| {
13253 if !selection.is_empty() {
13254 selection.goal = SelectionGoal::None;
13255 }
13256 let (cursor, goal) = movement::down_by_rows(
13257 map,
13258 selection.end,
13259 row_count,
13260 selection.goal,
13261 false,
13262 text_layout_details,
13263 );
13264 selection.collapse_to(cursor, goal);
13265 });
13266 });
13267 }
13268
13269 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13270 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13271 let text_layout_details = &self.text_layout_details(window);
13272 self.change_selections(Default::default(), window, cx, |s| {
13273 s.move_heads_with(|map, head, goal| {
13274 movement::down(map, head, goal, false, text_layout_details)
13275 })
13276 });
13277 }
13278
13279 pub fn context_menu_first(
13280 &mut self,
13281 _: &ContextMenuFirst,
13282 window: &mut Window,
13283 cx: &mut Context<Self>,
13284 ) {
13285 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13286 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13287 }
13288 }
13289
13290 pub fn context_menu_prev(
13291 &mut self,
13292 _: &ContextMenuPrevious,
13293 window: &mut Window,
13294 cx: &mut Context<Self>,
13295 ) {
13296 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13297 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13298 }
13299 }
13300
13301 pub fn context_menu_next(
13302 &mut self,
13303 _: &ContextMenuNext,
13304 window: &mut Window,
13305 cx: &mut Context<Self>,
13306 ) {
13307 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13308 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13309 }
13310 }
13311
13312 pub fn context_menu_last(
13313 &mut self,
13314 _: &ContextMenuLast,
13315 window: &mut Window,
13316 cx: &mut Context<Self>,
13317 ) {
13318 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13319 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13320 }
13321 }
13322
13323 pub fn signature_help_prev(
13324 &mut self,
13325 _: &SignatureHelpPrevious,
13326 _: &mut Window,
13327 cx: &mut Context<Self>,
13328 ) {
13329 if let Some(popover) = self.signature_help_state.popover_mut() {
13330 if popover.current_signature == 0 {
13331 popover.current_signature = popover.signatures.len() - 1;
13332 } else {
13333 popover.current_signature -= 1;
13334 }
13335 cx.notify();
13336 }
13337 }
13338
13339 pub fn signature_help_next(
13340 &mut self,
13341 _: &SignatureHelpNext,
13342 _: &mut Window,
13343 cx: &mut Context<Self>,
13344 ) {
13345 if let Some(popover) = self.signature_help_state.popover_mut() {
13346 if popover.current_signature + 1 == popover.signatures.len() {
13347 popover.current_signature = 0;
13348 } else {
13349 popover.current_signature += 1;
13350 }
13351 cx.notify();
13352 }
13353 }
13354
13355 pub fn move_to_previous_word_start(
13356 &mut self,
13357 _: &MoveToPreviousWordStart,
13358 window: &mut Window,
13359 cx: &mut Context<Self>,
13360 ) {
13361 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13362 self.change_selections(Default::default(), window, cx, |s| {
13363 s.move_cursors_with(|map, head, _| {
13364 (
13365 movement::previous_word_start(map, head),
13366 SelectionGoal::None,
13367 )
13368 });
13369 })
13370 }
13371
13372 pub fn move_to_previous_subword_start(
13373 &mut self,
13374 _: &MoveToPreviousSubwordStart,
13375 window: &mut Window,
13376 cx: &mut Context<Self>,
13377 ) {
13378 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13379 self.change_selections(Default::default(), window, cx, |s| {
13380 s.move_cursors_with(|map, head, _| {
13381 (
13382 movement::previous_subword_start(map, head),
13383 SelectionGoal::None,
13384 )
13385 });
13386 })
13387 }
13388
13389 pub fn select_to_previous_word_start(
13390 &mut self,
13391 _: &SelectToPreviousWordStart,
13392 window: &mut Window,
13393 cx: &mut Context<Self>,
13394 ) {
13395 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13396 self.change_selections(Default::default(), window, cx, |s| {
13397 s.move_heads_with(|map, head, _| {
13398 (
13399 movement::previous_word_start(map, head),
13400 SelectionGoal::None,
13401 )
13402 });
13403 })
13404 }
13405
13406 pub fn select_to_previous_subword_start(
13407 &mut self,
13408 _: &SelectToPreviousSubwordStart,
13409 window: &mut Window,
13410 cx: &mut Context<Self>,
13411 ) {
13412 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13413 self.change_selections(Default::default(), window, cx, |s| {
13414 s.move_heads_with(|map, head, _| {
13415 (
13416 movement::previous_subword_start(map, head),
13417 SelectionGoal::None,
13418 )
13419 });
13420 })
13421 }
13422
13423 pub fn delete_to_previous_word_start(
13424 &mut self,
13425 action: &DeleteToPreviousWordStart,
13426 window: &mut Window,
13427 cx: &mut Context<Self>,
13428 ) {
13429 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13430 self.transact(window, cx, |this, window, cx| {
13431 this.select_autoclose_pair(window, cx);
13432 this.change_selections(Default::default(), window, cx, |s| {
13433 s.move_with(|map, selection| {
13434 if selection.is_empty() {
13435 let mut cursor = if action.ignore_newlines {
13436 movement::previous_word_start(map, selection.head())
13437 } else {
13438 movement::previous_word_start_or_newline(map, selection.head())
13439 };
13440 cursor = movement::adjust_greedy_deletion(
13441 map,
13442 selection.head(),
13443 cursor,
13444 action.ignore_brackets,
13445 );
13446 selection.set_head(cursor, SelectionGoal::None);
13447 }
13448 });
13449 });
13450 this.insert("", window, cx);
13451 });
13452 }
13453
13454 pub fn delete_to_previous_subword_start(
13455 &mut self,
13456 _: &DeleteToPreviousSubwordStart,
13457 window: &mut Window,
13458 cx: &mut Context<Self>,
13459 ) {
13460 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13461 self.transact(window, cx, |this, window, cx| {
13462 this.select_autoclose_pair(window, cx);
13463 this.change_selections(Default::default(), window, cx, |s| {
13464 s.move_with(|map, selection| {
13465 if selection.is_empty() {
13466 let mut cursor = movement::previous_subword_start(map, selection.head());
13467 cursor =
13468 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13469 selection.set_head(cursor, SelectionGoal::None);
13470 }
13471 });
13472 });
13473 this.insert("", window, cx);
13474 });
13475 }
13476
13477 pub fn move_to_next_word_end(
13478 &mut self,
13479 _: &MoveToNextWordEnd,
13480 window: &mut Window,
13481 cx: &mut Context<Self>,
13482 ) {
13483 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13484 self.change_selections(Default::default(), window, cx, |s| {
13485 s.move_cursors_with(|map, head, _| {
13486 (movement::next_word_end(map, head), SelectionGoal::None)
13487 });
13488 })
13489 }
13490
13491 pub fn move_to_next_subword_end(
13492 &mut self,
13493 _: &MoveToNextSubwordEnd,
13494 window: &mut Window,
13495 cx: &mut Context<Self>,
13496 ) {
13497 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13498 self.change_selections(Default::default(), window, cx, |s| {
13499 s.move_cursors_with(|map, head, _| {
13500 (movement::next_subword_end(map, head), SelectionGoal::None)
13501 });
13502 })
13503 }
13504
13505 pub fn select_to_next_word_end(
13506 &mut self,
13507 _: &SelectToNextWordEnd,
13508 window: &mut Window,
13509 cx: &mut Context<Self>,
13510 ) {
13511 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13512 self.change_selections(Default::default(), window, cx, |s| {
13513 s.move_heads_with(|map, head, _| {
13514 (movement::next_word_end(map, head), SelectionGoal::None)
13515 });
13516 })
13517 }
13518
13519 pub fn select_to_next_subword_end(
13520 &mut self,
13521 _: &SelectToNextSubwordEnd,
13522 window: &mut Window,
13523 cx: &mut Context<Self>,
13524 ) {
13525 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13526 self.change_selections(Default::default(), window, cx, |s| {
13527 s.move_heads_with(|map, head, _| {
13528 (movement::next_subword_end(map, head), SelectionGoal::None)
13529 });
13530 })
13531 }
13532
13533 pub fn delete_to_next_word_end(
13534 &mut self,
13535 action: &DeleteToNextWordEnd,
13536 window: &mut Window,
13537 cx: &mut Context<Self>,
13538 ) {
13539 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13540 self.transact(window, cx, |this, window, cx| {
13541 this.change_selections(Default::default(), window, cx, |s| {
13542 s.move_with(|map, selection| {
13543 if selection.is_empty() {
13544 let mut cursor = if action.ignore_newlines {
13545 movement::next_word_end(map, selection.head())
13546 } else {
13547 movement::next_word_end_or_newline(map, selection.head())
13548 };
13549 cursor = movement::adjust_greedy_deletion(
13550 map,
13551 selection.head(),
13552 cursor,
13553 action.ignore_brackets,
13554 );
13555 selection.set_head(cursor, SelectionGoal::None);
13556 }
13557 });
13558 });
13559 this.insert("", window, cx);
13560 });
13561 }
13562
13563 pub fn delete_to_next_subword_end(
13564 &mut self,
13565 _: &DeleteToNextSubwordEnd,
13566 window: &mut Window,
13567 cx: &mut Context<Self>,
13568 ) {
13569 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13570 self.transact(window, cx, |this, window, cx| {
13571 this.change_selections(Default::default(), window, cx, |s| {
13572 s.move_with(|map, selection| {
13573 if selection.is_empty() {
13574 let mut cursor = movement::next_subword_end(map, selection.head());
13575 cursor =
13576 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13577 selection.set_head(cursor, SelectionGoal::None);
13578 }
13579 });
13580 });
13581 this.insert("", window, cx);
13582 });
13583 }
13584
13585 pub fn move_to_beginning_of_line(
13586 &mut self,
13587 action: &MoveToBeginningOfLine,
13588 window: &mut Window,
13589 cx: &mut Context<Self>,
13590 ) {
13591 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13592 self.change_selections(Default::default(), window, cx, |s| {
13593 s.move_cursors_with(|map, head, _| {
13594 (
13595 movement::indented_line_beginning(
13596 map,
13597 head,
13598 action.stop_at_soft_wraps,
13599 action.stop_at_indent,
13600 ),
13601 SelectionGoal::None,
13602 )
13603 });
13604 })
13605 }
13606
13607 pub fn select_to_beginning_of_line(
13608 &mut self,
13609 action: &SelectToBeginningOfLine,
13610 window: &mut Window,
13611 cx: &mut Context<Self>,
13612 ) {
13613 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13614 self.change_selections(Default::default(), window, cx, |s| {
13615 s.move_heads_with(|map, head, _| {
13616 (
13617 movement::indented_line_beginning(
13618 map,
13619 head,
13620 action.stop_at_soft_wraps,
13621 action.stop_at_indent,
13622 ),
13623 SelectionGoal::None,
13624 )
13625 });
13626 });
13627 }
13628
13629 pub fn delete_to_beginning_of_line(
13630 &mut self,
13631 action: &DeleteToBeginningOfLine,
13632 window: &mut Window,
13633 cx: &mut Context<Self>,
13634 ) {
13635 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13636 self.transact(window, cx, |this, window, cx| {
13637 this.change_selections(Default::default(), window, cx, |s| {
13638 s.move_with(|_, selection| {
13639 selection.reversed = true;
13640 });
13641 });
13642
13643 this.select_to_beginning_of_line(
13644 &SelectToBeginningOfLine {
13645 stop_at_soft_wraps: false,
13646 stop_at_indent: action.stop_at_indent,
13647 },
13648 window,
13649 cx,
13650 );
13651 this.backspace(&Backspace, window, cx);
13652 });
13653 }
13654
13655 pub fn move_to_end_of_line(
13656 &mut self,
13657 action: &MoveToEndOfLine,
13658 window: &mut Window,
13659 cx: &mut Context<Self>,
13660 ) {
13661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13662 self.change_selections(Default::default(), window, cx, |s| {
13663 s.move_cursors_with(|map, head, _| {
13664 (
13665 movement::line_end(map, head, action.stop_at_soft_wraps),
13666 SelectionGoal::None,
13667 )
13668 });
13669 })
13670 }
13671
13672 pub fn select_to_end_of_line(
13673 &mut self,
13674 action: &SelectToEndOfLine,
13675 window: &mut Window,
13676 cx: &mut Context<Self>,
13677 ) {
13678 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13679 self.change_selections(Default::default(), window, cx, |s| {
13680 s.move_heads_with(|map, head, _| {
13681 (
13682 movement::line_end(map, head, action.stop_at_soft_wraps),
13683 SelectionGoal::None,
13684 )
13685 });
13686 })
13687 }
13688
13689 pub fn delete_to_end_of_line(
13690 &mut self,
13691 _: &DeleteToEndOfLine,
13692 window: &mut Window,
13693 cx: &mut Context<Self>,
13694 ) {
13695 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13696 self.transact(window, cx, |this, window, cx| {
13697 this.select_to_end_of_line(
13698 &SelectToEndOfLine {
13699 stop_at_soft_wraps: false,
13700 },
13701 window,
13702 cx,
13703 );
13704 this.delete(&Delete, window, cx);
13705 });
13706 }
13707
13708 pub fn cut_to_end_of_line(
13709 &mut self,
13710 action: &CutToEndOfLine,
13711 window: &mut Window,
13712 cx: &mut Context<Self>,
13713 ) {
13714 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13715 self.transact(window, cx, |this, window, cx| {
13716 this.select_to_end_of_line(
13717 &SelectToEndOfLine {
13718 stop_at_soft_wraps: false,
13719 },
13720 window,
13721 cx,
13722 );
13723 if !action.stop_at_newlines {
13724 this.change_selections(Default::default(), window, cx, |s| {
13725 s.move_with(|_, sel| {
13726 if sel.is_empty() {
13727 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13728 }
13729 });
13730 });
13731 }
13732 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13733 let item = this.cut_common(false, window, cx);
13734 cx.write_to_clipboard(item);
13735 });
13736 }
13737
13738 pub fn move_to_start_of_paragraph(
13739 &mut self,
13740 _: &MoveToStartOfParagraph,
13741 window: &mut Window,
13742 cx: &mut Context<Self>,
13743 ) {
13744 if matches!(self.mode, EditorMode::SingleLine) {
13745 cx.propagate();
13746 return;
13747 }
13748 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13749 self.change_selections(Default::default(), window, cx, |s| {
13750 s.move_with(|map, selection| {
13751 selection.collapse_to(
13752 movement::start_of_paragraph(map, selection.head(), 1),
13753 SelectionGoal::None,
13754 )
13755 });
13756 })
13757 }
13758
13759 pub fn move_to_end_of_paragraph(
13760 &mut self,
13761 _: &MoveToEndOfParagraph,
13762 window: &mut Window,
13763 cx: &mut Context<Self>,
13764 ) {
13765 if matches!(self.mode, EditorMode::SingleLine) {
13766 cx.propagate();
13767 return;
13768 }
13769 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13770 self.change_selections(Default::default(), window, cx, |s| {
13771 s.move_with(|map, selection| {
13772 selection.collapse_to(
13773 movement::end_of_paragraph(map, selection.head(), 1),
13774 SelectionGoal::None,
13775 )
13776 });
13777 })
13778 }
13779
13780 pub fn select_to_start_of_paragraph(
13781 &mut self,
13782 _: &SelectToStartOfParagraph,
13783 window: &mut Window,
13784 cx: &mut Context<Self>,
13785 ) {
13786 if matches!(self.mode, EditorMode::SingleLine) {
13787 cx.propagate();
13788 return;
13789 }
13790 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13791 self.change_selections(Default::default(), window, cx, |s| {
13792 s.move_heads_with(|map, head, _| {
13793 (
13794 movement::start_of_paragraph(map, head, 1),
13795 SelectionGoal::None,
13796 )
13797 });
13798 })
13799 }
13800
13801 pub fn select_to_end_of_paragraph(
13802 &mut self,
13803 _: &SelectToEndOfParagraph,
13804 window: &mut Window,
13805 cx: &mut Context<Self>,
13806 ) {
13807 if matches!(self.mode, EditorMode::SingleLine) {
13808 cx.propagate();
13809 return;
13810 }
13811 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13812 self.change_selections(Default::default(), window, cx, |s| {
13813 s.move_heads_with(|map, head, _| {
13814 (
13815 movement::end_of_paragraph(map, head, 1),
13816 SelectionGoal::None,
13817 )
13818 });
13819 })
13820 }
13821
13822 pub fn move_to_start_of_excerpt(
13823 &mut self,
13824 _: &MoveToStartOfExcerpt,
13825 window: &mut Window,
13826 cx: &mut Context<Self>,
13827 ) {
13828 if matches!(self.mode, EditorMode::SingleLine) {
13829 cx.propagate();
13830 return;
13831 }
13832 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13833 self.change_selections(Default::default(), window, cx, |s| {
13834 s.move_with(|map, selection| {
13835 selection.collapse_to(
13836 movement::start_of_excerpt(
13837 map,
13838 selection.head(),
13839 workspace::searchable::Direction::Prev,
13840 ),
13841 SelectionGoal::None,
13842 )
13843 });
13844 })
13845 }
13846
13847 pub fn move_to_start_of_next_excerpt(
13848 &mut self,
13849 _: &MoveToStartOfNextExcerpt,
13850 window: &mut Window,
13851 cx: &mut Context<Self>,
13852 ) {
13853 if matches!(self.mode, EditorMode::SingleLine) {
13854 cx.propagate();
13855 return;
13856 }
13857
13858 self.change_selections(Default::default(), window, cx, |s| {
13859 s.move_with(|map, selection| {
13860 selection.collapse_to(
13861 movement::start_of_excerpt(
13862 map,
13863 selection.head(),
13864 workspace::searchable::Direction::Next,
13865 ),
13866 SelectionGoal::None,
13867 )
13868 });
13869 })
13870 }
13871
13872 pub fn move_to_end_of_excerpt(
13873 &mut self,
13874 _: &MoveToEndOfExcerpt,
13875 window: &mut Window,
13876 cx: &mut Context<Self>,
13877 ) {
13878 if matches!(self.mode, EditorMode::SingleLine) {
13879 cx.propagate();
13880 return;
13881 }
13882 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13883 self.change_selections(Default::default(), window, cx, |s| {
13884 s.move_with(|map, selection| {
13885 selection.collapse_to(
13886 movement::end_of_excerpt(
13887 map,
13888 selection.head(),
13889 workspace::searchable::Direction::Next,
13890 ),
13891 SelectionGoal::None,
13892 )
13893 });
13894 })
13895 }
13896
13897 pub fn move_to_end_of_previous_excerpt(
13898 &mut self,
13899 _: &MoveToEndOfPreviousExcerpt,
13900 window: &mut Window,
13901 cx: &mut Context<Self>,
13902 ) {
13903 if matches!(self.mode, EditorMode::SingleLine) {
13904 cx.propagate();
13905 return;
13906 }
13907 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13908 self.change_selections(Default::default(), window, cx, |s| {
13909 s.move_with(|map, selection| {
13910 selection.collapse_to(
13911 movement::end_of_excerpt(
13912 map,
13913 selection.head(),
13914 workspace::searchable::Direction::Prev,
13915 ),
13916 SelectionGoal::None,
13917 )
13918 });
13919 })
13920 }
13921
13922 pub fn select_to_start_of_excerpt(
13923 &mut self,
13924 _: &SelectToStartOfExcerpt,
13925 window: &mut Window,
13926 cx: &mut Context<Self>,
13927 ) {
13928 if matches!(self.mode, EditorMode::SingleLine) {
13929 cx.propagate();
13930 return;
13931 }
13932 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13933 self.change_selections(Default::default(), window, cx, |s| {
13934 s.move_heads_with(|map, head, _| {
13935 (
13936 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13937 SelectionGoal::None,
13938 )
13939 });
13940 })
13941 }
13942
13943 pub fn select_to_start_of_next_excerpt(
13944 &mut self,
13945 _: &SelectToStartOfNextExcerpt,
13946 window: &mut Window,
13947 cx: &mut Context<Self>,
13948 ) {
13949 if matches!(self.mode, EditorMode::SingleLine) {
13950 cx.propagate();
13951 return;
13952 }
13953 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13954 self.change_selections(Default::default(), window, cx, |s| {
13955 s.move_heads_with(|map, head, _| {
13956 (
13957 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13958 SelectionGoal::None,
13959 )
13960 });
13961 })
13962 }
13963
13964 pub fn select_to_end_of_excerpt(
13965 &mut self,
13966 _: &SelectToEndOfExcerpt,
13967 window: &mut Window,
13968 cx: &mut Context<Self>,
13969 ) {
13970 if matches!(self.mode, EditorMode::SingleLine) {
13971 cx.propagate();
13972 return;
13973 }
13974 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13975 self.change_selections(Default::default(), window, cx, |s| {
13976 s.move_heads_with(|map, head, _| {
13977 (
13978 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13979 SelectionGoal::None,
13980 )
13981 });
13982 })
13983 }
13984
13985 pub fn select_to_end_of_previous_excerpt(
13986 &mut self,
13987 _: &SelectToEndOfPreviousExcerpt,
13988 window: &mut Window,
13989 cx: &mut Context<Self>,
13990 ) {
13991 if matches!(self.mode, EditorMode::SingleLine) {
13992 cx.propagate();
13993 return;
13994 }
13995 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13996 self.change_selections(Default::default(), window, cx, |s| {
13997 s.move_heads_with(|map, head, _| {
13998 (
13999 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14000 SelectionGoal::None,
14001 )
14002 });
14003 })
14004 }
14005
14006 pub fn move_to_beginning(
14007 &mut self,
14008 _: &MoveToBeginning,
14009 window: &mut Window,
14010 cx: &mut Context<Self>,
14011 ) {
14012 if matches!(self.mode, EditorMode::SingleLine) {
14013 cx.propagate();
14014 return;
14015 }
14016 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14017 self.change_selections(Default::default(), window, cx, |s| {
14018 s.select_ranges(vec![0..0]);
14019 });
14020 }
14021
14022 pub fn select_to_beginning(
14023 &mut self,
14024 _: &SelectToBeginning,
14025 window: &mut Window,
14026 cx: &mut Context<Self>,
14027 ) {
14028 let mut selection = self.selections.last::<Point>(cx);
14029 selection.set_head(Point::zero(), SelectionGoal::None);
14030 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14031 self.change_selections(Default::default(), window, cx, |s| {
14032 s.select(vec![selection]);
14033 });
14034 }
14035
14036 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14037 if matches!(self.mode, EditorMode::SingleLine) {
14038 cx.propagate();
14039 return;
14040 }
14041 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14042 let cursor = self.buffer.read(cx).read(cx).len();
14043 self.change_selections(Default::default(), window, cx, |s| {
14044 s.select_ranges(vec![cursor..cursor])
14045 });
14046 }
14047
14048 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14049 self.nav_history = nav_history;
14050 }
14051
14052 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14053 self.nav_history.as_ref()
14054 }
14055
14056 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14057 self.push_to_nav_history(
14058 self.selections.newest_anchor().head(),
14059 None,
14060 false,
14061 true,
14062 cx,
14063 );
14064 }
14065
14066 fn push_to_nav_history(
14067 &mut self,
14068 cursor_anchor: Anchor,
14069 new_position: Option<Point>,
14070 is_deactivate: bool,
14071 always: bool,
14072 cx: &mut Context<Self>,
14073 ) {
14074 if let Some(nav_history) = self.nav_history.as_mut() {
14075 let buffer = self.buffer.read(cx).read(cx);
14076 let cursor_position = cursor_anchor.to_point(&buffer);
14077 let scroll_state = self.scroll_manager.anchor();
14078 let scroll_top_row = scroll_state.top_row(&buffer);
14079 drop(buffer);
14080
14081 if let Some(new_position) = new_position {
14082 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14083 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14084 return;
14085 }
14086 }
14087
14088 nav_history.push(
14089 Some(NavigationData {
14090 cursor_anchor,
14091 cursor_position,
14092 scroll_anchor: scroll_state,
14093 scroll_top_row,
14094 }),
14095 cx,
14096 );
14097 cx.emit(EditorEvent::PushedToNavHistory {
14098 anchor: cursor_anchor,
14099 is_deactivate,
14100 })
14101 }
14102 }
14103
14104 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14105 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14106 let buffer = self.buffer.read(cx).snapshot(cx);
14107 let mut selection = self.selections.first::<usize>(cx);
14108 selection.set_head(buffer.len(), SelectionGoal::None);
14109 self.change_selections(Default::default(), window, cx, |s| {
14110 s.select(vec![selection]);
14111 });
14112 }
14113
14114 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14115 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14116 let end = self.buffer.read(cx).read(cx).len();
14117 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14118 s.select_ranges(vec![0..end]);
14119 });
14120 }
14121
14122 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14123 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14124 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14125 let mut selections = self.selections.all::<Point>(cx);
14126 let max_point = display_map.buffer_snapshot().max_point();
14127 for selection in &mut selections {
14128 let rows = selection.spanned_rows(true, &display_map);
14129 selection.start = Point::new(rows.start.0, 0);
14130 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14131 selection.reversed = false;
14132 }
14133 self.change_selections(Default::default(), window, cx, |s| {
14134 s.select(selections);
14135 });
14136 }
14137
14138 pub fn split_selection_into_lines(
14139 &mut self,
14140 action: &SplitSelectionIntoLines,
14141 window: &mut Window,
14142 cx: &mut Context<Self>,
14143 ) {
14144 let selections = self
14145 .selections
14146 .all::<Point>(cx)
14147 .into_iter()
14148 .map(|selection| selection.start..selection.end)
14149 .collect::<Vec<_>>();
14150 self.unfold_ranges(&selections, true, true, cx);
14151
14152 let mut new_selection_ranges = Vec::new();
14153 {
14154 let buffer = self.buffer.read(cx).read(cx);
14155 for selection in selections {
14156 for row in selection.start.row..selection.end.row {
14157 let line_start = Point::new(row, 0);
14158 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14159
14160 if action.keep_selections {
14161 // Keep the selection range for each line
14162 let selection_start = if row == selection.start.row {
14163 selection.start
14164 } else {
14165 line_start
14166 };
14167 new_selection_ranges.push(selection_start..line_end);
14168 } else {
14169 // Collapse to cursor at end of line
14170 new_selection_ranges.push(line_end..line_end);
14171 }
14172 }
14173
14174 let is_multiline_selection = selection.start.row != selection.end.row;
14175 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14176 // so this action feels more ergonomic when paired with other selection operations
14177 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14178 if !should_skip_last {
14179 if action.keep_selections {
14180 if is_multiline_selection {
14181 let line_start = Point::new(selection.end.row, 0);
14182 new_selection_ranges.push(line_start..selection.end);
14183 } else {
14184 new_selection_ranges.push(selection.start..selection.end);
14185 }
14186 } else {
14187 new_selection_ranges.push(selection.end..selection.end);
14188 }
14189 }
14190 }
14191 }
14192 self.change_selections(Default::default(), window, cx, |s| {
14193 s.select_ranges(new_selection_ranges);
14194 });
14195 }
14196
14197 pub fn add_selection_above(
14198 &mut self,
14199 _: &AddSelectionAbove,
14200 window: &mut Window,
14201 cx: &mut Context<Self>,
14202 ) {
14203 self.add_selection(true, window, cx);
14204 }
14205
14206 pub fn add_selection_below(
14207 &mut self,
14208 _: &AddSelectionBelow,
14209 window: &mut Window,
14210 cx: &mut Context<Self>,
14211 ) {
14212 self.add_selection(false, window, cx);
14213 }
14214
14215 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14216 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14217
14218 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14219 let all_selections = self.selections.all::<Point>(cx);
14220 let text_layout_details = self.text_layout_details(window);
14221
14222 let (mut columnar_selections, new_selections_to_columnarize) = {
14223 if let Some(state) = self.add_selections_state.as_ref() {
14224 let columnar_selection_ids: HashSet<_> = state
14225 .groups
14226 .iter()
14227 .flat_map(|group| group.stack.iter())
14228 .copied()
14229 .collect();
14230
14231 all_selections
14232 .into_iter()
14233 .partition(|s| columnar_selection_ids.contains(&s.id))
14234 } else {
14235 (Vec::new(), all_selections)
14236 }
14237 };
14238
14239 let mut state = self
14240 .add_selections_state
14241 .take()
14242 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14243
14244 for selection in new_selections_to_columnarize {
14245 let range = selection.display_range(&display_map).sorted();
14246 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14247 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14248 let positions = start_x.min(end_x)..start_x.max(end_x);
14249 let mut stack = Vec::new();
14250 for row in range.start.row().0..=range.end.row().0 {
14251 if let Some(selection) = self.selections.build_columnar_selection(
14252 &display_map,
14253 DisplayRow(row),
14254 &positions,
14255 selection.reversed,
14256 &text_layout_details,
14257 ) {
14258 stack.push(selection.id);
14259 columnar_selections.push(selection);
14260 }
14261 }
14262 if !stack.is_empty() {
14263 if above {
14264 stack.reverse();
14265 }
14266 state.groups.push(AddSelectionsGroup { above, stack });
14267 }
14268 }
14269
14270 let mut final_selections = Vec::new();
14271 let end_row = if above {
14272 DisplayRow(0)
14273 } else {
14274 display_map.max_point().row()
14275 };
14276
14277 let mut last_added_item_per_group = HashMap::default();
14278 for group in state.groups.iter_mut() {
14279 if let Some(last_id) = group.stack.last() {
14280 last_added_item_per_group.insert(*last_id, group);
14281 }
14282 }
14283
14284 for selection in columnar_selections {
14285 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14286 if above == group.above {
14287 let range = selection.display_range(&display_map).sorted();
14288 debug_assert_eq!(range.start.row(), range.end.row());
14289 let mut row = range.start.row();
14290 let positions =
14291 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14292 Pixels::from(start)..Pixels::from(end)
14293 } else {
14294 let start_x =
14295 display_map.x_for_display_point(range.start, &text_layout_details);
14296 let end_x =
14297 display_map.x_for_display_point(range.end, &text_layout_details);
14298 start_x.min(end_x)..start_x.max(end_x)
14299 };
14300
14301 let mut maybe_new_selection = None;
14302 while row != end_row {
14303 if above {
14304 row.0 -= 1;
14305 } else {
14306 row.0 += 1;
14307 }
14308 if let Some(new_selection) = self.selections.build_columnar_selection(
14309 &display_map,
14310 row,
14311 &positions,
14312 selection.reversed,
14313 &text_layout_details,
14314 ) {
14315 maybe_new_selection = Some(new_selection);
14316 break;
14317 }
14318 }
14319
14320 if let Some(new_selection) = maybe_new_selection {
14321 group.stack.push(new_selection.id);
14322 if above {
14323 final_selections.push(new_selection);
14324 final_selections.push(selection);
14325 } else {
14326 final_selections.push(selection);
14327 final_selections.push(new_selection);
14328 }
14329 } else {
14330 final_selections.push(selection);
14331 }
14332 } else {
14333 group.stack.pop();
14334 }
14335 } else {
14336 final_selections.push(selection);
14337 }
14338 }
14339
14340 self.change_selections(Default::default(), window, cx, |s| {
14341 s.select(final_selections);
14342 });
14343
14344 let final_selection_ids: HashSet<_> = self
14345 .selections
14346 .all::<Point>(cx)
14347 .iter()
14348 .map(|s| s.id)
14349 .collect();
14350 state.groups.retain_mut(|group| {
14351 // selections might get merged above so we remove invalid items from stacks
14352 group.stack.retain(|id| final_selection_ids.contains(id));
14353
14354 // single selection in stack can be treated as initial state
14355 group.stack.len() > 1
14356 });
14357
14358 if !state.groups.is_empty() {
14359 self.add_selections_state = Some(state);
14360 }
14361 }
14362
14363 fn select_match_ranges(
14364 &mut self,
14365 range: Range<usize>,
14366 reversed: bool,
14367 replace_newest: bool,
14368 auto_scroll: Option<Autoscroll>,
14369 window: &mut Window,
14370 cx: &mut Context<Editor>,
14371 ) {
14372 self.unfold_ranges(
14373 std::slice::from_ref(&range),
14374 false,
14375 auto_scroll.is_some(),
14376 cx,
14377 );
14378 let effects = if let Some(scroll) = auto_scroll {
14379 SelectionEffects::scroll(scroll)
14380 } else {
14381 SelectionEffects::no_scroll()
14382 };
14383 self.change_selections(effects, window, cx, |s| {
14384 if replace_newest {
14385 s.delete(s.newest_anchor().id);
14386 }
14387 if reversed {
14388 s.insert_range(range.end..range.start);
14389 } else {
14390 s.insert_range(range);
14391 }
14392 });
14393 }
14394
14395 pub fn select_next_match_internal(
14396 &mut self,
14397 display_map: &DisplaySnapshot,
14398 replace_newest: bool,
14399 autoscroll: Option<Autoscroll>,
14400 window: &mut Window,
14401 cx: &mut Context<Self>,
14402 ) -> Result<()> {
14403 let buffer = display_map.buffer_snapshot();
14404 let mut selections = self.selections.all::<usize>(cx);
14405 if let Some(mut select_next_state) = self.select_next_state.take() {
14406 let query = &select_next_state.query;
14407 if !select_next_state.done {
14408 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14409 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14410 let mut next_selected_range = None;
14411
14412 let bytes_after_last_selection =
14413 buffer.bytes_in_range(last_selection.end..buffer.len());
14414 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14415 let query_matches = query
14416 .stream_find_iter(bytes_after_last_selection)
14417 .map(|result| (last_selection.end, result))
14418 .chain(
14419 query
14420 .stream_find_iter(bytes_before_first_selection)
14421 .map(|result| (0, result)),
14422 );
14423
14424 for (start_offset, query_match) in query_matches {
14425 let query_match = query_match.unwrap(); // can only fail due to I/O
14426 let offset_range =
14427 start_offset + query_match.start()..start_offset + query_match.end();
14428
14429 if !select_next_state.wordwise
14430 || (!buffer.is_inside_word(offset_range.start, None)
14431 && !buffer.is_inside_word(offset_range.end, None))
14432 {
14433 // TODO: This is n^2, because we might check all the selections
14434 if !selections
14435 .iter()
14436 .any(|selection| selection.range().overlaps(&offset_range))
14437 {
14438 next_selected_range = Some(offset_range);
14439 break;
14440 }
14441 }
14442 }
14443
14444 if let Some(next_selected_range) = next_selected_range {
14445 self.select_match_ranges(
14446 next_selected_range,
14447 last_selection.reversed,
14448 replace_newest,
14449 autoscroll,
14450 window,
14451 cx,
14452 );
14453 } else {
14454 select_next_state.done = true;
14455 }
14456 }
14457
14458 self.select_next_state = Some(select_next_state);
14459 } else {
14460 let mut only_carets = true;
14461 let mut same_text_selected = true;
14462 let mut selected_text = None;
14463
14464 let mut selections_iter = selections.iter().peekable();
14465 while let Some(selection) = selections_iter.next() {
14466 if selection.start != selection.end {
14467 only_carets = false;
14468 }
14469
14470 if same_text_selected {
14471 if selected_text.is_none() {
14472 selected_text =
14473 Some(buffer.text_for_range(selection.range()).collect::<String>());
14474 }
14475
14476 if let Some(next_selection) = selections_iter.peek() {
14477 if next_selection.range().len() == selection.range().len() {
14478 let next_selected_text = buffer
14479 .text_for_range(next_selection.range())
14480 .collect::<String>();
14481 if Some(next_selected_text) != selected_text {
14482 same_text_selected = false;
14483 selected_text = None;
14484 }
14485 } else {
14486 same_text_selected = false;
14487 selected_text = None;
14488 }
14489 }
14490 }
14491 }
14492
14493 if only_carets {
14494 for selection in &mut selections {
14495 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14496 selection.start = word_range.start;
14497 selection.end = word_range.end;
14498 selection.goal = SelectionGoal::None;
14499 selection.reversed = false;
14500 self.select_match_ranges(
14501 selection.start..selection.end,
14502 selection.reversed,
14503 replace_newest,
14504 autoscroll,
14505 window,
14506 cx,
14507 );
14508 }
14509
14510 if selections.len() == 1 {
14511 let selection = selections
14512 .last()
14513 .expect("ensured that there's only one selection");
14514 let query = buffer
14515 .text_for_range(selection.start..selection.end)
14516 .collect::<String>();
14517 let is_empty = query.is_empty();
14518 let select_state = SelectNextState {
14519 query: AhoCorasick::new(&[query])?,
14520 wordwise: true,
14521 done: is_empty,
14522 };
14523 self.select_next_state = Some(select_state);
14524 } else {
14525 self.select_next_state = None;
14526 }
14527 } else if let Some(selected_text) = selected_text {
14528 self.select_next_state = Some(SelectNextState {
14529 query: AhoCorasick::new(&[selected_text])?,
14530 wordwise: false,
14531 done: false,
14532 });
14533 self.select_next_match_internal(
14534 display_map,
14535 replace_newest,
14536 autoscroll,
14537 window,
14538 cx,
14539 )?;
14540 }
14541 }
14542 Ok(())
14543 }
14544
14545 pub fn select_all_matches(
14546 &mut self,
14547 _action: &SelectAllMatches,
14548 window: &mut Window,
14549 cx: &mut Context<Self>,
14550 ) -> Result<()> {
14551 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14552
14553 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14554
14555 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14556 let Some(select_next_state) = self.select_next_state.as_mut() else {
14557 return Ok(());
14558 };
14559 if select_next_state.done {
14560 return Ok(());
14561 }
14562
14563 let mut new_selections = Vec::new();
14564
14565 let reversed = self.selections.oldest::<usize>(cx).reversed;
14566 let buffer = display_map.buffer_snapshot();
14567 let query_matches = select_next_state
14568 .query
14569 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14570
14571 for query_match in query_matches.into_iter() {
14572 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14573 let offset_range = if reversed {
14574 query_match.end()..query_match.start()
14575 } else {
14576 query_match.start()..query_match.end()
14577 };
14578
14579 if !select_next_state.wordwise
14580 || (!buffer.is_inside_word(offset_range.start, None)
14581 && !buffer.is_inside_word(offset_range.end, None))
14582 {
14583 new_selections.push(offset_range.start..offset_range.end);
14584 }
14585 }
14586
14587 select_next_state.done = true;
14588
14589 if new_selections.is_empty() {
14590 log::error!("bug: new_selections is empty in select_all_matches");
14591 return Ok(());
14592 }
14593
14594 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14595 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14596 selections.select_ranges(new_selections)
14597 });
14598
14599 Ok(())
14600 }
14601
14602 pub fn select_next(
14603 &mut self,
14604 action: &SelectNext,
14605 window: &mut Window,
14606 cx: &mut Context<Self>,
14607 ) -> Result<()> {
14608 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14609 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14610 self.select_next_match_internal(
14611 &display_map,
14612 action.replace_newest,
14613 Some(Autoscroll::newest()),
14614 window,
14615 cx,
14616 )?;
14617 Ok(())
14618 }
14619
14620 pub fn select_previous(
14621 &mut self,
14622 action: &SelectPrevious,
14623 window: &mut Window,
14624 cx: &mut Context<Self>,
14625 ) -> Result<()> {
14626 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14627 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14628 let buffer = display_map.buffer_snapshot();
14629 let mut selections = self.selections.all::<usize>(cx);
14630 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14631 let query = &select_prev_state.query;
14632 if !select_prev_state.done {
14633 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14634 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14635 let mut next_selected_range = None;
14636 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14637 let bytes_before_last_selection =
14638 buffer.reversed_bytes_in_range(0..last_selection.start);
14639 let bytes_after_first_selection =
14640 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14641 let query_matches = query
14642 .stream_find_iter(bytes_before_last_selection)
14643 .map(|result| (last_selection.start, result))
14644 .chain(
14645 query
14646 .stream_find_iter(bytes_after_first_selection)
14647 .map(|result| (buffer.len(), result)),
14648 );
14649 for (end_offset, query_match) in query_matches {
14650 let query_match = query_match.unwrap(); // can only fail due to I/O
14651 let offset_range =
14652 end_offset - query_match.end()..end_offset - query_match.start();
14653
14654 if !select_prev_state.wordwise
14655 || (!buffer.is_inside_word(offset_range.start, None)
14656 && !buffer.is_inside_word(offset_range.end, None))
14657 {
14658 next_selected_range = Some(offset_range);
14659 break;
14660 }
14661 }
14662
14663 if let Some(next_selected_range) = next_selected_range {
14664 self.select_match_ranges(
14665 next_selected_range,
14666 last_selection.reversed,
14667 action.replace_newest,
14668 Some(Autoscroll::newest()),
14669 window,
14670 cx,
14671 );
14672 } else {
14673 select_prev_state.done = true;
14674 }
14675 }
14676
14677 self.select_prev_state = Some(select_prev_state);
14678 } else {
14679 let mut only_carets = true;
14680 let mut same_text_selected = true;
14681 let mut selected_text = None;
14682
14683 let mut selections_iter = selections.iter().peekable();
14684 while let Some(selection) = selections_iter.next() {
14685 if selection.start != selection.end {
14686 only_carets = false;
14687 }
14688
14689 if same_text_selected {
14690 if selected_text.is_none() {
14691 selected_text =
14692 Some(buffer.text_for_range(selection.range()).collect::<String>());
14693 }
14694
14695 if let Some(next_selection) = selections_iter.peek() {
14696 if next_selection.range().len() == selection.range().len() {
14697 let next_selected_text = buffer
14698 .text_for_range(next_selection.range())
14699 .collect::<String>();
14700 if Some(next_selected_text) != selected_text {
14701 same_text_selected = false;
14702 selected_text = None;
14703 }
14704 } else {
14705 same_text_selected = false;
14706 selected_text = None;
14707 }
14708 }
14709 }
14710 }
14711
14712 if only_carets {
14713 for selection in &mut selections {
14714 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14715 selection.start = word_range.start;
14716 selection.end = word_range.end;
14717 selection.goal = SelectionGoal::None;
14718 selection.reversed = false;
14719 self.select_match_ranges(
14720 selection.start..selection.end,
14721 selection.reversed,
14722 action.replace_newest,
14723 Some(Autoscroll::newest()),
14724 window,
14725 cx,
14726 );
14727 }
14728 if selections.len() == 1 {
14729 let selection = selections
14730 .last()
14731 .expect("ensured that there's only one selection");
14732 let query = buffer
14733 .text_for_range(selection.start..selection.end)
14734 .collect::<String>();
14735 let is_empty = query.is_empty();
14736 let select_state = SelectNextState {
14737 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14738 wordwise: true,
14739 done: is_empty,
14740 };
14741 self.select_prev_state = Some(select_state);
14742 } else {
14743 self.select_prev_state = None;
14744 }
14745 } else if let Some(selected_text) = selected_text {
14746 self.select_prev_state = Some(SelectNextState {
14747 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14748 wordwise: false,
14749 done: false,
14750 });
14751 self.select_previous(action, window, cx)?;
14752 }
14753 }
14754 Ok(())
14755 }
14756
14757 pub fn find_next_match(
14758 &mut self,
14759 _: &FindNextMatch,
14760 window: &mut Window,
14761 cx: &mut Context<Self>,
14762 ) -> Result<()> {
14763 let selections = self.selections.disjoint_anchors_arc();
14764 match selections.first() {
14765 Some(first) if selections.len() >= 2 => {
14766 self.change_selections(Default::default(), window, cx, |s| {
14767 s.select_ranges([first.range()]);
14768 });
14769 }
14770 _ => self.select_next(
14771 &SelectNext {
14772 replace_newest: true,
14773 },
14774 window,
14775 cx,
14776 )?,
14777 }
14778 Ok(())
14779 }
14780
14781 pub fn find_previous_match(
14782 &mut self,
14783 _: &FindPreviousMatch,
14784 window: &mut Window,
14785 cx: &mut Context<Self>,
14786 ) -> Result<()> {
14787 let selections = self.selections.disjoint_anchors_arc();
14788 match selections.last() {
14789 Some(last) if selections.len() >= 2 => {
14790 self.change_selections(Default::default(), window, cx, |s| {
14791 s.select_ranges([last.range()]);
14792 });
14793 }
14794 _ => self.select_previous(
14795 &SelectPrevious {
14796 replace_newest: true,
14797 },
14798 window,
14799 cx,
14800 )?,
14801 }
14802 Ok(())
14803 }
14804
14805 pub fn toggle_comments(
14806 &mut self,
14807 action: &ToggleComments,
14808 window: &mut Window,
14809 cx: &mut Context<Self>,
14810 ) {
14811 if self.read_only(cx) {
14812 return;
14813 }
14814 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14815 let text_layout_details = &self.text_layout_details(window);
14816 self.transact(window, cx, |this, window, cx| {
14817 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14818 let mut edits = Vec::new();
14819 let mut selection_edit_ranges = Vec::new();
14820 let mut last_toggled_row = None;
14821 let snapshot = this.buffer.read(cx).read(cx);
14822 let empty_str: Arc<str> = Arc::default();
14823 let mut suffixes_inserted = Vec::new();
14824 let ignore_indent = action.ignore_indent;
14825
14826 fn comment_prefix_range(
14827 snapshot: &MultiBufferSnapshot,
14828 row: MultiBufferRow,
14829 comment_prefix: &str,
14830 comment_prefix_whitespace: &str,
14831 ignore_indent: bool,
14832 ) -> Range<Point> {
14833 let indent_size = if ignore_indent {
14834 0
14835 } else {
14836 snapshot.indent_size_for_line(row).len
14837 };
14838
14839 let start = Point::new(row.0, indent_size);
14840
14841 let mut line_bytes = snapshot
14842 .bytes_in_range(start..snapshot.max_point())
14843 .flatten()
14844 .copied();
14845
14846 // If this line currently begins with the line comment prefix, then record
14847 // the range containing the prefix.
14848 if line_bytes
14849 .by_ref()
14850 .take(comment_prefix.len())
14851 .eq(comment_prefix.bytes())
14852 {
14853 // Include any whitespace that matches the comment prefix.
14854 let matching_whitespace_len = line_bytes
14855 .zip(comment_prefix_whitespace.bytes())
14856 .take_while(|(a, b)| a == b)
14857 .count() as u32;
14858 let end = Point::new(
14859 start.row,
14860 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14861 );
14862 start..end
14863 } else {
14864 start..start
14865 }
14866 }
14867
14868 fn comment_suffix_range(
14869 snapshot: &MultiBufferSnapshot,
14870 row: MultiBufferRow,
14871 comment_suffix: &str,
14872 comment_suffix_has_leading_space: bool,
14873 ) -> Range<Point> {
14874 let end = Point::new(row.0, snapshot.line_len(row));
14875 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14876
14877 let mut line_end_bytes = snapshot
14878 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14879 .flatten()
14880 .copied();
14881
14882 let leading_space_len = if suffix_start_column > 0
14883 && line_end_bytes.next() == Some(b' ')
14884 && comment_suffix_has_leading_space
14885 {
14886 1
14887 } else {
14888 0
14889 };
14890
14891 // If this line currently begins with the line comment prefix, then record
14892 // the range containing the prefix.
14893 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14894 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14895 start..end
14896 } else {
14897 end..end
14898 }
14899 }
14900
14901 // TODO: Handle selections that cross excerpts
14902 for selection in &mut selections {
14903 let start_column = snapshot
14904 .indent_size_for_line(MultiBufferRow(selection.start.row))
14905 .len;
14906 let language = if let Some(language) =
14907 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14908 {
14909 language
14910 } else {
14911 continue;
14912 };
14913
14914 selection_edit_ranges.clear();
14915
14916 // If multiple selections contain a given row, avoid processing that
14917 // row more than once.
14918 let mut start_row = MultiBufferRow(selection.start.row);
14919 if last_toggled_row == Some(start_row) {
14920 start_row = start_row.next_row();
14921 }
14922 let end_row =
14923 if selection.end.row > selection.start.row && selection.end.column == 0 {
14924 MultiBufferRow(selection.end.row - 1)
14925 } else {
14926 MultiBufferRow(selection.end.row)
14927 };
14928 last_toggled_row = Some(end_row);
14929
14930 if start_row > end_row {
14931 continue;
14932 }
14933
14934 // If the language has line comments, toggle those.
14935 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14936
14937 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14938 if ignore_indent {
14939 full_comment_prefixes = full_comment_prefixes
14940 .into_iter()
14941 .map(|s| Arc::from(s.trim_end()))
14942 .collect();
14943 }
14944
14945 if !full_comment_prefixes.is_empty() {
14946 let first_prefix = full_comment_prefixes
14947 .first()
14948 .expect("prefixes is non-empty");
14949 let prefix_trimmed_lengths = full_comment_prefixes
14950 .iter()
14951 .map(|p| p.trim_end_matches(' ').len())
14952 .collect::<SmallVec<[usize; 4]>>();
14953
14954 let mut all_selection_lines_are_comments = true;
14955
14956 for row in start_row.0..=end_row.0 {
14957 let row = MultiBufferRow(row);
14958 if start_row < end_row && snapshot.is_line_blank(row) {
14959 continue;
14960 }
14961
14962 let prefix_range = full_comment_prefixes
14963 .iter()
14964 .zip(prefix_trimmed_lengths.iter().copied())
14965 .map(|(prefix, trimmed_prefix_len)| {
14966 comment_prefix_range(
14967 snapshot.deref(),
14968 row,
14969 &prefix[..trimmed_prefix_len],
14970 &prefix[trimmed_prefix_len..],
14971 ignore_indent,
14972 )
14973 })
14974 .max_by_key(|range| range.end.column - range.start.column)
14975 .expect("prefixes is non-empty");
14976
14977 if prefix_range.is_empty() {
14978 all_selection_lines_are_comments = false;
14979 }
14980
14981 selection_edit_ranges.push(prefix_range);
14982 }
14983
14984 if all_selection_lines_are_comments {
14985 edits.extend(
14986 selection_edit_ranges
14987 .iter()
14988 .cloned()
14989 .map(|range| (range, empty_str.clone())),
14990 );
14991 } else {
14992 let min_column = selection_edit_ranges
14993 .iter()
14994 .map(|range| range.start.column)
14995 .min()
14996 .unwrap_or(0);
14997 edits.extend(selection_edit_ranges.iter().map(|range| {
14998 let position = Point::new(range.start.row, min_column);
14999 (position..position, first_prefix.clone())
15000 }));
15001 }
15002 } else if let Some(BlockCommentConfig {
15003 start: full_comment_prefix,
15004 end: comment_suffix,
15005 ..
15006 }) = language.block_comment()
15007 {
15008 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15009 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15010 let prefix_range = comment_prefix_range(
15011 snapshot.deref(),
15012 start_row,
15013 comment_prefix,
15014 comment_prefix_whitespace,
15015 ignore_indent,
15016 );
15017 let suffix_range = comment_suffix_range(
15018 snapshot.deref(),
15019 end_row,
15020 comment_suffix.trim_start_matches(' '),
15021 comment_suffix.starts_with(' '),
15022 );
15023
15024 if prefix_range.is_empty() || suffix_range.is_empty() {
15025 edits.push((
15026 prefix_range.start..prefix_range.start,
15027 full_comment_prefix.clone(),
15028 ));
15029 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15030 suffixes_inserted.push((end_row, comment_suffix.len()));
15031 } else {
15032 edits.push((prefix_range, empty_str.clone()));
15033 edits.push((suffix_range, empty_str.clone()));
15034 }
15035 } else {
15036 continue;
15037 }
15038 }
15039
15040 drop(snapshot);
15041 this.buffer.update(cx, |buffer, cx| {
15042 buffer.edit(edits, None, cx);
15043 });
15044
15045 // Adjust selections so that they end before any comment suffixes that
15046 // were inserted.
15047 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15048 let mut selections = this.selections.all::<Point>(cx);
15049 let snapshot = this.buffer.read(cx).read(cx);
15050 for selection in &mut selections {
15051 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15052 match row.cmp(&MultiBufferRow(selection.end.row)) {
15053 Ordering::Less => {
15054 suffixes_inserted.next();
15055 continue;
15056 }
15057 Ordering::Greater => break,
15058 Ordering::Equal => {
15059 if selection.end.column == snapshot.line_len(row) {
15060 if selection.is_empty() {
15061 selection.start.column -= suffix_len as u32;
15062 }
15063 selection.end.column -= suffix_len as u32;
15064 }
15065 break;
15066 }
15067 }
15068 }
15069 }
15070
15071 drop(snapshot);
15072 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15073
15074 let selections = this.selections.all::<Point>(cx);
15075 let selections_on_single_row = selections.windows(2).all(|selections| {
15076 selections[0].start.row == selections[1].start.row
15077 && selections[0].end.row == selections[1].end.row
15078 && selections[0].start.row == selections[0].end.row
15079 });
15080 let selections_selecting = selections
15081 .iter()
15082 .any(|selection| selection.start != selection.end);
15083 let advance_downwards = action.advance_downwards
15084 && selections_on_single_row
15085 && !selections_selecting
15086 && !matches!(this.mode, EditorMode::SingleLine);
15087
15088 if advance_downwards {
15089 let snapshot = this.buffer.read(cx).snapshot(cx);
15090
15091 this.change_selections(Default::default(), window, cx, |s| {
15092 s.move_cursors_with(|display_snapshot, display_point, _| {
15093 let mut point = display_point.to_point(display_snapshot);
15094 point.row += 1;
15095 point = snapshot.clip_point(point, Bias::Left);
15096 let display_point = point.to_display_point(display_snapshot);
15097 let goal = SelectionGoal::HorizontalPosition(
15098 display_snapshot
15099 .x_for_display_point(display_point, text_layout_details)
15100 .into(),
15101 );
15102 (display_point, goal)
15103 })
15104 });
15105 }
15106 });
15107 }
15108
15109 pub fn select_enclosing_symbol(
15110 &mut self,
15111 _: &SelectEnclosingSymbol,
15112 window: &mut Window,
15113 cx: &mut Context<Self>,
15114 ) {
15115 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15116
15117 let buffer = self.buffer.read(cx).snapshot(cx);
15118 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
15119
15120 fn update_selection(
15121 selection: &Selection<usize>,
15122 buffer_snap: &MultiBufferSnapshot,
15123 ) -> Option<Selection<usize>> {
15124 let cursor = selection.head();
15125 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15126 for symbol in symbols.iter().rev() {
15127 let start = symbol.range.start.to_offset(buffer_snap);
15128 let end = symbol.range.end.to_offset(buffer_snap);
15129 let new_range = start..end;
15130 if start < selection.start || end > selection.end {
15131 return Some(Selection {
15132 id: selection.id,
15133 start: new_range.start,
15134 end: new_range.end,
15135 goal: SelectionGoal::None,
15136 reversed: selection.reversed,
15137 });
15138 }
15139 }
15140 None
15141 }
15142
15143 let mut selected_larger_symbol = false;
15144 let new_selections = old_selections
15145 .iter()
15146 .map(|selection| match update_selection(selection, &buffer) {
15147 Some(new_selection) => {
15148 if new_selection.range() != selection.range() {
15149 selected_larger_symbol = true;
15150 }
15151 new_selection
15152 }
15153 None => selection.clone(),
15154 })
15155 .collect::<Vec<_>>();
15156
15157 if selected_larger_symbol {
15158 self.change_selections(Default::default(), window, cx, |s| {
15159 s.select(new_selections);
15160 });
15161 }
15162 }
15163
15164 pub fn select_larger_syntax_node(
15165 &mut self,
15166 _: &SelectLargerSyntaxNode,
15167 window: &mut Window,
15168 cx: &mut Context<Self>,
15169 ) {
15170 let Some(visible_row_count) = self.visible_row_count() else {
15171 return;
15172 };
15173 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15174 if old_selections.is_empty() {
15175 return;
15176 }
15177
15178 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15179
15180 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15181 let buffer = self.buffer.read(cx).snapshot(cx);
15182
15183 let mut selected_larger_node = false;
15184 let mut new_selections = old_selections
15185 .iter()
15186 .map(|selection| {
15187 let old_range = selection.start..selection.end;
15188
15189 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15190 // manually select word at selection
15191 if ["string_content", "inline"].contains(&node.kind()) {
15192 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15193 // ignore if word is already selected
15194 if !word_range.is_empty() && old_range != word_range {
15195 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15196 // only select word if start and end point belongs to same word
15197 if word_range == last_word_range {
15198 selected_larger_node = true;
15199 return Selection {
15200 id: selection.id,
15201 start: word_range.start,
15202 end: word_range.end,
15203 goal: SelectionGoal::None,
15204 reversed: selection.reversed,
15205 };
15206 }
15207 }
15208 }
15209 }
15210
15211 let mut new_range = old_range.clone();
15212 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15213 new_range = range;
15214 if !node.is_named() {
15215 continue;
15216 }
15217 if !display_map.intersects_fold(new_range.start)
15218 && !display_map.intersects_fold(new_range.end)
15219 {
15220 break;
15221 }
15222 }
15223
15224 selected_larger_node |= new_range != old_range;
15225 Selection {
15226 id: selection.id,
15227 start: new_range.start,
15228 end: new_range.end,
15229 goal: SelectionGoal::None,
15230 reversed: selection.reversed,
15231 }
15232 })
15233 .collect::<Vec<_>>();
15234
15235 if !selected_larger_node {
15236 return; // don't put this call in the history
15237 }
15238
15239 // scroll based on transformation done to the last selection created by the user
15240 let (last_old, last_new) = old_selections
15241 .last()
15242 .zip(new_selections.last().cloned())
15243 .expect("old_selections isn't empty");
15244
15245 // revert selection
15246 let is_selection_reversed = {
15247 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15248 new_selections.last_mut().expect("checked above").reversed =
15249 should_newest_selection_be_reversed;
15250 should_newest_selection_be_reversed
15251 };
15252
15253 if selected_larger_node {
15254 self.select_syntax_node_history.disable_clearing = true;
15255 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15256 s.select(new_selections.clone());
15257 });
15258 self.select_syntax_node_history.disable_clearing = false;
15259 }
15260
15261 let start_row = last_new.start.to_display_point(&display_map).row().0;
15262 let end_row = last_new.end.to_display_point(&display_map).row().0;
15263 let selection_height = end_row - start_row + 1;
15264 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15265
15266 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15267 let scroll_behavior = if fits_on_the_screen {
15268 self.request_autoscroll(Autoscroll::fit(), cx);
15269 SelectSyntaxNodeScrollBehavior::FitSelection
15270 } else if is_selection_reversed {
15271 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15272 SelectSyntaxNodeScrollBehavior::CursorTop
15273 } else {
15274 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15275 SelectSyntaxNodeScrollBehavior::CursorBottom
15276 };
15277
15278 self.select_syntax_node_history.push((
15279 old_selections,
15280 scroll_behavior,
15281 is_selection_reversed,
15282 ));
15283 }
15284
15285 pub fn select_smaller_syntax_node(
15286 &mut self,
15287 _: &SelectSmallerSyntaxNode,
15288 window: &mut Window,
15289 cx: &mut Context<Self>,
15290 ) {
15291 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15292
15293 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15294 self.select_syntax_node_history.pop()
15295 {
15296 if let Some(selection) = selections.last_mut() {
15297 selection.reversed = is_selection_reversed;
15298 }
15299
15300 self.select_syntax_node_history.disable_clearing = true;
15301 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15302 s.select(selections.to_vec());
15303 });
15304 self.select_syntax_node_history.disable_clearing = false;
15305
15306 match scroll_behavior {
15307 SelectSyntaxNodeScrollBehavior::CursorTop => {
15308 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15309 }
15310 SelectSyntaxNodeScrollBehavior::FitSelection => {
15311 self.request_autoscroll(Autoscroll::fit(), cx);
15312 }
15313 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15314 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15315 }
15316 }
15317 }
15318 }
15319
15320 pub fn unwrap_syntax_node(
15321 &mut self,
15322 _: &UnwrapSyntaxNode,
15323 window: &mut Window,
15324 cx: &mut Context<Self>,
15325 ) {
15326 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15327
15328 let buffer = self.buffer.read(cx).snapshot(cx);
15329 let selections = self
15330 .selections
15331 .all::<usize>(cx)
15332 .into_iter()
15333 // subtracting the offset requires sorting
15334 .sorted_by_key(|i| i.start);
15335
15336 let full_edits = selections
15337 .into_iter()
15338 .filter_map(|selection| {
15339 let child = if selection.is_empty()
15340 && let Some((_, ancestor_range)) =
15341 buffer.syntax_ancestor(selection.start..selection.end)
15342 {
15343 ancestor_range
15344 } else {
15345 selection.range()
15346 };
15347
15348 let mut parent = child.clone();
15349 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15350 parent = ancestor_range;
15351 if parent.start < child.start || parent.end > child.end {
15352 break;
15353 }
15354 }
15355
15356 if parent == child {
15357 return None;
15358 }
15359 let text = buffer.text_for_range(child).collect::<String>();
15360 Some((selection.id, parent, text))
15361 })
15362 .collect::<Vec<_>>();
15363 if full_edits.is_empty() {
15364 return;
15365 }
15366
15367 self.transact(window, cx, |this, window, cx| {
15368 this.buffer.update(cx, |buffer, cx| {
15369 buffer.edit(
15370 full_edits
15371 .iter()
15372 .map(|(_, p, t)| (p.clone(), t.clone()))
15373 .collect::<Vec<_>>(),
15374 None,
15375 cx,
15376 );
15377 });
15378 this.change_selections(Default::default(), window, cx, |s| {
15379 let mut offset = 0;
15380 let mut selections = vec![];
15381 for (id, parent, text) in full_edits {
15382 let start = parent.start - offset;
15383 offset += parent.len() - text.len();
15384 selections.push(Selection {
15385 id,
15386 start,
15387 end: start + text.len(),
15388 reversed: false,
15389 goal: Default::default(),
15390 });
15391 }
15392 s.select(selections);
15393 });
15394 });
15395 }
15396
15397 pub fn select_next_syntax_node(
15398 &mut self,
15399 _: &SelectNextSyntaxNode,
15400 window: &mut Window,
15401 cx: &mut Context<Self>,
15402 ) {
15403 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15404 if old_selections.is_empty() {
15405 return;
15406 }
15407
15408 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15409
15410 let buffer = self.buffer.read(cx).snapshot(cx);
15411 let mut selected_sibling = false;
15412
15413 let new_selections = old_selections
15414 .iter()
15415 .map(|selection| {
15416 let old_range = selection.start..selection.end;
15417
15418 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15419 let new_range = node.byte_range();
15420 selected_sibling = true;
15421 Selection {
15422 id: selection.id,
15423 start: new_range.start,
15424 end: new_range.end,
15425 goal: SelectionGoal::None,
15426 reversed: selection.reversed,
15427 }
15428 } else {
15429 selection.clone()
15430 }
15431 })
15432 .collect::<Vec<_>>();
15433
15434 if selected_sibling {
15435 self.change_selections(
15436 SelectionEffects::scroll(Autoscroll::fit()),
15437 window,
15438 cx,
15439 |s| {
15440 s.select(new_selections);
15441 },
15442 );
15443 }
15444 }
15445
15446 pub fn select_prev_syntax_node(
15447 &mut self,
15448 _: &SelectPreviousSyntaxNode,
15449 window: &mut Window,
15450 cx: &mut Context<Self>,
15451 ) {
15452 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15453 if old_selections.is_empty() {
15454 return;
15455 }
15456
15457 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15458
15459 let buffer = self.buffer.read(cx).snapshot(cx);
15460 let mut selected_sibling = false;
15461
15462 let new_selections = old_selections
15463 .iter()
15464 .map(|selection| {
15465 let old_range = selection.start..selection.end;
15466
15467 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15468 let new_range = node.byte_range();
15469 selected_sibling = true;
15470 Selection {
15471 id: selection.id,
15472 start: new_range.start,
15473 end: new_range.end,
15474 goal: SelectionGoal::None,
15475 reversed: selection.reversed,
15476 }
15477 } else {
15478 selection.clone()
15479 }
15480 })
15481 .collect::<Vec<_>>();
15482
15483 if selected_sibling {
15484 self.change_selections(
15485 SelectionEffects::scroll(Autoscroll::fit()),
15486 window,
15487 cx,
15488 |s| {
15489 s.select(new_selections);
15490 },
15491 );
15492 }
15493 }
15494
15495 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15496 if !EditorSettings::get_global(cx).gutter.runnables {
15497 self.clear_tasks();
15498 return Task::ready(());
15499 }
15500 let project = self.project().map(Entity::downgrade);
15501 let task_sources = self.lsp_task_sources(cx);
15502 let multi_buffer = self.buffer.downgrade();
15503 cx.spawn_in(window, async move |editor, cx| {
15504 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15505 let Some(project) = project.and_then(|p| p.upgrade()) else {
15506 return;
15507 };
15508 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15509 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15510 }) else {
15511 return;
15512 };
15513
15514 let hide_runnables = project
15515 .update(cx, |project, _| project.is_via_collab())
15516 .unwrap_or(true);
15517 if hide_runnables {
15518 return;
15519 }
15520 let new_rows =
15521 cx.background_spawn({
15522 let snapshot = display_snapshot.clone();
15523 async move {
15524 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15525 }
15526 })
15527 .await;
15528 let Ok(lsp_tasks) =
15529 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15530 else {
15531 return;
15532 };
15533 let lsp_tasks = lsp_tasks.await;
15534
15535 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15536 lsp_tasks
15537 .into_iter()
15538 .flat_map(|(kind, tasks)| {
15539 tasks.into_iter().filter_map(move |(location, task)| {
15540 Some((kind.clone(), location?, task))
15541 })
15542 })
15543 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15544 let buffer = location.target.buffer;
15545 let buffer_snapshot = buffer.read(cx).snapshot();
15546 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15547 |(excerpt_id, snapshot, _)| {
15548 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15549 display_snapshot
15550 .buffer_snapshot()
15551 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15552 } else {
15553 None
15554 }
15555 },
15556 );
15557 if let Some(offset) = offset {
15558 let task_buffer_range =
15559 location.target.range.to_point(&buffer_snapshot);
15560 let context_buffer_range =
15561 task_buffer_range.to_offset(&buffer_snapshot);
15562 let context_range = BufferOffset(context_buffer_range.start)
15563 ..BufferOffset(context_buffer_range.end);
15564
15565 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15566 .or_insert_with(|| RunnableTasks {
15567 templates: Vec::new(),
15568 offset,
15569 column: task_buffer_range.start.column,
15570 extra_variables: HashMap::default(),
15571 context_range,
15572 })
15573 .templates
15574 .push((kind, task.original_task().clone()));
15575 }
15576
15577 acc
15578 })
15579 }) else {
15580 return;
15581 };
15582
15583 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15584 buffer.language_settings(cx).tasks.prefer_lsp
15585 }) else {
15586 return;
15587 };
15588
15589 let rows = Self::runnable_rows(
15590 project,
15591 display_snapshot,
15592 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15593 new_rows,
15594 cx.clone(),
15595 )
15596 .await;
15597 editor
15598 .update(cx, |editor, _| {
15599 editor.clear_tasks();
15600 for (key, mut value) in rows {
15601 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15602 value.templates.extend(lsp_tasks.templates);
15603 }
15604
15605 editor.insert_tasks(key, value);
15606 }
15607 for (key, value) in lsp_tasks_by_rows {
15608 editor.insert_tasks(key, value);
15609 }
15610 })
15611 .ok();
15612 })
15613 }
15614 fn fetch_runnable_ranges(
15615 snapshot: &DisplaySnapshot,
15616 range: Range<Anchor>,
15617 ) -> Vec<language::RunnableRange> {
15618 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15619 }
15620
15621 fn runnable_rows(
15622 project: Entity<Project>,
15623 snapshot: DisplaySnapshot,
15624 prefer_lsp: bool,
15625 runnable_ranges: Vec<RunnableRange>,
15626 cx: AsyncWindowContext,
15627 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15628 cx.spawn(async move |cx| {
15629 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15630 for mut runnable in runnable_ranges {
15631 let Some(tasks) = cx
15632 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15633 .ok()
15634 else {
15635 continue;
15636 };
15637 let mut tasks = tasks.await;
15638
15639 if prefer_lsp {
15640 tasks.retain(|(task_kind, _)| {
15641 !matches!(task_kind, TaskSourceKind::Language { .. })
15642 });
15643 }
15644 if tasks.is_empty() {
15645 continue;
15646 }
15647
15648 let point = runnable
15649 .run_range
15650 .start
15651 .to_point(&snapshot.buffer_snapshot());
15652 let Some(row) = snapshot
15653 .buffer_snapshot()
15654 .buffer_line_for_row(MultiBufferRow(point.row))
15655 .map(|(_, range)| range.start.row)
15656 else {
15657 continue;
15658 };
15659
15660 let context_range =
15661 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15662 runnable_rows.push((
15663 (runnable.buffer_id, row),
15664 RunnableTasks {
15665 templates: tasks,
15666 offset: snapshot
15667 .buffer_snapshot()
15668 .anchor_before(runnable.run_range.start),
15669 context_range,
15670 column: point.column,
15671 extra_variables: runnable.extra_captures,
15672 },
15673 ));
15674 }
15675 runnable_rows
15676 })
15677 }
15678
15679 fn templates_with_tags(
15680 project: &Entity<Project>,
15681 runnable: &mut Runnable,
15682 cx: &mut App,
15683 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15684 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15685 let (worktree_id, file) = project
15686 .buffer_for_id(runnable.buffer, cx)
15687 .and_then(|buffer| buffer.read(cx).file())
15688 .map(|file| (file.worktree_id(cx), file.clone()))
15689 .unzip();
15690
15691 (
15692 project.task_store().read(cx).task_inventory().cloned(),
15693 worktree_id,
15694 file,
15695 )
15696 });
15697
15698 let tags = mem::take(&mut runnable.tags);
15699 let language = runnable.language.clone();
15700 cx.spawn(async move |cx| {
15701 let mut templates_with_tags = Vec::new();
15702 if let Some(inventory) = inventory {
15703 for RunnableTag(tag) in tags {
15704 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15705 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15706 }) else {
15707 return templates_with_tags;
15708 };
15709 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15710 move |(_, template)| {
15711 template.tags.iter().any(|source_tag| source_tag == &tag)
15712 },
15713 ));
15714 }
15715 }
15716 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15717
15718 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15719 // Strongest source wins; if we have worktree tag binding, prefer that to
15720 // global and language bindings;
15721 // if we have a global binding, prefer that to language binding.
15722 let first_mismatch = templates_with_tags
15723 .iter()
15724 .position(|(tag_source, _)| tag_source != leading_tag_source);
15725 if let Some(index) = first_mismatch {
15726 templates_with_tags.truncate(index);
15727 }
15728 }
15729
15730 templates_with_tags
15731 })
15732 }
15733
15734 pub fn move_to_enclosing_bracket(
15735 &mut self,
15736 _: &MoveToEnclosingBracket,
15737 window: &mut Window,
15738 cx: &mut Context<Self>,
15739 ) {
15740 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15741 self.change_selections(Default::default(), window, cx, |s| {
15742 s.move_offsets_with(|snapshot, selection| {
15743 let Some(enclosing_bracket_ranges) =
15744 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15745 else {
15746 return;
15747 };
15748
15749 let mut best_length = usize::MAX;
15750 let mut best_inside = false;
15751 let mut best_in_bracket_range = false;
15752 let mut best_destination = None;
15753 for (open, close) in enclosing_bracket_ranges {
15754 let close = close.to_inclusive();
15755 let length = close.end() - open.start;
15756 let inside = selection.start >= open.end && selection.end <= *close.start();
15757 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15758 || close.contains(&selection.head());
15759
15760 // If best is next to a bracket and current isn't, skip
15761 if !in_bracket_range && best_in_bracket_range {
15762 continue;
15763 }
15764
15765 // Prefer smaller lengths unless best is inside and current isn't
15766 if length > best_length && (best_inside || !inside) {
15767 continue;
15768 }
15769
15770 best_length = length;
15771 best_inside = inside;
15772 best_in_bracket_range = in_bracket_range;
15773 best_destination = Some(
15774 if close.contains(&selection.start) && close.contains(&selection.end) {
15775 if inside { open.end } else { open.start }
15776 } else if inside {
15777 *close.start()
15778 } else {
15779 *close.end()
15780 },
15781 );
15782 }
15783
15784 if let Some(destination) = best_destination {
15785 selection.collapse_to(destination, SelectionGoal::None);
15786 }
15787 })
15788 });
15789 }
15790
15791 pub fn undo_selection(
15792 &mut self,
15793 _: &UndoSelection,
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.undo_stack.pop_back() {
15799 self.selection_history.mode = SelectionHistoryMode::Undoing;
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 redo_selection(
15818 &mut self,
15819 _: &RedoSelection,
15820 window: &mut Window,
15821 cx: &mut Context<Self>,
15822 ) {
15823 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15824 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15825 self.selection_history.mode = SelectionHistoryMode::Redoing;
15826 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15827 this.end_selection(window, cx);
15828 this.change_selections(
15829 SelectionEffects::scroll(Autoscroll::newest()),
15830 window,
15831 cx,
15832 |s| s.select_anchors(entry.selections.to_vec()),
15833 );
15834 });
15835 self.selection_history.mode = SelectionHistoryMode::Normal;
15836
15837 self.select_next_state = entry.select_next_state;
15838 self.select_prev_state = entry.select_prev_state;
15839 self.add_selections_state = entry.add_selections_state;
15840 }
15841 }
15842
15843 pub fn expand_excerpts(
15844 &mut self,
15845 action: &ExpandExcerpts,
15846 _: &mut Window,
15847 cx: &mut Context<Self>,
15848 ) {
15849 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15850 }
15851
15852 pub fn expand_excerpts_down(
15853 &mut self,
15854 action: &ExpandExcerptsDown,
15855 _: &mut Window,
15856 cx: &mut Context<Self>,
15857 ) {
15858 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15859 }
15860
15861 pub fn expand_excerpts_up(
15862 &mut self,
15863 action: &ExpandExcerptsUp,
15864 _: &mut Window,
15865 cx: &mut Context<Self>,
15866 ) {
15867 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15868 }
15869
15870 pub fn expand_excerpts_for_direction(
15871 &mut self,
15872 lines: u32,
15873 direction: ExpandExcerptDirection,
15874
15875 cx: &mut Context<Self>,
15876 ) {
15877 let selections = self.selections.disjoint_anchors_arc();
15878
15879 let lines = if lines == 0 {
15880 EditorSettings::get_global(cx).expand_excerpt_lines
15881 } else {
15882 lines
15883 };
15884
15885 self.buffer.update(cx, |buffer, cx| {
15886 let snapshot = buffer.snapshot(cx);
15887 let mut excerpt_ids = selections
15888 .iter()
15889 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15890 .collect::<Vec<_>>();
15891 excerpt_ids.sort();
15892 excerpt_ids.dedup();
15893 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15894 })
15895 }
15896
15897 pub fn expand_excerpt(
15898 &mut self,
15899 excerpt: ExcerptId,
15900 direction: ExpandExcerptDirection,
15901 window: &mut Window,
15902 cx: &mut Context<Self>,
15903 ) {
15904 let current_scroll_position = self.scroll_position(cx);
15905 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15906 let mut should_scroll_up = false;
15907
15908 if direction == ExpandExcerptDirection::Down {
15909 let multi_buffer = self.buffer.read(cx);
15910 let snapshot = multi_buffer.snapshot(cx);
15911 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15912 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15913 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15914 {
15915 let buffer_snapshot = buffer.read(cx).snapshot();
15916 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15917 let last_row = buffer_snapshot.max_point().row;
15918 let lines_below = last_row.saturating_sub(excerpt_end_row);
15919 should_scroll_up = lines_below >= lines_to_expand;
15920 }
15921 }
15922
15923 self.buffer.update(cx, |buffer, cx| {
15924 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15925 });
15926
15927 if should_scroll_up {
15928 let new_scroll_position =
15929 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15930 self.set_scroll_position(new_scroll_position, window, cx);
15931 }
15932 }
15933
15934 pub fn go_to_singleton_buffer_point(
15935 &mut self,
15936 point: Point,
15937 window: &mut Window,
15938 cx: &mut Context<Self>,
15939 ) {
15940 self.go_to_singleton_buffer_range(point..point, window, cx);
15941 }
15942
15943 pub fn go_to_singleton_buffer_range(
15944 &mut self,
15945 range: Range<Point>,
15946 window: &mut Window,
15947 cx: &mut Context<Self>,
15948 ) {
15949 let multibuffer = self.buffer().read(cx);
15950 let Some(buffer) = multibuffer.as_singleton() else {
15951 return;
15952 };
15953 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15954 return;
15955 };
15956 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15957 return;
15958 };
15959 self.change_selections(
15960 SelectionEffects::default().nav_history(true),
15961 window,
15962 cx,
15963 |s| s.select_anchor_ranges([start..end]),
15964 );
15965 }
15966
15967 pub fn go_to_diagnostic(
15968 &mut self,
15969 action: &GoToDiagnostic,
15970 window: &mut Window,
15971 cx: &mut Context<Self>,
15972 ) {
15973 if !self.diagnostics_enabled() {
15974 return;
15975 }
15976 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15977 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15978 }
15979
15980 pub fn go_to_prev_diagnostic(
15981 &mut self,
15982 action: &GoToPreviousDiagnostic,
15983 window: &mut Window,
15984 cx: &mut Context<Self>,
15985 ) {
15986 if !self.diagnostics_enabled() {
15987 return;
15988 }
15989 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15990 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15991 }
15992
15993 pub fn go_to_diagnostic_impl(
15994 &mut self,
15995 direction: Direction,
15996 severity: GoToDiagnosticSeverityFilter,
15997 window: &mut Window,
15998 cx: &mut Context<Self>,
15999 ) {
16000 let buffer = self.buffer.read(cx).snapshot(cx);
16001 let selection = self.selections.newest::<usize>(cx);
16002
16003 let mut active_group_id = None;
16004 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16005 && active_group.active_range.start.to_offset(&buffer) == selection.start
16006 {
16007 active_group_id = Some(active_group.group_id);
16008 }
16009
16010 fn filtered<'a>(
16011 snapshot: EditorSnapshot,
16012 severity: GoToDiagnosticSeverityFilter,
16013 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16014 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16015 diagnostics
16016 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16017 .filter(|entry| entry.range.start != entry.range.end)
16018 .filter(|entry| !entry.diagnostic.is_unnecessary)
16019 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16020 }
16021
16022 let snapshot = self.snapshot(window, cx);
16023 let before = filtered(
16024 snapshot.clone(),
16025 severity,
16026 buffer
16027 .diagnostics_in_range(0..selection.start)
16028 .filter(|entry| entry.range.start <= selection.start),
16029 );
16030 let after = filtered(
16031 snapshot,
16032 severity,
16033 buffer
16034 .diagnostics_in_range(selection.start..buffer.len())
16035 .filter(|entry| entry.range.start >= selection.start),
16036 );
16037
16038 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16039 if direction == Direction::Prev {
16040 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16041 {
16042 for diagnostic in prev_diagnostics.into_iter().rev() {
16043 if diagnostic.range.start != selection.start
16044 || active_group_id
16045 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16046 {
16047 found = Some(diagnostic);
16048 break 'outer;
16049 }
16050 }
16051 }
16052 } else {
16053 for diagnostic in after.chain(before) {
16054 if diagnostic.range.start != selection.start
16055 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16056 {
16057 found = Some(diagnostic);
16058 break;
16059 }
16060 }
16061 }
16062 let Some(next_diagnostic) = found else {
16063 return;
16064 };
16065
16066 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16067 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16068 return;
16069 };
16070 self.change_selections(Default::default(), window, cx, |s| {
16071 s.select_ranges(vec![
16072 next_diagnostic.range.start..next_diagnostic.range.start,
16073 ])
16074 });
16075 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16076 self.refresh_edit_prediction(false, true, window, cx);
16077 }
16078
16079 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16080 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16081 let snapshot = self.snapshot(window, cx);
16082 let selection = self.selections.newest::<Point>(cx);
16083 self.go_to_hunk_before_or_after_position(
16084 &snapshot,
16085 selection.head(),
16086 Direction::Next,
16087 window,
16088 cx,
16089 );
16090 }
16091
16092 pub fn go_to_hunk_before_or_after_position(
16093 &mut self,
16094 snapshot: &EditorSnapshot,
16095 position: Point,
16096 direction: Direction,
16097 window: &mut Window,
16098 cx: &mut Context<Editor>,
16099 ) {
16100 let row = if direction == Direction::Next {
16101 self.hunk_after_position(snapshot, position)
16102 .map(|hunk| hunk.row_range.start)
16103 } else {
16104 self.hunk_before_position(snapshot, position)
16105 };
16106
16107 if let Some(row) = row {
16108 let destination = Point::new(row.0, 0);
16109 let autoscroll = Autoscroll::center();
16110
16111 self.unfold_ranges(&[destination..destination], false, false, cx);
16112 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16113 s.select_ranges([destination..destination]);
16114 });
16115 }
16116 }
16117
16118 fn hunk_after_position(
16119 &mut self,
16120 snapshot: &EditorSnapshot,
16121 position: Point,
16122 ) -> Option<MultiBufferDiffHunk> {
16123 snapshot
16124 .buffer_snapshot()
16125 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16126 .find(|hunk| hunk.row_range.start.0 > position.row)
16127 .or_else(|| {
16128 snapshot
16129 .buffer_snapshot()
16130 .diff_hunks_in_range(Point::zero()..position)
16131 .find(|hunk| hunk.row_range.end.0 < position.row)
16132 })
16133 }
16134
16135 fn go_to_prev_hunk(
16136 &mut self,
16137 _: &GoToPreviousHunk,
16138 window: &mut Window,
16139 cx: &mut Context<Self>,
16140 ) {
16141 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16142 let snapshot = self.snapshot(window, cx);
16143 let selection = self.selections.newest::<Point>(cx);
16144 self.go_to_hunk_before_or_after_position(
16145 &snapshot,
16146 selection.head(),
16147 Direction::Prev,
16148 window,
16149 cx,
16150 );
16151 }
16152
16153 fn hunk_before_position(
16154 &mut self,
16155 snapshot: &EditorSnapshot,
16156 position: Point,
16157 ) -> Option<MultiBufferRow> {
16158 snapshot
16159 .buffer_snapshot()
16160 .diff_hunk_before(position)
16161 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16162 }
16163
16164 fn go_to_next_change(
16165 &mut self,
16166 _: &GoToNextChange,
16167 window: &mut Window,
16168 cx: &mut Context<Self>,
16169 ) {
16170 if let Some(selections) = self
16171 .change_list
16172 .next_change(1, Direction::Next)
16173 .map(|s| s.to_vec())
16174 {
16175 self.change_selections(Default::default(), window, cx, |s| {
16176 let map = s.display_map();
16177 s.select_display_ranges(selections.iter().map(|a| {
16178 let point = a.to_display_point(&map);
16179 point..point
16180 }))
16181 })
16182 }
16183 }
16184
16185 fn go_to_previous_change(
16186 &mut self,
16187 _: &GoToPreviousChange,
16188 window: &mut Window,
16189 cx: &mut Context<Self>,
16190 ) {
16191 if let Some(selections) = self
16192 .change_list
16193 .next_change(1, Direction::Prev)
16194 .map(|s| s.to_vec())
16195 {
16196 self.change_selections(Default::default(), window, cx, |s| {
16197 let map = s.display_map();
16198 s.select_display_ranges(selections.iter().map(|a| {
16199 let point = a.to_display_point(&map);
16200 point..point
16201 }))
16202 })
16203 }
16204 }
16205
16206 pub fn go_to_next_document_highlight(
16207 &mut self,
16208 _: &GoToNextDocumentHighlight,
16209 window: &mut Window,
16210 cx: &mut Context<Self>,
16211 ) {
16212 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16213 }
16214
16215 pub fn go_to_prev_document_highlight(
16216 &mut self,
16217 _: &GoToPreviousDocumentHighlight,
16218 window: &mut Window,
16219 cx: &mut Context<Self>,
16220 ) {
16221 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16222 }
16223
16224 pub fn go_to_document_highlight_before_or_after_position(
16225 &mut self,
16226 direction: Direction,
16227 window: &mut Window,
16228 cx: &mut Context<Editor>,
16229 ) {
16230 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16231 let snapshot = self.snapshot(window, cx);
16232 let buffer = &snapshot.buffer_snapshot();
16233 let position = self.selections.newest::<Point>(cx).head();
16234 let anchor_position = buffer.anchor_after(position);
16235
16236 // Get all document highlights (both read and write)
16237 let mut all_highlights = Vec::new();
16238
16239 if let Some((_, read_highlights)) = self
16240 .background_highlights
16241 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16242 {
16243 all_highlights.extend(read_highlights.iter());
16244 }
16245
16246 if let Some((_, write_highlights)) = self
16247 .background_highlights
16248 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16249 {
16250 all_highlights.extend(write_highlights.iter());
16251 }
16252
16253 if all_highlights.is_empty() {
16254 return;
16255 }
16256
16257 // Sort highlights by position
16258 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16259
16260 let target_highlight = match direction {
16261 Direction::Next => {
16262 // Find the first highlight after the current position
16263 all_highlights
16264 .iter()
16265 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16266 }
16267 Direction::Prev => {
16268 // Find the last highlight before the current position
16269 all_highlights
16270 .iter()
16271 .rev()
16272 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16273 }
16274 };
16275
16276 if let Some(highlight) = target_highlight {
16277 let destination = highlight.start.to_point(buffer);
16278 let autoscroll = Autoscroll::center();
16279
16280 self.unfold_ranges(&[destination..destination], false, false, cx);
16281 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16282 s.select_ranges([destination..destination]);
16283 });
16284 }
16285 }
16286
16287 fn go_to_line<T: 'static>(
16288 &mut self,
16289 position: Anchor,
16290 highlight_color: Option<Hsla>,
16291 window: &mut Window,
16292 cx: &mut Context<Self>,
16293 ) {
16294 let snapshot = self.snapshot(window, cx).display_snapshot;
16295 let position = position.to_point(&snapshot.buffer_snapshot());
16296 let start = snapshot
16297 .buffer_snapshot()
16298 .clip_point(Point::new(position.row, 0), Bias::Left);
16299 let end = start + Point::new(1, 0);
16300 let start = snapshot.buffer_snapshot().anchor_before(start);
16301 let end = snapshot.buffer_snapshot().anchor_before(end);
16302
16303 self.highlight_rows::<T>(
16304 start..end,
16305 highlight_color
16306 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16307 Default::default(),
16308 cx,
16309 );
16310
16311 if self.buffer.read(cx).is_singleton() {
16312 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16313 }
16314 }
16315
16316 pub fn go_to_definition(
16317 &mut self,
16318 _: &GoToDefinition,
16319 window: &mut Window,
16320 cx: &mut Context<Self>,
16321 ) -> Task<Result<Navigated>> {
16322 let definition =
16323 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16324 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16325 cx.spawn_in(window, async move |editor, cx| {
16326 if definition.await? == Navigated::Yes {
16327 return Ok(Navigated::Yes);
16328 }
16329 match fallback_strategy {
16330 GoToDefinitionFallback::None => Ok(Navigated::No),
16331 GoToDefinitionFallback::FindAllReferences => {
16332 match editor.update_in(cx, |editor, window, cx| {
16333 editor.find_all_references(&FindAllReferences, window, cx)
16334 })? {
16335 Some(references) => references.await,
16336 None => Ok(Navigated::No),
16337 }
16338 }
16339 }
16340 })
16341 }
16342
16343 pub fn go_to_declaration(
16344 &mut self,
16345 _: &GoToDeclaration,
16346 window: &mut Window,
16347 cx: &mut Context<Self>,
16348 ) -> Task<Result<Navigated>> {
16349 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16350 }
16351
16352 pub fn go_to_declaration_split(
16353 &mut self,
16354 _: &GoToDeclaration,
16355 window: &mut Window,
16356 cx: &mut Context<Self>,
16357 ) -> Task<Result<Navigated>> {
16358 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16359 }
16360
16361 pub fn go_to_implementation(
16362 &mut self,
16363 _: &GoToImplementation,
16364 window: &mut Window,
16365 cx: &mut Context<Self>,
16366 ) -> Task<Result<Navigated>> {
16367 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16368 }
16369
16370 pub fn go_to_implementation_split(
16371 &mut self,
16372 _: &GoToImplementationSplit,
16373 window: &mut Window,
16374 cx: &mut Context<Self>,
16375 ) -> Task<Result<Navigated>> {
16376 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16377 }
16378
16379 pub fn go_to_type_definition(
16380 &mut self,
16381 _: &GoToTypeDefinition,
16382 window: &mut Window,
16383 cx: &mut Context<Self>,
16384 ) -> Task<Result<Navigated>> {
16385 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16386 }
16387
16388 pub fn go_to_definition_split(
16389 &mut self,
16390 _: &GoToDefinitionSplit,
16391 window: &mut Window,
16392 cx: &mut Context<Self>,
16393 ) -> Task<Result<Navigated>> {
16394 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16395 }
16396
16397 pub fn go_to_type_definition_split(
16398 &mut self,
16399 _: &GoToTypeDefinitionSplit,
16400 window: &mut Window,
16401 cx: &mut Context<Self>,
16402 ) -> Task<Result<Navigated>> {
16403 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16404 }
16405
16406 fn go_to_definition_of_kind(
16407 &mut self,
16408 kind: GotoDefinitionKind,
16409 split: bool,
16410 window: &mut Window,
16411 cx: &mut Context<Self>,
16412 ) -> Task<Result<Navigated>> {
16413 let Some(provider) = self.semantics_provider.clone() else {
16414 return Task::ready(Ok(Navigated::No));
16415 };
16416 let head = self.selections.newest::<usize>(cx).head();
16417 let buffer = self.buffer.read(cx);
16418 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16419 return Task::ready(Ok(Navigated::No));
16420 };
16421 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16422 return Task::ready(Ok(Navigated::No));
16423 };
16424
16425 cx.spawn_in(window, async move |editor, cx| {
16426 let Some(definitions) = definitions.await? else {
16427 return Ok(Navigated::No);
16428 };
16429 let navigated = editor
16430 .update_in(cx, |editor, window, cx| {
16431 editor.navigate_to_hover_links(
16432 Some(kind),
16433 definitions
16434 .into_iter()
16435 .filter(|location| {
16436 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16437 })
16438 .map(HoverLink::Text)
16439 .collect::<Vec<_>>(),
16440 split,
16441 window,
16442 cx,
16443 )
16444 })?
16445 .await?;
16446 anyhow::Ok(navigated)
16447 })
16448 }
16449
16450 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16451 let selection = self.selections.newest_anchor();
16452 let head = selection.head();
16453 let tail = selection.tail();
16454
16455 let Some((buffer, start_position)) =
16456 self.buffer.read(cx).text_anchor_for_position(head, cx)
16457 else {
16458 return;
16459 };
16460
16461 let end_position = if head != tail {
16462 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16463 return;
16464 };
16465 Some(pos)
16466 } else {
16467 None
16468 };
16469
16470 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16471 let url = if let Some(end_pos) = end_position {
16472 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16473 } else {
16474 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16475 };
16476
16477 if let Some(url) = url {
16478 cx.update(|window, cx| {
16479 if parse_zed_link(&url, cx).is_some() {
16480 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16481 } else {
16482 cx.open_url(&url);
16483 }
16484 })?;
16485 }
16486
16487 anyhow::Ok(())
16488 });
16489
16490 url_finder.detach();
16491 }
16492
16493 pub fn open_selected_filename(
16494 &mut self,
16495 _: &OpenSelectedFilename,
16496 window: &mut Window,
16497 cx: &mut Context<Self>,
16498 ) {
16499 let Some(workspace) = self.workspace() else {
16500 return;
16501 };
16502
16503 let position = self.selections.newest_anchor().head();
16504
16505 let Some((buffer, buffer_position)) =
16506 self.buffer.read(cx).text_anchor_for_position(position, cx)
16507 else {
16508 return;
16509 };
16510
16511 let project = self.project.clone();
16512
16513 cx.spawn_in(window, async move |_, cx| {
16514 let result = find_file(&buffer, project, buffer_position, cx).await;
16515
16516 if let Some((_, path)) = result {
16517 workspace
16518 .update_in(cx, |workspace, window, cx| {
16519 workspace.open_resolved_path(path, window, cx)
16520 })?
16521 .await?;
16522 }
16523 anyhow::Ok(())
16524 })
16525 .detach();
16526 }
16527
16528 pub(crate) fn navigate_to_hover_links(
16529 &mut self,
16530 kind: Option<GotoDefinitionKind>,
16531 definitions: Vec<HoverLink>,
16532 split: bool,
16533 window: &mut Window,
16534 cx: &mut Context<Editor>,
16535 ) -> Task<Result<Navigated>> {
16536 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16537 let mut first_url_or_file = None;
16538 let definitions: Vec<_> = definitions
16539 .into_iter()
16540 .filter_map(|def| match def {
16541 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16542 HoverLink::InlayHint(lsp_location, server_id) => {
16543 let computation =
16544 self.compute_target_location(lsp_location, server_id, window, cx);
16545 Some(cx.background_spawn(computation))
16546 }
16547 HoverLink::Url(url) => {
16548 first_url_or_file = Some(Either::Left(url));
16549 None
16550 }
16551 HoverLink::File(path) => {
16552 first_url_or_file = Some(Either::Right(path));
16553 None
16554 }
16555 })
16556 .collect();
16557
16558 let workspace = self.workspace();
16559
16560 cx.spawn_in(window, async move |editor, cx| {
16561 let locations: Vec<Location> = future::join_all(definitions)
16562 .await
16563 .into_iter()
16564 .filter_map(|location| location.transpose())
16565 .collect::<Result<_>>()
16566 .context("location tasks")?;
16567 let mut locations = cx.update(|_, cx| {
16568 locations
16569 .into_iter()
16570 .map(|location| {
16571 let buffer = location.buffer.read(cx);
16572 (location.buffer, location.range.to_point(buffer))
16573 })
16574 .into_group_map()
16575 })?;
16576 let mut num_locations = 0;
16577 for ranges in locations.values_mut() {
16578 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16579 ranges.dedup();
16580 num_locations += ranges.len();
16581 }
16582
16583 if num_locations > 1 {
16584 let Some(workspace) = workspace else {
16585 return Ok(Navigated::No);
16586 };
16587
16588 let tab_kind = match kind {
16589 Some(GotoDefinitionKind::Implementation) => "Implementations",
16590 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16591 Some(GotoDefinitionKind::Declaration) => "Declarations",
16592 Some(GotoDefinitionKind::Type) => "Types",
16593 };
16594 let title = editor
16595 .update_in(cx, |_, _, cx| {
16596 let target = locations
16597 .iter()
16598 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16599 .map(|(buffer, location)| {
16600 buffer
16601 .read(cx)
16602 .text_for_range(location.clone())
16603 .collect::<String>()
16604 })
16605 .filter(|text| !text.contains('\n'))
16606 .unique()
16607 .take(3)
16608 .join(", ");
16609 if target.is_empty() {
16610 tab_kind.to_owned()
16611 } else {
16612 format!("{tab_kind} for {target}")
16613 }
16614 })
16615 .context("buffer title")?;
16616
16617 let opened = workspace
16618 .update_in(cx, |workspace, window, cx| {
16619 Self::open_locations_in_multibuffer(
16620 workspace,
16621 locations,
16622 title,
16623 split,
16624 MultibufferSelectionMode::First,
16625 window,
16626 cx,
16627 )
16628 })
16629 .is_ok();
16630
16631 anyhow::Ok(Navigated::from_bool(opened))
16632 } else if num_locations == 0 {
16633 // If there is one url or file, open it directly
16634 match first_url_or_file {
16635 Some(Either::Left(url)) => {
16636 cx.update(|_, cx| cx.open_url(&url))?;
16637 Ok(Navigated::Yes)
16638 }
16639 Some(Either::Right(path)) => {
16640 let Some(workspace) = workspace else {
16641 return Ok(Navigated::No);
16642 };
16643
16644 workspace
16645 .update_in(cx, |workspace, window, cx| {
16646 workspace.open_resolved_path(path, window, cx)
16647 })?
16648 .await?;
16649 Ok(Navigated::Yes)
16650 }
16651 None => Ok(Navigated::No),
16652 }
16653 } else {
16654 let Some(workspace) = workspace else {
16655 return Ok(Navigated::No);
16656 };
16657
16658 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16659 let target_range = target_ranges.first().unwrap().clone();
16660
16661 editor.update_in(cx, |editor, window, cx| {
16662 let range = target_range.to_point(target_buffer.read(cx));
16663 let range = editor.range_for_match(&range);
16664 let range = collapse_multiline_range(range);
16665
16666 if !split
16667 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16668 {
16669 editor.go_to_singleton_buffer_range(range, window, cx);
16670 } else {
16671 let pane = workspace.read(cx).active_pane().clone();
16672 window.defer(cx, move |window, cx| {
16673 let target_editor: Entity<Self> =
16674 workspace.update(cx, |workspace, cx| {
16675 let pane = if split {
16676 workspace.adjacent_pane(window, cx)
16677 } else {
16678 workspace.active_pane().clone()
16679 };
16680
16681 workspace.open_project_item(
16682 pane,
16683 target_buffer.clone(),
16684 true,
16685 true,
16686 window,
16687 cx,
16688 )
16689 });
16690 target_editor.update(cx, |target_editor, cx| {
16691 // When selecting a definition in a different buffer, disable the nav history
16692 // to avoid creating a history entry at the previous cursor location.
16693 pane.update(cx, |pane, _| pane.disable_history());
16694 target_editor.go_to_singleton_buffer_range(range, window, cx);
16695 pane.update(cx, |pane, _| pane.enable_history());
16696 });
16697 });
16698 }
16699 Navigated::Yes
16700 })
16701 }
16702 })
16703 }
16704
16705 fn compute_target_location(
16706 &self,
16707 lsp_location: lsp::Location,
16708 server_id: LanguageServerId,
16709 window: &mut Window,
16710 cx: &mut Context<Self>,
16711 ) -> Task<anyhow::Result<Option<Location>>> {
16712 let Some(project) = self.project.clone() else {
16713 return Task::ready(Ok(None));
16714 };
16715
16716 cx.spawn_in(window, async move |editor, cx| {
16717 let location_task = editor.update(cx, |_, cx| {
16718 project.update(cx, |project, cx| {
16719 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16720 })
16721 })?;
16722 let location = Some({
16723 let target_buffer_handle = location_task.await.context("open local buffer")?;
16724 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16725 let target_start = target_buffer
16726 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16727 let target_end = target_buffer
16728 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16729 target_buffer.anchor_after(target_start)
16730 ..target_buffer.anchor_before(target_end)
16731 })?;
16732 Location {
16733 buffer: target_buffer_handle,
16734 range,
16735 }
16736 });
16737 Ok(location)
16738 })
16739 }
16740
16741 pub fn find_all_references(
16742 &mut self,
16743 _: &FindAllReferences,
16744 window: &mut Window,
16745 cx: &mut Context<Self>,
16746 ) -> Option<Task<Result<Navigated>>> {
16747 let selection = self.selections.newest::<usize>(cx);
16748 let multi_buffer = self.buffer.read(cx);
16749 let head = selection.head();
16750
16751 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16752 let head_anchor = multi_buffer_snapshot.anchor_at(
16753 head,
16754 if head < selection.tail() {
16755 Bias::Right
16756 } else {
16757 Bias::Left
16758 },
16759 );
16760
16761 match self
16762 .find_all_references_task_sources
16763 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16764 {
16765 Ok(_) => {
16766 log::info!(
16767 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16768 );
16769 return None;
16770 }
16771 Err(i) => {
16772 self.find_all_references_task_sources.insert(i, head_anchor);
16773 }
16774 }
16775
16776 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16777 let workspace = self.workspace()?;
16778 let project = workspace.read(cx).project().clone();
16779 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16780 Some(cx.spawn_in(window, async move |editor, cx| {
16781 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16782 if let Ok(i) = editor
16783 .find_all_references_task_sources
16784 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16785 {
16786 editor.find_all_references_task_sources.remove(i);
16787 }
16788 });
16789
16790 let Some(locations) = references.await? else {
16791 return anyhow::Ok(Navigated::No);
16792 };
16793 let mut locations = cx.update(|_, cx| {
16794 locations
16795 .into_iter()
16796 .map(|location| {
16797 let buffer = location.buffer.read(cx);
16798 (location.buffer, location.range.to_point(buffer))
16799 })
16800 .into_group_map()
16801 })?;
16802 if locations.is_empty() {
16803 return anyhow::Ok(Navigated::No);
16804 }
16805 for ranges in locations.values_mut() {
16806 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16807 ranges.dedup();
16808 }
16809
16810 workspace.update_in(cx, |workspace, window, cx| {
16811 let target = locations
16812 .iter()
16813 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16814 .map(|(buffer, location)| {
16815 buffer
16816 .read(cx)
16817 .text_for_range(location.clone())
16818 .collect::<String>()
16819 })
16820 .filter(|text| !text.contains('\n'))
16821 .unique()
16822 .take(3)
16823 .join(", ");
16824 let title = if target.is_empty() {
16825 "References".to_owned()
16826 } else {
16827 format!("References to {target}")
16828 };
16829 Self::open_locations_in_multibuffer(
16830 workspace,
16831 locations,
16832 title,
16833 false,
16834 MultibufferSelectionMode::First,
16835 window,
16836 cx,
16837 );
16838 Navigated::Yes
16839 })
16840 }))
16841 }
16842
16843 /// Opens a multibuffer with the given project locations in it
16844 pub fn open_locations_in_multibuffer(
16845 workspace: &mut Workspace,
16846 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16847 title: String,
16848 split: bool,
16849 multibuffer_selection_mode: MultibufferSelectionMode,
16850 window: &mut Window,
16851 cx: &mut Context<Workspace>,
16852 ) {
16853 if locations.is_empty() {
16854 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16855 return;
16856 }
16857
16858 let capability = workspace.project().read(cx).capability();
16859 let mut ranges = <Vec<Range<Anchor>>>::new();
16860
16861 // a key to find existing multibuffer editors with the same set of locations
16862 // to prevent us from opening more and more multibuffer tabs for searches and the like
16863 let mut key = (title.clone(), vec![]);
16864 let excerpt_buffer = cx.new(|cx| {
16865 let key = &mut key.1;
16866 let mut multibuffer = MultiBuffer::new(capability);
16867 for (buffer, mut ranges_for_buffer) in locations {
16868 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16869 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16870 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16871 PathKey::for_buffer(&buffer, cx),
16872 buffer.clone(),
16873 ranges_for_buffer,
16874 multibuffer_context_lines(cx),
16875 cx,
16876 );
16877 ranges.extend(new_ranges)
16878 }
16879
16880 multibuffer.with_title(title)
16881 });
16882 let existing = workspace.active_pane().update(cx, |pane, cx| {
16883 pane.items()
16884 .filter_map(|item| item.downcast::<Editor>())
16885 .find(|editor| {
16886 editor
16887 .read(cx)
16888 .lookup_key
16889 .as_ref()
16890 .and_then(|it| {
16891 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16892 })
16893 .is_some_and(|it| *it == key)
16894 })
16895 });
16896 let editor = existing.unwrap_or_else(|| {
16897 cx.new(|cx| {
16898 let mut editor = Editor::for_multibuffer(
16899 excerpt_buffer,
16900 Some(workspace.project().clone()),
16901 window,
16902 cx,
16903 );
16904 editor.lookup_key = Some(Box::new(key));
16905 editor
16906 })
16907 });
16908 editor.update(cx, |editor, cx| {
16909 match multibuffer_selection_mode {
16910 MultibufferSelectionMode::First => {
16911 if let Some(first_range) = ranges.first() {
16912 editor.change_selections(
16913 SelectionEffects::no_scroll(),
16914 window,
16915 cx,
16916 |selections| {
16917 selections.clear_disjoint();
16918 selections
16919 .select_anchor_ranges(std::iter::once(first_range.clone()));
16920 },
16921 );
16922 }
16923 editor.highlight_background::<Self>(
16924 &ranges,
16925 |theme| theme.colors().editor_highlighted_line_background,
16926 cx,
16927 );
16928 }
16929 MultibufferSelectionMode::All => {
16930 editor.change_selections(
16931 SelectionEffects::no_scroll(),
16932 window,
16933 cx,
16934 |selections| {
16935 selections.clear_disjoint();
16936 selections.select_anchor_ranges(ranges);
16937 },
16938 );
16939 }
16940 }
16941 editor.register_buffers_with_language_servers(cx);
16942 });
16943
16944 let item = Box::new(editor);
16945 let item_id = item.item_id();
16946
16947 if split {
16948 let pane = workspace.adjacent_pane(window, cx);
16949 workspace.add_item(pane, item, None, true, true, window, cx);
16950 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16951 let (preview_item_id, preview_item_idx) =
16952 workspace.active_pane().read_with(cx, |pane, _| {
16953 (pane.preview_item_id(), pane.preview_item_idx())
16954 });
16955
16956 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16957
16958 if let Some(preview_item_id) = preview_item_id {
16959 workspace.active_pane().update(cx, |pane, cx| {
16960 pane.remove_item(preview_item_id, false, false, window, cx);
16961 });
16962 }
16963 } else {
16964 workspace.add_item_to_active_pane(item, None, true, window, cx);
16965 }
16966 workspace.active_pane().update(cx, |pane, cx| {
16967 pane.set_preview_item_id(Some(item_id), cx);
16968 });
16969 }
16970
16971 pub fn rename(
16972 &mut self,
16973 _: &Rename,
16974 window: &mut Window,
16975 cx: &mut Context<Self>,
16976 ) -> Option<Task<Result<()>>> {
16977 use language::ToOffset as _;
16978
16979 let provider = self.semantics_provider.clone()?;
16980 let selection = self.selections.newest_anchor().clone();
16981 let (cursor_buffer, cursor_buffer_position) = self
16982 .buffer
16983 .read(cx)
16984 .text_anchor_for_position(selection.head(), cx)?;
16985 let (tail_buffer, cursor_buffer_position_end) = self
16986 .buffer
16987 .read(cx)
16988 .text_anchor_for_position(selection.tail(), cx)?;
16989 if tail_buffer != cursor_buffer {
16990 return None;
16991 }
16992
16993 let snapshot = cursor_buffer.read(cx).snapshot();
16994 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16995 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16996 let prepare_rename = provider
16997 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16998 .unwrap_or_else(|| Task::ready(Ok(None)));
16999 drop(snapshot);
17000
17001 Some(cx.spawn_in(window, async move |this, cx| {
17002 let rename_range = if let Some(range) = prepare_rename.await? {
17003 Some(range)
17004 } else {
17005 this.update(cx, |this, cx| {
17006 let buffer = this.buffer.read(cx).snapshot(cx);
17007 let mut buffer_highlights = this
17008 .document_highlights_for_position(selection.head(), &buffer)
17009 .filter(|highlight| {
17010 highlight.start.excerpt_id == selection.head().excerpt_id
17011 && highlight.end.excerpt_id == selection.head().excerpt_id
17012 });
17013 buffer_highlights
17014 .next()
17015 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17016 })?
17017 };
17018 if let Some(rename_range) = rename_range {
17019 this.update_in(cx, |this, window, cx| {
17020 let snapshot = cursor_buffer.read(cx).snapshot();
17021 let rename_buffer_range = rename_range.to_offset(&snapshot);
17022 let cursor_offset_in_rename_range =
17023 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17024 let cursor_offset_in_rename_range_end =
17025 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17026
17027 this.take_rename(false, window, cx);
17028 let buffer = this.buffer.read(cx).read(cx);
17029 let cursor_offset = selection.head().to_offset(&buffer);
17030 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17031 let rename_end = rename_start + rename_buffer_range.len();
17032 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17033 let mut old_highlight_id = None;
17034 let old_name: Arc<str> = buffer
17035 .chunks(rename_start..rename_end, true)
17036 .map(|chunk| {
17037 if old_highlight_id.is_none() {
17038 old_highlight_id = chunk.syntax_highlight_id;
17039 }
17040 chunk.text
17041 })
17042 .collect::<String>()
17043 .into();
17044
17045 drop(buffer);
17046
17047 // Position the selection in the rename editor so that it matches the current selection.
17048 this.show_local_selections = false;
17049 let rename_editor = cx.new(|cx| {
17050 let mut editor = Editor::single_line(window, cx);
17051 editor.buffer.update(cx, |buffer, cx| {
17052 buffer.edit([(0..0, old_name.clone())], None, cx)
17053 });
17054 let rename_selection_range = match cursor_offset_in_rename_range
17055 .cmp(&cursor_offset_in_rename_range_end)
17056 {
17057 Ordering::Equal => {
17058 editor.select_all(&SelectAll, window, cx);
17059 return editor;
17060 }
17061 Ordering::Less => {
17062 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17063 }
17064 Ordering::Greater => {
17065 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17066 }
17067 };
17068 if rename_selection_range.end > old_name.len() {
17069 editor.select_all(&SelectAll, window, cx);
17070 } else {
17071 editor.change_selections(Default::default(), window, cx, |s| {
17072 s.select_ranges([rename_selection_range]);
17073 });
17074 }
17075 editor
17076 });
17077 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17078 if e == &EditorEvent::Focused {
17079 cx.emit(EditorEvent::FocusedIn)
17080 }
17081 })
17082 .detach();
17083
17084 let write_highlights =
17085 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17086 let read_highlights =
17087 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17088 let ranges = write_highlights
17089 .iter()
17090 .flat_map(|(_, ranges)| ranges.iter())
17091 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17092 .cloned()
17093 .collect();
17094
17095 this.highlight_text::<Rename>(
17096 ranges,
17097 HighlightStyle {
17098 fade_out: Some(0.6),
17099 ..Default::default()
17100 },
17101 cx,
17102 );
17103 let rename_focus_handle = rename_editor.focus_handle(cx);
17104 window.focus(&rename_focus_handle);
17105 let block_id = this.insert_blocks(
17106 [BlockProperties {
17107 style: BlockStyle::Flex,
17108 placement: BlockPlacement::Below(range.start),
17109 height: Some(1),
17110 render: Arc::new({
17111 let rename_editor = rename_editor.clone();
17112 move |cx: &mut BlockContext| {
17113 let mut text_style = cx.editor_style.text.clone();
17114 if let Some(highlight_style) = old_highlight_id
17115 .and_then(|h| h.style(&cx.editor_style.syntax))
17116 {
17117 text_style = text_style.highlight(highlight_style);
17118 }
17119 div()
17120 .block_mouse_except_scroll()
17121 .pl(cx.anchor_x)
17122 .child(EditorElement::new(
17123 &rename_editor,
17124 EditorStyle {
17125 background: cx.theme().system().transparent,
17126 local_player: cx.editor_style.local_player,
17127 text: text_style,
17128 scrollbar_width: cx.editor_style.scrollbar_width,
17129 syntax: cx.editor_style.syntax.clone(),
17130 status: cx.editor_style.status.clone(),
17131 inlay_hints_style: HighlightStyle {
17132 font_weight: Some(FontWeight::BOLD),
17133 ..make_inlay_hints_style(cx.app)
17134 },
17135 edit_prediction_styles: make_suggestion_styles(
17136 cx.app,
17137 ),
17138 ..EditorStyle::default()
17139 },
17140 ))
17141 .into_any_element()
17142 }
17143 }),
17144 priority: 0,
17145 }],
17146 Some(Autoscroll::fit()),
17147 cx,
17148 )[0];
17149 this.pending_rename = Some(RenameState {
17150 range,
17151 old_name,
17152 editor: rename_editor,
17153 block_id,
17154 });
17155 })?;
17156 }
17157
17158 Ok(())
17159 }))
17160 }
17161
17162 pub fn confirm_rename(
17163 &mut self,
17164 _: &ConfirmRename,
17165 window: &mut Window,
17166 cx: &mut Context<Self>,
17167 ) -> Option<Task<Result<()>>> {
17168 let rename = self.take_rename(false, window, cx)?;
17169 let workspace = self.workspace()?.downgrade();
17170 let (buffer, start) = self
17171 .buffer
17172 .read(cx)
17173 .text_anchor_for_position(rename.range.start, cx)?;
17174 let (end_buffer, _) = self
17175 .buffer
17176 .read(cx)
17177 .text_anchor_for_position(rename.range.end, cx)?;
17178 if buffer != end_buffer {
17179 return None;
17180 }
17181
17182 let old_name = rename.old_name;
17183 let new_name = rename.editor.read(cx).text(cx);
17184
17185 let rename = self.semantics_provider.as_ref()?.perform_rename(
17186 &buffer,
17187 start,
17188 new_name.clone(),
17189 cx,
17190 )?;
17191
17192 Some(cx.spawn_in(window, async move |editor, cx| {
17193 let project_transaction = rename.await?;
17194 Self::open_project_transaction(
17195 &editor,
17196 workspace,
17197 project_transaction,
17198 format!("Rename: {} → {}", old_name, new_name),
17199 cx,
17200 )
17201 .await?;
17202
17203 editor.update(cx, |editor, cx| {
17204 editor.refresh_document_highlights(cx);
17205 })?;
17206 Ok(())
17207 }))
17208 }
17209
17210 fn take_rename(
17211 &mut self,
17212 moving_cursor: bool,
17213 window: &mut Window,
17214 cx: &mut Context<Self>,
17215 ) -> Option<RenameState> {
17216 let rename = self.pending_rename.take()?;
17217 if rename.editor.focus_handle(cx).is_focused(window) {
17218 window.focus(&self.focus_handle);
17219 }
17220
17221 self.remove_blocks(
17222 [rename.block_id].into_iter().collect(),
17223 Some(Autoscroll::fit()),
17224 cx,
17225 );
17226 self.clear_highlights::<Rename>(cx);
17227 self.show_local_selections = true;
17228
17229 if moving_cursor {
17230 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17231 editor.selections.newest::<usize>(cx).head()
17232 });
17233
17234 // Update the selection to match the position of the selection inside
17235 // the rename editor.
17236 let snapshot = self.buffer.read(cx).read(cx);
17237 let rename_range = rename.range.to_offset(&snapshot);
17238 let cursor_in_editor = snapshot
17239 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17240 .min(rename_range.end);
17241 drop(snapshot);
17242
17243 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17244 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17245 });
17246 } else {
17247 self.refresh_document_highlights(cx);
17248 }
17249
17250 Some(rename)
17251 }
17252
17253 pub fn pending_rename(&self) -> Option<&RenameState> {
17254 self.pending_rename.as_ref()
17255 }
17256
17257 fn format(
17258 &mut self,
17259 _: &Format,
17260 window: &mut Window,
17261 cx: &mut Context<Self>,
17262 ) -> Option<Task<Result<()>>> {
17263 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17264
17265 let project = match &self.project {
17266 Some(project) => project.clone(),
17267 None => return None,
17268 };
17269
17270 Some(self.perform_format(
17271 project,
17272 FormatTrigger::Manual,
17273 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17274 window,
17275 cx,
17276 ))
17277 }
17278
17279 fn format_selections(
17280 &mut self,
17281 _: &FormatSelections,
17282 window: &mut Window,
17283 cx: &mut Context<Self>,
17284 ) -> Option<Task<Result<()>>> {
17285 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17286
17287 let project = match &self.project {
17288 Some(project) => project.clone(),
17289 None => return None,
17290 };
17291
17292 let ranges = self
17293 .selections
17294 .all_adjusted(cx)
17295 .into_iter()
17296 .map(|selection| selection.range())
17297 .collect_vec();
17298
17299 Some(self.perform_format(
17300 project,
17301 FormatTrigger::Manual,
17302 FormatTarget::Ranges(ranges),
17303 window,
17304 cx,
17305 ))
17306 }
17307
17308 fn perform_format(
17309 &mut self,
17310 project: Entity<Project>,
17311 trigger: FormatTrigger,
17312 target: FormatTarget,
17313 window: &mut Window,
17314 cx: &mut Context<Self>,
17315 ) -> Task<Result<()>> {
17316 let buffer = self.buffer.clone();
17317 let (buffers, target) = match target {
17318 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17319 FormatTarget::Ranges(selection_ranges) => {
17320 let multi_buffer = buffer.read(cx);
17321 let snapshot = multi_buffer.read(cx);
17322 let mut buffers = HashSet::default();
17323 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17324 BTreeMap::new();
17325 for selection_range in selection_ranges {
17326 for (buffer, buffer_range, _) in
17327 snapshot.range_to_buffer_ranges(selection_range)
17328 {
17329 let buffer_id = buffer.remote_id();
17330 let start = buffer.anchor_before(buffer_range.start);
17331 let end = buffer.anchor_after(buffer_range.end);
17332 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17333 buffer_id_to_ranges
17334 .entry(buffer_id)
17335 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17336 .or_insert_with(|| vec![start..end]);
17337 }
17338 }
17339 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17340 }
17341 };
17342
17343 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17344 let selections_prev = transaction_id_prev
17345 .and_then(|transaction_id_prev| {
17346 // default to selections as they were after the last edit, if we have them,
17347 // instead of how they are now.
17348 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17349 // will take you back to where you made the last edit, instead of staying where you scrolled
17350 self.selection_history
17351 .transaction(transaction_id_prev)
17352 .map(|t| t.0.clone())
17353 })
17354 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17355
17356 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17357 let format = project.update(cx, |project, cx| {
17358 project.format(buffers, target, true, trigger, cx)
17359 });
17360
17361 cx.spawn_in(window, async move |editor, cx| {
17362 let transaction = futures::select_biased! {
17363 transaction = format.log_err().fuse() => transaction,
17364 () = timeout => {
17365 log::warn!("timed out waiting for formatting");
17366 None
17367 }
17368 };
17369
17370 buffer
17371 .update(cx, |buffer, cx| {
17372 if let Some(transaction) = transaction
17373 && !buffer.is_singleton()
17374 {
17375 buffer.push_transaction(&transaction.0, cx);
17376 }
17377 cx.notify();
17378 })
17379 .ok();
17380
17381 if let Some(transaction_id_now) =
17382 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17383 {
17384 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17385 if has_new_transaction {
17386 _ = editor.update(cx, |editor, _| {
17387 editor
17388 .selection_history
17389 .insert_transaction(transaction_id_now, selections_prev);
17390 });
17391 }
17392 }
17393
17394 Ok(())
17395 })
17396 }
17397
17398 fn organize_imports(
17399 &mut self,
17400 _: &OrganizeImports,
17401 window: &mut Window,
17402 cx: &mut Context<Self>,
17403 ) -> Option<Task<Result<()>>> {
17404 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17405 let project = match &self.project {
17406 Some(project) => project.clone(),
17407 None => return None,
17408 };
17409 Some(self.perform_code_action_kind(
17410 project,
17411 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17412 window,
17413 cx,
17414 ))
17415 }
17416
17417 fn perform_code_action_kind(
17418 &mut self,
17419 project: Entity<Project>,
17420 kind: CodeActionKind,
17421 window: &mut Window,
17422 cx: &mut Context<Self>,
17423 ) -> Task<Result<()>> {
17424 let buffer = self.buffer.clone();
17425 let buffers = buffer.read(cx).all_buffers();
17426 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17427 let apply_action = project.update(cx, |project, cx| {
17428 project.apply_code_action_kind(buffers, kind, true, cx)
17429 });
17430 cx.spawn_in(window, async move |_, cx| {
17431 let transaction = futures::select_biased! {
17432 () = timeout => {
17433 log::warn!("timed out waiting for executing code action");
17434 None
17435 }
17436 transaction = apply_action.log_err().fuse() => transaction,
17437 };
17438 buffer
17439 .update(cx, |buffer, cx| {
17440 // check if we need this
17441 if let Some(transaction) = transaction
17442 && !buffer.is_singleton()
17443 {
17444 buffer.push_transaction(&transaction.0, cx);
17445 }
17446 cx.notify();
17447 })
17448 .ok();
17449 Ok(())
17450 })
17451 }
17452
17453 pub fn restart_language_server(
17454 &mut self,
17455 _: &RestartLanguageServer,
17456 _: &mut Window,
17457 cx: &mut Context<Self>,
17458 ) {
17459 if let Some(project) = self.project.clone() {
17460 self.buffer.update(cx, |multi_buffer, cx| {
17461 project.update(cx, |project, cx| {
17462 project.restart_language_servers_for_buffers(
17463 multi_buffer.all_buffers().into_iter().collect(),
17464 HashSet::default(),
17465 cx,
17466 );
17467 });
17468 })
17469 }
17470 }
17471
17472 pub fn stop_language_server(
17473 &mut self,
17474 _: &StopLanguageServer,
17475 _: &mut Window,
17476 cx: &mut Context<Self>,
17477 ) {
17478 if let Some(project) = self.project.clone() {
17479 self.buffer.update(cx, |multi_buffer, cx| {
17480 project.update(cx, |project, cx| {
17481 project.stop_language_servers_for_buffers(
17482 multi_buffer.all_buffers().into_iter().collect(),
17483 HashSet::default(),
17484 cx,
17485 );
17486 cx.emit(project::Event::RefreshInlayHints);
17487 });
17488 });
17489 }
17490 }
17491
17492 fn cancel_language_server_work(
17493 workspace: &mut Workspace,
17494 _: &actions::CancelLanguageServerWork,
17495 _: &mut Window,
17496 cx: &mut Context<Workspace>,
17497 ) {
17498 let project = workspace.project();
17499 let buffers = workspace
17500 .active_item(cx)
17501 .and_then(|item| item.act_as::<Editor>(cx))
17502 .map_or(HashSet::default(), |editor| {
17503 editor.read(cx).buffer.read(cx).all_buffers()
17504 });
17505 project.update(cx, |project, cx| {
17506 project.cancel_language_server_work_for_buffers(buffers, cx);
17507 });
17508 }
17509
17510 fn show_character_palette(
17511 &mut self,
17512 _: &ShowCharacterPalette,
17513 window: &mut Window,
17514 _: &mut Context<Self>,
17515 ) {
17516 window.show_character_palette();
17517 }
17518
17519 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17520 if !self.diagnostics_enabled() {
17521 return;
17522 }
17523
17524 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17525 let buffer = self.buffer.read(cx).snapshot(cx);
17526 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17527 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17528 let is_valid = buffer
17529 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17530 .any(|entry| {
17531 entry.diagnostic.is_primary
17532 && !entry.range.is_empty()
17533 && entry.range.start == primary_range_start
17534 && entry.diagnostic.message == active_diagnostics.active_message
17535 });
17536
17537 if !is_valid {
17538 self.dismiss_diagnostics(cx);
17539 }
17540 }
17541 }
17542
17543 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17544 match &self.active_diagnostics {
17545 ActiveDiagnostic::Group(group) => Some(group),
17546 _ => None,
17547 }
17548 }
17549
17550 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17551 if !self.diagnostics_enabled() {
17552 return;
17553 }
17554 self.dismiss_diagnostics(cx);
17555 self.active_diagnostics = ActiveDiagnostic::All;
17556 }
17557
17558 fn activate_diagnostics(
17559 &mut self,
17560 buffer_id: BufferId,
17561 diagnostic: DiagnosticEntryRef<'_, usize>,
17562 window: &mut Window,
17563 cx: &mut Context<Self>,
17564 ) {
17565 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17566 return;
17567 }
17568 self.dismiss_diagnostics(cx);
17569 let snapshot = self.snapshot(window, cx);
17570 let buffer = self.buffer.read(cx).snapshot(cx);
17571 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17572 return;
17573 };
17574
17575 let diagnostic_group = buffer
17576 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17577 .collect::<Vec<_>>();
17578
17579 let blocks =
17580 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17581
17582 let blocks = self.display_map.update(cx, |display_map, cx| {
17583 display_map.insert_blocks(blocks, cx).into_iter().collect()
17584 });
17585 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17586 active_range: buffer.anchor_before(diagnostic.range.start)
17587 ..buffer.anchor_after(diagnostic.range.end),
17588 active_message: diagnostic.diagnostic.message.clone(),
17589 group_id: diagnostic.diagnostic.group_id,
17590 blocks,
17591 });
17592 cx.notify();
17593 }
17594
17595 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17596 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17597 return;
17598 };
17599
17600 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17601 if let ActiveDiagnostic::Group(group) = prev {
17602 self.display_map.update(cx, |display_map, cx| {
17603 display_map.remove_blocks(group.blocks, cx);
17604 });
17605 cx.notify();
17606 }
17607 }
17608
17609 /// Disable inline diagnostics rendering for this editor.
17610 pub fn disable_inline_diagnostics(&mut self) {
17611 self.inline_diagnostics_enabled = false;
17612 self.inline_diagnostics_update = Task::ready(());
17613 self.inline_diagnostics.clear();
17614 }
17615
17616 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17617 self.diagnostics_enabled = false;
17618 self.dismiss_diagnostics(cx);
17619 self.inline_diagnostics_update = Task::ready(());
17620 self.inline_diagnostics.clear();
17621 }
17622
17623 pub fn disable_word_completions(&mut self) {
17624 self.word_completions_enabled = false;
17625 }
17626
17627 pub fn diagnostics_enabled(&self) -> bool {
17628 self.diagnostics_enabled && self.mode.is_full()
17629 }
17630
17631 pub fn inline_diagnostics_enabled(&self) -> bool {
17632 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17633 }
17634
17635 pub fn show_inline_diagnostics(&self) -> bool {
17636 self.show_inline_diagnostics
17637 }
17638
17639 pub fn toggle_inline_diagnostics(
17640 &mut self,
17641 _: &ToggleInlineDiagnostics,
17642 window: &mut Window,
17643 cx: &mut Context<Editor>,
17644 ) {
17645 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17646 self.refresh_inline_diagnostics(false, window, cx);
17647 }
17648
17649 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17650 self.diagnostics_max_severity = severity;
17651 self.display_map.update(cx, |display_map, _| {
17652 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17653 });
17654 }
17655
17656 pub fn toggle_diagnostics(
17657 &mut self,
17658 _: &ToggleDiagnostics,
17659 window: &mut Window,
17660 cx: &mut Context<Editor>,
17661 ) {
17662 if !self.diagnostics_enabled() {
17663 return;
17664 }
17665
17666 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17667 EditorSettings::get_global(cx)
17668 .diagnostics_max_severity
17669 .filter(|severity| severity != &DiagnosticSeverity::Off)
17670 .unwrap_or(DiagnosticSeverity::Hint)
17671 } else {
17672 DiagnosticSeverity::Off
17673 };
17674 self.set_max_diagnostics_severity(new_severity, cx);
17675 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17676 self.active_diagnostics = ActiveDiagnostic::None;
17677 self.inline_diagnostics_update = Task::ready(());
17678 self.inline_diagnostics.clear();
17679 } else {
17680 self.refresh_inline_diagnostics(false, window, cx);
17681 }
17682
17683 cx.notify();
17684 }
17685
17686 pub fn toggle_minimap(
17687 &mut self,
17688 _: &ToggleMinimap,
17689 window: &mut Window,
17690 cx: &mut Context<Editor>,
17691 ) {
17692 if self.supports_minimap(cx) {
17693 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17694 }
17695 }
17696
17697 fn refresh_inline_diagnostics(
17698 &mut self,
17699 debounce: bool,
17700 window: &mut Window,
17701 cx: &mut Context<Self>,
17702 ) {
17703 let max_severity = ProjectSettings::get_global(cx)
17704 .diagnostics
17705 .inline
17706 .max_severity
17707 .unwrap_or(self.diagnostics_max_severity);
17708
17709 if !self.inline_diagnostics_enabled()
17710 || !self.show_inline_diagnostics
17711 || max_severity == DiagnosticSeverity::Off
17712 {
17713 self.inline_diagnostics_update = Task::ready(());
17714 self.inline_diagnostics.clear();
17715 return;
17716 }
17717
17718 let debounce_ms = ProjectSettings::get_global(cx)
17719 .diagnostics
17720 .inline
17721 .update_debounce_ms;
17722 let debounce = if debounce && debounce_ms > 0 {
17723 Some(Duration::from_millis(debounce_ms))
17724 } else {
17725 None
17726 };
17727 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17728 if let Some(debounce) = debounce {
17729 cx.background_executor().timer(debounce).await;
17730 }
17731 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17732 editor
17733 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17734 .ok()
17735 }) else {
17736 return;
17737 };
17738
17739 let new_inline_diagnostics = cx
17740 .background_spawn(async move {
17741 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17742 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17743 let message = diagnostic_entry
17744 .diagnostic
17745 .message
17746 .split_once('\n')
17747 .map(|(line, _)| line)
17748 .map(SharedString::new)
17749 .unwrap_or_else(|| {
17750 SharedString::new(&*diagnostic_entry.diagnostic.message)
17751 });
17752 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17753 let (Ok(i) | Err(i)) = inline_diagnostics
17754 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17755 inline_diagnostics.insert(
17756 i,
17757 (
17758 start_anchor,
17759 InlineDiagnostic {
17760 message,
17761 group_id: diagnostic_entry.diagnostic.group_id,
17762 start: diagnostic_entry.range.start.to_point(&snapshot),
17763 is_primary: diagnostic_entry.diagnostic.is_primary,
17764 severity: diagnostic_entry.diagnostic.severity,
17765 },
17766 ),
17767 );
17768 }
17769 inline_diagnostics
17770 })
17771 .await;
17772
17773 editor
17774 .update(cx, |editor, cx| {
17775 editor.inline_diagnostics = new_inline_diagnostics;
17776 cx.notify();
17777 })
17778 .ok();
17779 });
17780 }
17781
17782 fn pull_diagnostics(
17783 &mut self,
17784 buffer_id: Option<BufferId>,
17785 window: &Window,
17786 cx: &mut Context<Self>,
17787 ) -> Option<()> {
17788 if !self.mode().is_full() {
17789 return None;
17790 }
17791 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17792 .diagnostics
17793 .lsp_pull_diagnostics;
17794 if !pull_diagnostics_settings.enabled {
17795 return None;
17796 }
17797 let project = self.project()?.downgrade();
17798 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17799 let mut buffers = self.buffer.read(cx).all_buffers();
17800 if let Some(buffer_id) = buffer_id {
17801 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17802 }
17803
17804 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17805 cx.background_executor().timer(debounce).await;
17806
17807 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17808 buffers
17809 .into_iter()
17810 .filter_map(|buffer| {
17811 project
17812 .update(cx, |project, cx| {
17813 project.lsp_store().update(cx, |lsp_store, cx| {
17814 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17815 })
17816 })
17817 .ok()
17818 })
17819 .collect::<FuturesUnordered<_>>()
17820 }) else {
17821 return;
17822 };
17823
17824 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17825 match pull_task {
17826 Ok(()) => {
17827 if editor
17828 .update_in(cx, |editor, window, cx| {
17829 editor.update_diagnostics_state(window, cx);
17830 })
17831 .is_err()
17832 {
17833 return;
17834 }
17835 }
17836 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17837 }
17838 }
17839 });
17840
17841 Some(())
17842 }
17843
17844 pub fn set_selections_from_remote(
17845 &mut self,
17846 selections: Vec<Selection<Anchor>>,
17847 pending_selection: Option<Selection<Anchor>>,
17848 window: &mut Window,
17849 cx: &mut Context<Self>,
17850 ) {
17851 let old_cursor_position = self.selections.newest_anchor().head();
17852 self.selections.change_with(cx, |s| {
17853 s.select_anchors(selections);
17854 if let Some(pending_selection) = pending_selection {
17855 s.set_pending(pending_selection, SelectMode::Character);
17856 } else {
17857 s.clear_pending();
17858 }
17859 });
17860 self.selections_did_change(
17861 false,
17862 &old_cursor_position,
17863 SelectionEffects::default(),
17864 window,
17865 cx,
17866 );
17867 }
17868
17869 pub fn transact(
17870 &mut self,
17871 window: &mut Window,
17872 cx: &mut Context<Self>,
17873 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17874 ) -> Option<TransactionId> {
17875 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17876 this.start_transaction_at(Instant::now(), window, cx);
17877 update(this, window, cx);
17878 this.end_transaction_at(Instant::now(), cx)
17879 })
17880 }
17881
17882 pub fn start_transaction_at(
17883 &mut self,
17884 now: Instant,
17885 window: &mut Window,
17886 cx: &mut Context<Self>,
17887 ) -> Option<TransactionId> {
17888 self.end_selection(window, cx);
17889 if let Some(tx_id) = self
17890 .buffer
17891 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17892 {
17893 self.selection_history
17894 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17895 cx.emit(EditorEvent::TransactionBegun {
17896 transaction_id: tx_id,
17897 });
17898 Some(tx_id)
17899 } else {
17900 None
17901 }
17902 }
17903
17904 pub fn end_transaction_at(
17905 &mut self,
17906 now: Instant,
17907 cx: &mut Context<Self>,
17908 ) -> Option<TransactionId> {
17909 if let Some(transaction_id) = self
17910 .buffer
17911 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17912 {
17913 if let Some((_, end_selections)) =
17914 self.selection_history.transaction_mut(transaction_id)
17915 {
17916 *end_selections = Some(self.selections.disjoint_anchors_arc());
17917 } else {
17918 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17919 }
17920
17921 cx.emit(EditorEvent::Edited { transaction_id });
17922 Some(transaction_id)
17923 } else {
17924 None
17925 }
17926 }
17927
17928 pub fn modify_transaction_selection_history(
17929 &mut self,
17930 transaction_id: TransactionId,
17931 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17932 ) -> bool {
17933 self.selection_history
17934 .transaction_mut(transaction_id)
17935 .map(modify)
17936 .is_some()
17937 }
17938
17939 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17940 if self.selection_mark_mode {
17941 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17942 s.move_with(|_, sel| {
17943 sel.collapse_to(sel.head(), SelectionGoal::None);
17944 });
17945 })
17946 }
17947 self.selection_mark_mode = true;
17948 cx.notify();
17949 }
17950
17951 pub fn swap_selection_ends(
17952 &mut self,
17953 _: &actions::SwapSelectionEnds,
17954 window: &mut Window,
17955 cx: &mut Context<Self>,
17956 ) {
17957 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17958 s.move_with(|_, sel| {
17959 if sel.start != sel.end {
17960 sel.reversed = !sel.reversed
17961 }
17962 });
17963 });
17964 self.request_autoscroll(Autoscroll::newest(), cx);
17965 cx.notify();
17966 }
17967
17968 pub fn toggle_focus(
17969 workspace: &mut Workspace,
17970 _: &actions::ToggleFocus,
17971 window: &mut Window,
17972 cx: &mut Context<Workspace>,
17973 ) {
17974 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17975 return;
17976 };
17977 workspace.activate_item(&item, true, true, window, cx);
17978 }
17979
17980 pub fn toggle_fold(
17981 &mut self,
17982 _: &actions::ToggleFold,
17983 window: &mut Window,
17984 cx: &mut Context<Self>,
17985 ) {
17986 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
17987 let selection = self.selections.newest::<Point>(cx);
17988
17989 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17990 let range = if selection.is_empty() {
17991 let point = selection.head().to_display_point(&display_map);
17992 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17993 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17994 .to_point(&display_map);
17995 start..end
17996 } else {
17997 selection.range()
17998 };
17999 if display_map.folds_in_range(range).next().is_some() {
18000 self.unfold_lines(&Default::default(), window, cx)
18001 } else {
18002 self.fold(&Default::default(), window, cx)
18003 }
18004 } else {
18005 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18006 let buffer_ids: HashSet<_> = self
18007 .selections
18008 .disjoint_anchor_ranges()
18009 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18010 .collect();
18011
18012 let should_unfold = buffer_ids
18013 .iter()
18014 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18015
18016 for buffer_id in buffer_ids {
18017 if should_unfold {
18018 self.unfold_buffer(buffer_id, cx);
18019 } else {
18020 self.fold_buffer(buffer_id, cx);
18021 }
18022 }
18023 }
18024 }
18025
18026 pub fn toggle_fold_recursive(
18027 &mut self,
18028 _: &actions::ToggleFoldRecursive,
18029 window: &mut Window,
18030 cx: &mut Context<Self>,
18031 ) {
18032 let selection = self.selections.newest::<Point>(cx);
18033
18034 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18035 let range = if selection.is_empty() {
18036 let point = selection.head().to_display_point(&display_map);
18037 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18038 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18039 .to_point(&display_map);
18040 start..end
18041 } else {
18042 selection.range()
18043 };
18044 if display_map.folds_in_range(range).next().is_some() {
18045 self.unfold_recursive(&Default::default(), window, cx)
18046 } else {
18047 self.fold_recursive(&Default::default(), window, cx)
18048 }
18049 }
18050
18051 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18052 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18053 let mut to_fold = Vec::new();
18054 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18055 let selections = self.selections.all_adjusted(cx);
18056
18057 for selection in selections {
18058 let range = selection.range().sorted();
18059 let buffer_start_row = range.start.row;
18060
18061 if range.start.row != range.end.row {
18062 let mut found = false;
18063 let mut row = range.start.row;
18064 while row <= range.end.row {
18065 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18066 {
18067 found = true;
18068 row = crease.range().end.row + 1;
18069 to_fold.push(crease);
18070 } else {
18071 row += 1
18072 }
18073 }
18074 if found {
18075 continue;
18076 }
18077 }
18078
18079 for row in (0..=range.start.row).rev() {
18080 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18081 && crease.range().end.row >= buffer_start_row
18082 {
18083 to_fold.push(crease);
18084 if row <= range.start.row {
18085 break;
18086 }
18087 }
18088 }
18089 }
18090
18091 self.fold_creases(to_fold, true, window, cx);
18092 } else {
18093 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18094 let buffer_ids = self
18095 .selections
18096 .disjoint_anchor_ranges()
18097 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18098 .collect::<HashSet<_>>();
18099 for buffer_id in buffer_ids {
18100 self.fold_buffer(buffer_id, cx);
18101 }
18102 }
18103 }
18104
18105 pub fn toggle_fold_all(
18106 &mut self,
18107 _: &actions::ToggleFoldAll,
18108 window: &mut Window,
18109 cx: &mut Context<Self>,
18110 ) {
18111 if self.buffer.read(cx).is_singleton() {
18112 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18113 let has_folds = display_map
18114 .folds_in_range(0..display_map.buffer_snapshot().len())
18115 .next()
18116 .is_some();
18117
18118 if has_folds {
18119 self.unfold_all(&actions::UnfoldAll, window, cx);
18120 } else {
18121 self.fold_all(&actions::FoldAll, window, cx);
18122 }
18123 } else {
18124 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18125 let should_unfold = buffer_ids
18126 .iter()
18127 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18128
18129 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18130 editor
18131 .update_in(cx, |editor, _, cx| {
18132 for buffer_id in buffer_ids {
18133 if should_unfold {
18134 editor.unfold_buffer(buffer_id, cx);
18135 } else {
18136 editor.fold_buffer(buffer_id, cx);
18137 }
18138 }
18139 })
18140 .ok();
18141 });
18142 }
18143 }
18144
18145 fn fold_at_level(
18146 &mut self,
18147 fold_at: &FoldAtLevel,
18148 window: &mut Window,
18149 cx: &mut Context<Self>,
18150 ) {
18151 if !self.buffer.read(cx).is_singleton() {
18152 return;
18153 }
18154
18155 let fold_at_level = fold_at.0;
18156 let snapshot = self.buffer.read(cx).snapshot(cx);
18157 let mut to_fold = Vec::new();
18158 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18159
18160 let row_ranges_to_keep: Vec<Range<u32>> = self
18161 .selections
18162 .all::<Point>(cx)
18163 .into_iter()
18164 .map(|sel| sel.start.row..sel.end.row)
18165 .collect();
18166
18167 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18168 while start_row < end_row {
18169 match self
18170 .snapshot(window, cx)
18171 .crease_for_buffer_row(MultiBufferRow(start_row))
18172 {
18173 Some(crease) => {
18174 let nested_start_row = crease.range().start.row + 1;
18175 let nested_end_row = crease.range().end.row;
18176
18177 if current_level < fold_at_level {
18178 stack.push((nested_start_row, nested_end_row, current_level + 1));
18179 } else if current_level == fold_at_level {
18180 // Fold iff there is no selection completely contained within the fold region
18181 if !row_ranges_to_keep.iter().any(|selection| {
18182 selection.end >= nested_start_row
18183 && selection.start <= nested_end_row
18184 }) {
18185 to_fold.push(crease);
18186 }
18187 }
18188
18189 start_row = nested_end_row + 1;
18190 }
18191 None => start_row += 1,
18192 }
18193 }
18194 }
18195
18196 self.fold_creases(to_fold, true, window, cx);
18197 }
18198
18199 pub fn fold_at_level_1(
18200 &mut self,
18201 _: &actions::FoldAtLevel1,
18202 window: &mut Window,
18203 cx: &mut Context<Self>,
18204 ) {
18205 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18206 }
18207
18208 pub fn fold_at_level_2(
18209 &mut self,
18210 _: &actions::FoldAtLevel2,
18211 window: &mut Window,
18212 cx: &mut Context<Self>,
18213 ) {
18214 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18215 }
18216
18217 pub fn fold_at_level_3(
18218 &mut self,
18219 _: &actions::FoldAtLevel3,
18220 window: &mut Window,
18221 cx: &mut Context<Self>,
18222 ) {
18223 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18224 }
18225
18226 pub fn fold_at_level_4(
18227 &mut self,
18228 _: &actions::FoldAtLevel4,
18229 window: &mut Window,
18230 cx: &mut Context<Self>,
18231 ) {
18232 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18233 }
18234
18235 pub fn fold_at_level_5(
18236 &mut self,
18237 _: &actions::FoldAtLevel5,
18238 window: &mut Window,
18239 cx: &mut Context<Self>,
18240 ) {
18241 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18242 }
18243
18244 pub fn fold_at_level_6(
18245 &mut self,
18246 _: &actions::FoldAtLevel6,
18247 window: &mut Window,
18248 cx: &mut Context<Self>,
18249 ) {
18250 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18251 }
18252
18253 pub fn fold_at_level_7(
18254 &mut self,
18255 _: &actions::FoldAtLevel7,
18256 window: &mut Window,
18257 cx: &mut Context<Self>,
18258 ) {
18259 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18260 }
18261
18262 pub fn fold_at_level_8(
18263 &mut self,
18264 _: &actions::FoldAtLevel8,
18265 window: &mut Window,
18266 cx: &mut Context<Self>,
18267 ) {
18268 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18269 }
18270
18271 pub fn fold_at_level_9(
18272 &mut self,
18273 _: &actions::FoldAtLevel9,
18274 window: &mut Window,
18275 cx: &mut Context<Self>,
18276 ) {
18277 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18278 }
18279
18280 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18281 if self.buffer.read(cx).is_singleton() {
18282 let mut fold_ranges = Vec::new();
18283 let snapshot = self.buffer.read(cx).snapshot(cx);
18284
18285 for row in 0..snapshot.max_row().0 {
18286 if let Some(foldable_range) = self
18287 .snapshot(window, cx)
18288 .crease_for_buffer_row(MultiBufferRow(row))
18289 {
18290 fold_ranges.push(foldable_range);
18291 }
18292 }
18293
18294 self.fold_creases(fold_ranges, true, window, cx);
18295 } else {
18296 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18297 editor
18298 .update_in(cx, |editor, _, cx| {
18299 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18300 editor.fold_buffer(buffer_id, cx);
18301 }
18302 })
18303 .ok();
18304 });
18305 }
18306 }
18307
18308 pub fn fold_function_bodies(
18309 &mut self,
18310 _: &actions::FoldFunctionBodies,
18311 window: &mut Window,
18312 cx: &mut Context<Self>,
18313 ) {
18314 let snapshot = self.buffer.read(cx).snapshot(cx);
18315
18316 let ranges = snapshot
18317 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18318 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18319 .collect::<Vec<_>>();
18320
18321 let creases = ranges
18322 .into_iter()
18323 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18324 .collect();
18325
18326 self.fold_creases(creases, true, window, cx);
18327 }
18328
18329 pub fn fold_recursive(
18330 &mut self,
18331 _: &actions::FoldRecursive,
18332 window: &mut Window,
18333 cx: &mut Context<Self>,
18334 ) {
18335 let mut to_fold = Vec::new();
18336 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18337 let selections = self.selections.all_adjusted(cx);
18338
18339 for selection in selections {
18340 let range = selection.range().sorted();
18341 let buffer_start_row = range.start.row;
18342
18343 if range.start.row != range.end.row {
18344 let mut found = false;
18345 for row in range.start.row..=range.end.row {
18346 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18347 found = true;
18348 to_fold.push(crease);
18349 }
18350 }
18351 if found {
18352 continue;
18353 }
18354 }
18355
18356 for row in (0..=range.start.row).rev() {
18357 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18358 if crease.range().end.row >= buffer_start_row {
18359 to_fold.push(crease);
18360 } else {
18361 break;
18362 }
18363 }
18364 }
18365 }
18366
18367 self.fold_creases(to_fold, true, window, cx);
18368 }
18369
18370 pub fn fold_at(
18371 &mut self,
18372 buffer_row: MultiBufferRow,
18373 window: &mut Window,
18374 cx: &mut Context<Self>,
18375 ) {
18376 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18377
18378 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18379 let autoscroll = self
18380 .selections
18381 .all::<Point>(cx)
18382 .iter()
18383 .any(|selection| crease.range().overlaps(&selection.range()));
18384
18385 self.fold_creases(vec![crease], autoscroll, window, cx);
18386 }
18387 }
18388
18389 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18390 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18391 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18392 let buffer = display_map.buffer_snapshot();
18393 let selections = self.selections.all::<Point>(cx);
18394 let ranges = selections
18395 .iter()
18396 .map(|s| {
18397 let range = s.display_range(&display_map).sorted();
18398 let mut start = range.start.to_point(&display_map);
18399 let mut end = range.end.to_point(&display_map);
18400 start.column = 0;
18401 end.column = buffer.line_len(MultiBufferRow(end.row));
18402 start..end
18403 })
18404 .collect::<Vec<_>>();
18405
18406 self.unfold_ranges(&ranges, true, true, cx);
18407 } else {
18408 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18409 let buffer_ids = self
18410 .selections
18411 .disjoint_anchor_ranges()
18412 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18413 .collect::<HashSet<_>>();
18414 for buffer_id in buffer_ids {
18415 self.unfold_buffer(buffer_id, cx);
18416 }
18417 }
18418 }
18419
18420 pub fn unfold_recursive(
18421 &mut self,
18422 _: &UnfoldRecursive,
18423 _window: &mut Window,
18424 cx: &mut Context<Self>,
18425 ) {
18426 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18427 let selections = self.selections.all::<Point>(cx);
18428 let ranges = selections
18429 .iter()
18430 .map(|s| {
18431 let mut range = s.display_range(&display_map).sorted();
18432 *range.start.column_mut() = 0;
18433 *range.end.column_mut() = display_map.line_len(range.end.row());
18434 let start = range.start.to_point(&display_map);
18435 let end = range.end.to_point(&display_map);
18436 start..end
18437 })
18438 .collect::<Vec<_>>();
18439
18440 self.unfold_ranges(&ranges, true, true, cx);
18441 }
18442
18443 pub fn unfold_at(
18444 &mut self,
18445 buffer_row: MultiBufferRow,
18446 _window: &mut Window,
18447 cx: &mut Context<Self>,
18448 ) {
18449 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18450
18451 let intersection_range = Point::new(buffer_row.0, 0)
18452 ..Point::new(
18453 buffer_row.0,
18454 display_map.buffer_snapshot().line_len(buffer_row),
18455 );
18456
18457 let autoscroll = self
18458 .selections
18459 .all::<Point>(cx)
18460 .iter()
18461 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18462
18463 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18464 }
18465
18466 pub fn unfold_all(
18467 &mut self,
18468 _: &actions::UnfoldAll,
18469 _window: &mut Window,
18470 cx: &mut Context<Self>,
18471 ) {
18472 if self.buffer.read(cx).is_singleton() {
18473 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18474 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18475 } else {
18476 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18477 editor
18478 .update(cx, |editor, cx| {
18479 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18480 editor.unfold_buffer(buffer_id, cx);
18481 }
18482 })
18483 .ok();
18484 });
18485 }
18486 }
18487
18488 pub fn fold_selected_ranges(
18489 &mut self,
18490 _: &FoldSelectedRanges,
18491 window: &mut Window,
18492 cx: &mut Context<Self>,
18493 ) {
18494 let selections = self.selections.all_adjusted(cx);
18495 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18496 let ranges = selections
18497 .into_iter()
18498 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18499 .collect::<Vec<_>>();
18500 self.fold_creases(ranges, true, window, cx);
18501 }
18502
18503 pub fn fold_ranges<T: ToOffset + Clone>(
18504 &mut self,
18505 ranges: Vec<Range<T>>,
18506 auto_scroll: bool,
18507 window: &mut Window,
18508 cx: &mut Context<Self>,
18509 ) {
18510 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18511 let ranges = ranges
18512 .into_iter()
18513 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18514 .collect::<Vec<_>>();
18515 self.fold_creases(ranges, auto_scroll, window, cx);
18516 }
18517
18518 pub fn fold_creases<T: ToOffset + Clone>(
18519 &mut self,
18520 creases: Vec<Crease<T>>,
18521 auto_scroll: bool,
18522 _window: &mut Window,
18523 cx: &mut Context<Self>,
18524 ) {
18525 if creases.is_empty() {
18526 return;
18527 }
18528
18529 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18530
18531 if auto_scroll {
18532 self.request_autoscroll(Autoscroll::fit(), cx);
18533 }
18534
18535 cx.notify();
18536
18537 self.scrollbar_marker_state.dirty = true;
18538 self.folds_did_change(cx);
18539 }
18540
18541 /// Removes any folds whose ranges intersect any of the given ranges.
18542 pub fn unfold_ranges<T: ToOffset + Clone>(
18543 &mut self,
18544 ranges: &[Range<T>],
18545 inclusive: bool,
18546 auto_scroll: bool,
18547 cx: &mut Context<Self>,
18548 ) {
18549 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18550 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18551 });
18552 self.folds_did_change(cx);
18553 }
18554
18555 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18556 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18557 return;
18558 }
18559 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18560 self.display_map.update(cx, |display_map, cx| {
18561 display_map.fold_buffers([buffer_id], cx)
18562 });
18563 cx.emit(EditorEvent::BufferFoldToggled {
18564 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18565 folded: true,
18566 });
18567 cx.notify();
18568 }
18569
18570 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18571 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18572 return;
18573 }
18574 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18575 self.display_map.update(cx, |display_map, cx| {
18576 display_map.unfold_buffers([buffer_id], cx);
18577 });
18578 cx.emit(EditorEvent::BufferFoldToggled {
18579 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18580 folded: false,
18581 });
18582 cx.notify();
18583 }
18584
18585 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18586 self.display_map.read(cx).is_buffer_folded(buffer)
18587 }
18588
18589 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18590 self.display_map.read(cx).folded_buffers()
18591 }
18592
18593 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18594 self.display_map.update(cx, |display_map, cx| {
18595 display_map.disable_header_for_buffer(buffer_id, cx);
18596 });
18597 cx.notify();
18598 }
18599
18600 /// Removes any folds with the given ranges.
18601 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18602 &mut self,
18603 ranges: &[Range<T>],
18604 type_id: TypeId,
18605 auto_scroll: bool,
18606 cx: &mut Context<Self>,
18607 ) {
18608 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18609 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18610 });
18611 self.folds_did_change(cx);
18612 }
18613
18614 fn remove_folds_with<T: ToOffset + Clone>(
18615 &mut self,
18616 ranges: &[Range<T>],
18617 auto_scroll: bool,
18618 cx: &mut Context<Self>,
18619 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18620 ) {
18621 if ranges.is_empty() {
18622 return;
18623 }
18624
18625 let mut buffers_affected = HashSet::default();
18626 let multi_buffer = self.buffer().read(cx);
18627 for range in ranges {
18628 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18629 buffers_affected.insert(buffer.read(cx).remote_id());
18630 };
18631 }
18632
18633 self.display_map.update(cx, update);
18634
18635 if auto_scroll {
18636 self.request_autoscroll(Autoscroll::fit(), cx);
18637 }
18638
18639 cx.notify();
18640 self.scrollbar_marker_state.dirty = true;
18641 self.active_indent_guides_state.dirty = true;
18642 }
18643
18644 pub fn update_renderer_widths(
18645 &mut self,
18646 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18647 cx: &mut Context<Self>,
18648 ) -> bool {
18649 self.display_map
18650 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18651 }
18652
18653 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18654 self.display_map.read(cx).fold_placeholder.clone()
18655 }
18656
18657 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18658 self.buffer.update(cx, |buffer, cx| {
18659 buffer.set_all_diff_hunks_expanded(cx);
18660 });
18661 }
18662
18663 pub fn expand_all_diff_hunks(
18664 &mut self,
18665 _: &ExpandAllDiffHunks,
18666 _window: &mut Window,
18667 cx: &mut Context<Self>,
18668 ) {
18669 self.buffer.update(cx, |buffer, cx| {
18670 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18671 });
18672 }
18673
18674 pub fn toggle_selected_diff_hunks(
18675 &mut self,
18676 _: &ToggleSelectedDiffHunks,
18677 _window: &mut Window,
18678 cx: &mut Context<Self>,
18679 ) {
18680 let ranges: Vec<_> = self
18681 .selections
18682 .disjoint_anchors()
18683 .iter()
18684 .map(|s| s.range())
18685 .collect();
18686 self.toggle_diff_hunks_in_ranges(ranges, cx);
18687 }
18688
18689 pub fn diff_hunks_in_ranges<'a>(
18690 &'a self,
18691 ranges: &'a [Range<Anchor>],
18692 buffer: &'a MultiBufferSnapshot,
18693 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18694 ranges.iter().flat_map(move |range| {
18695 let end_excerpt_id = range.end.excerpt_id;
18696 let range = range.to_point(buffer);
18697 let mut peek_end = range.end;
18698 if range.end.row < buffer.max_row().0 {
18699 peek_end = Point::new(range.end.row + 1, 0);
18700 }
18701 buffer
18702 .diff_hunks_in_range(range.start..peek_end)
18703 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18704 })
18705 }
18706
18707 pub fn has_stageable_diff_hunks_in_ranges(
18708 &self,
18709 ranges: &[Range<Anchor>],
18710 snapshot: &MultiBufferSnapshot,
18711 ) -> bool {
18712 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18713 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18714 }
18715
18716 pub fn toggle_staged_selected_diff_hunks(
18717 &mut self,
18718 _: &::git::ToggleStaged,
18719 _: &mut Window,
18720 cx: &mut Context<Self>,
18721 ) {
18722 let snapshot = self.buffer.read(cx).snapshot(cx);
18723 let ranges: Vec<_> = self
18724 .selections
18725 .disjoint_anchors()
18726 .iter()
18727 .map(|s| s.range())
18728 .collect();
18729 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18730 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18731 }
18732
18733 pub fn set_render_diff_hunk_controls(
18734 &mut self,
18735 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18736 cx: &mut Context<Self>,
18737 ) {
18738 self.render_diff_hunk_controls = render_diff_hunk_controls;
18739 cx.notify();
18740 }
18741
18742 pub fn stage_and_next(
18743 &mut self,
18744 _: &::git::StageAndNext,
18745 window: &mut Window,
18746 cx: &mut Context<Self>,
18747 ) {
18748 self.do_stage_or_unstage_and_next(true, window, cx);
18749 }
18750
18751 pub fn unstage_and_next(
18752 &mut self,
18753 _: &::git::UnstageAndNext,
18754 window: &mut Window,
18755 cx: &mut Context<Self>,
18756 ) {
18757 self.do_stage_or_unstage_and_next(false, window, cx);
18758 }
18759
18760 pub fn stage_or_unstage_diff_hunks(
18761 &mut self,
18762 stage: bool,
18763 ranges: Vec<Range<Anchor>>,
18764 cx: &mut Context<Self>,
18765 ) {
18766 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18767 cx.spawn(async move |this, cx| {
18768 task.await?;
18769 this.update(cx, |this, cx| {
18770 let snapshot = this.buffer.read(cx).snapshot(cx);
18771 let chunk_by = this
18772 .diff_hunks_in_ranges(&ranges, &snapshot)
18773 .chunk_by(|hunk| hunk.buffer_id);
18774 for (buffer_id, hunks) in &chunk_by {
18775 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18776 }
18777 })
18778 })
18779 .detach_and_log_err(cx);
18780 }
18781
18782 fn save_buffers_for_ranges_if_needed(
18783 &mut self,
18784 ranges: &[Range<Anchor>],
18785 cx: &mut Context<Editor>,
18786 ) -> Task<Result<()>> {
18787 let multibuffer = self.buffer.read(cx);
18788 let snapshot = multibuffer.read(cx);
18789 let buffer_ids: HashSet<_> = ranges
18790 .iter()
18791 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18792 .collect();
18793 drop(snapshot);
18794
18795 let mut buffers = HashSet::default();
18796 for buffer_id in buffer_ids {
18797 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18798 let buffer = buffer_entity.read(cx);
18799 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18800 {
18801 buffers.insert(buffer_entity);
18802 }
18803 }
18804 }
18805
18806 if let Some(project) = &self.project {
18807 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18808 } else {
18809 Task::ready(Ok(()))
18810 }
18811 }
18812
18813 fn do_stage_or_unstage_and_next(
18814 &mut self,
18815 stage: bool,
18816 window: &mut Window,
18817 cx: &mut Context<Self>,
18818 ) {
18819 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18820
18821 if ranges.iter().any(|range| range.start != range.end) {
18822 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18823 return;
18824 }
18825
18826 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18827 let snapshot = self.snapshot(window, cx);
18828 let position = self.selections.newest::<Point>(cx).head();
18829 let mut row = snapshot
18830 .buffer_snapshot()
18831 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18832 .find(|hunk| hunk.row_range.start.0 > position.row)
18833 .map(|hunk| hunk.row_range.start);
18834
18835 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18836 // Outside of the project diff editor, wrap around to the beginning.
18837 if !all_diff_hunks_expanded {
18838 row = row.or_else(|| {
18839 snapshot
18840 .buffer_snapshot()
18841 .diff_hunks_in_range(Point::zero()..position)
18842 .find(|hunk| hunk.row_range.end.0 < position.row)
18843 .map(|hunk| hunk.row_range.start)
18844 });
18845 }
18846
18847 if let Some(row) = row {
18848 let destination = Point::new(row.0, 0);
18849 let autoscroll = Autoscroll::center();
18850
18851 self.unfold_ranges(&[destination..destination], false, false, cx);
18852 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18853 s.select_ranges([destination..destination]);
18854 });
18855 }
18856 }
18857
18858 fn do_stage_or_unstage(
18859 &self,
18860 stage: bool,
18861 buffer_id: BufferId,
18862 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18863 cx: &mut App,
18864 ) -> Option<()> {
18865 let project = self.project()?;
18866 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18867 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18868 let buffer_snapshot = buffer.read(cx).snapshot();
18869 let file_exists = buffer_snapshot
18870 .file()
18871 .is_some_and(|file| file.disk_state().exists());
18872 diff.update(cx, |diff, cx| {
18873 diff.stage_or_unstage_hunks(
18874 stage,
18875 &hunks
18876 .map(|hunk| buffer_diff::DiffHunk {
18877 buffer_range: hunk.buffer_range,
18878 diff_base_byte_range: hunk.diff_base_byte_range,
18879 secondary_status: hunk.secondary_status,
18880 range: Point::zero()..Point::zero(), // unused
18881 })
18882 .collect::<Vec<_>>(),
18883 &buffer_snapshot,
18884 file_exists,
18885 cx,
18886 )
18887 });
18888 None
18889 }
18890
18891 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18892 let ranges: Vec<_> = self
18893 .selections
18894 .disjoint_anchors()
18895 .iter()
18896 .map(|s| s.range())
18897 .collect();
18898 self.buffer
18899 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18900 }
18901
18902 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18903 self.buffer.update(cx, |buffer, cx| {
18904 let ranges = vec![Anchor::min()..Anchor::max()];
18905 if !buffer.all_diff_hunks_expanded()
18906 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18907 {
18908 buffer.collapse_diff_hunks(ranges, cx);
18909 true
18910 } else {
18911 false
18912 }
18913 })
18914 }
18915
18916 fn toggle_diff_hunks_in_ranges(
18917 &mut self,
18918 ranges: Vec<Range<Anchor>>,
18919 cx: &mut Context<Editor>,
18920 ) {
18921 self.buffer.update(cx, |buffer, cx| {
18922 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18923 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18924 })
18925 }
18926
18927 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18928 self.buffer.update(cx, |buffer, cx| {
18929 let snapshot = buffer.snapshot(cx);
18930 let excerpt_id = range.end.excerpt_id;
18931 let point_range = range.to_point(&snapshot);
18932 let expand = !buffer.single_hunk_is_expanded(range, cx);
18933 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18934 })
18935 }
18936
18937 pub(crate) fn apply_all_diff_hunks(
18938 &mut self,
18939 _: &ApplyAllDiffHunks,
18940 window: &mut Window,
18941 cx: &mut Context<Self>,
18942 ) {
18943 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18944
18945 let buffers = self.buffer.read(cx).all_buffers();
18946 for branch_buffer in buffers {
18947 branch_buffer.update(cx, |branch_buffer, cx| {
18948 branch_buffer.merge_into_base(Vec::new(), cx);
18949 });
18950 }
18951
18952 if let Some(project) = self.project.clone() {
18953 self.save(
18954 SaveOptions {
18955 format: true,
18956 autosave: false,
18957 },
18958 project,
18959 window,
18960 cx,
18961 )
18962 .detach_and_log_err(cx);
18963 }
18964 }
18965
18966 pub(crate) fn apply_selected_diff_hunks(
18967 &mut self,
18968 _: &ApplyDiffHunk,
18969 window: &mut Window,
18970 cx: &mut Context<Self>,
18971 ) {
18972 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18973 let snapshot = self.snapshot(window, cx);
18974 let hunks = snapshot.hunks_for_ranges(
18975 self.selections
18976 .all(cx)
18977 .into_iter()
18978 .map(|selection| selection.range()),
18979 );
18980 let mut ranges_by_buffer = HashMap::default();
18981 self.transact(window, cx, |editor, _window, cx| {
18982 for hunk in hunks {
18983 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18984 ranges_by_buffer
18985 .entry(buffer.clone())
18986 .or_insert_with(Vec::new)
18987 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18988 }
18989 }
18990
18991 for (buffer, ranges) in ranges_by_buffer {
18992 buffer.update(cx, |buffer, cx| {
18993 buffer.merge_into_base(ranges, cx);
18994 });
18995 }
18996 });
18997
18998 if let Some(project) = self.project.clone() {
18999 self.save(
19000 SaveOptions {
19001 format: true,
19002 autosave: false,
19003 },
19004 project,
19005 window,
19006 cx,
19007 )
19008 .detach_and_log_err(cx);
19009 }
19010 }
19011
19012 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19013 if hovered != self.gutter_hovered {
19014 self.gutter_hovered = hovered;
19015 cx.notify();
19016 }
19017 }
19018
19019 pub fn insert_blocks(
19020 &mut self,
19021 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19022 autoscroll: Option<Autoscroll>,
19023 cx: &mut Context<Self>,
19024 ) -> Vec<CustomBlockId> {
19025 let blocks = self
19026 .display_map
19027 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19028 if let Some(autoscroll) = autoscroll {
19029 self.request_autoscroll(autoscroll, cx);
19030 }
19031 cx.notify();
19032 blocks
19033 }
19034
19035 pub fn resize_blocks(
19036 &mut self,
19037 heights: HashMap<CustomBlockId, u32>,
19038 autoscroll: Option<Autoscroll>,
19039 cx: &mut Context<Self>,
19040 ) {
19041 self.display_map
19042 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19043 if let Some(autoscroll) = autoscroll {
19044 self.request_autoscroll(autoscroll, cx);
19045 }
19046 cx.notify();
19047 }
19048
19049 pub fn replace_blocks(
19050 &mut self,
19051 renderers: HashMap<CustomBlockId, RenderBlock>,
19052 autoscroll: Option<Autoscroll>,
19053 cx: &mut Context<Self>,
19054 ) {
19055 self.display_map
19056 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19057 if let Some(autoscroll) = autoscroll {
19058 self.request_autoscroll(autoscroll, cx);
19059 }
19060 cx.notify();
19061 }
19062
19063 pub fn remove_blocks(
19064 &mut self,
19065 block_ids: HashSet<CustomBlockId>,
19066 autoscroll: Option<Autoscroll>,
19067 cx: &mut Context<Self>,
19068 ) {
19069 self.display_map.update(cx, |display_map, cx| {
19070 display_map.remove_blocks(block_ids, cx)
19071 });
19072 if let Some(autoscroll) = autoscroll {
19073 self.request_autoscroll(autoscroll, cx);
19074 }
19075 cx.notify();
19076 }
19077
19078 pub fn row_for_block(
19079 &self,
19080 block_id: CustomBlockId,
19081 cx: &mut Context<Self>,
19082 ) -> Option<DisplayRow> {
19083 self.display_map
19084 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19085 }
19086
19087 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19088 self.focused_block = Some(focused_block);
19089 }
19090
19091 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19092 self.focused_block.take()
19093 }
19094
19095 pub fn insert_creases(
19096 &mut self,
19097 creases: impl IntoIterator<Item = Crease<Anchor>>,
19098 cx: &mut Context<Self>,
19099 ) -> Vec<CreaseId> {
19100 self.display_map
19101 .update(cx, |map, cx| map.insert_creases(creases, cx))
19102 }
19103
19104 pub fn remove_creases(
19105 &mut self,
19106 ids: impl IntoIterator<Item = CreaseId>,
19107 cx: &mut Context<Self>,
19108 ) -> Vec<(CreaseId, Range<Anchor>)> {
19109 self.display_map
19110 .update(cx, |map, cx| map.remove_creases(ids, cx))
19111 }
19112
19113 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19114 self.display_map
19115 .update(cx, |map, cx| map.snapshot(cx))
19116 .longest_row()
19117 }
19118
19119 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19120 self.display_map
19121 .update(cx, |map, cx| map.snapshot(cx))
19122 .max_point()
19123 }
19124
19125 pub fn text(&self, cx: &App) -> String {
19126 self.buffer.read(cx).read(cx).text()
19127 }
19128
19129 pub fn is_empty(&self, cx: &App) -> bool {
19130 self.buffer.read(cx).read(cx).is_empty()
19131 }
19132
19133 pub fn text_option(&self, cx: &App) -> Option<String> {
19134 let text = self.text(cx);
19135 let text = text.trim();
19136
19137 if text.is_empty() {
19138 return None;
19139 }
19140
19141 Some(text.to_string())
19142 }
19143
19144 pub fn set_text(
19145 &mut self,
19146 text: impl Into<Arc<str>>,
19147 window: &mut Window,
19148 cx: &mut Context<Self>,
19149 ) {
19150 self.transact(window, cx, |this, _, cx| {
19151 this.buffer
19152 .read(cx)
19153 .as_singleton()
19154 .expect("you can only call set_text on editors for singleton buffers")
19155 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19156 });
19157 }
19158
19159 pub fn display_text(&self, cx: &mut App) -> String {
19160 self.display_map
19161 .update(cx, |map, cx| map.snapshot(cx))
19162 .text()
19163 }
19164
19165 fn create_minimap(
19166 &self,
19167 minimap_settings: MinimapSettings,
19168 window: &mut Window,
19169 cx: &mut Context<Self>,
19170 ) -> Option<Entity<Self>> {
19171 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19172 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19173 }
19174
19175 fn initialize_new_minimap(
19176 &self,
19177 minimap_settings: MinimapSettings,
19178 window: &mut Window,
19179 cx: &mut Context<Self>,
19180 ) -> Entity<Self> {
19181 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19182
19183 let mut minimap = Editor::new_internal(
19184 EditorMode::Minimap {
19185 parent: cx.weak_entity(),
19186 },
19187 self.buffer.clone(),
19188 None,
19189 Some(self.display_map.clone()),
19190 window,
19191 cx,
19192 );
19193 minimap.scroll_manager.clone_state(&self.scroll_manager);
19194 minimap.set_text_style_refinement(TextStyleRefinement {
19195 font_size: Some(MINIMAP_FONT_SIZE),
19196 font_weight: Some(MINIMAP_FONT_WEIGHT),
19197 ..Default::default()
19198 });
19199 minimap.update_minimap_configuration(minimap_settings, cx);
19200 cx.new(|_| minimap)
19201 }
19202
19203 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19204 let current_line_highlight = minimap_settings
19205 .current_line_highlight
19206 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19207 self.set_current_line_highlight(Some(current_line_highlight));
19208 }
19209
19210 pub fn minimap(&self) -> Option<&Entity<Self>> {
19211 self.minimap
19212 .as_ref()
19213 .filter(|_| self.minimap_visibility.visible())
19214 }
19215
19216 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19217 let mut wrap_guides = smallvec![];
19218
19219 if self.show_wrap_guides == Some(false) {
19220 return wrap_guides;
19221 }
19222
19223 let settings = self.buffer.read(cx).language_settings(cx);
19224 if settings.show_wrap_guides {
19225 match self.soft_wrap_mode(cx) {
19226 SoftWrap::Column(soft_wrap) => {
19227 wrap_guides.push((soft_wrap as usize, true));
19228 }
19229 SoftWrap::Bounded(soft_wrap) => {
19230 wrap_guides.push((soft_wrap as usize, true));
19231 }
19232 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19233 }
19234 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19235 }
19236
19237 wrap_guides
19238 }
19239
19240 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19241 let settings = self.buffer.read(cx).language_settings(cx);
19242 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19243 match mode {
19244 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19245 SoftWrap::None
19246 }
19247 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19248 language_settings::SoftWrap::PreferredLineLength => {
19249 SoftWrap::Column(settings.preferred_line_length)
19250 }
19251 language_settings::SoftWrap::Bounded => {
19252 SoftWrap::Bounded(settings.preferred_line_length)
19253 }
19254 }
19255 }
19256
19257 pub fn set_soft_wrap_mode(
19258 &mut self,
19259 mode: language_settings::SoftWrap,
19260
19261 cx: &mut Context<Self>,
19262 ) {
19263 self.soft_wrap_mode_override = Some(mode);
19264 cx.notify();
19265 }
19266
19267 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19268 self.hard_wrap = hard_wrap;
19269 cx.notify();
19270 }
19271
19272 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19273 self.text_style_refinement = Some(style);
19274 }
19275
19276 /// called by the Element so we know what style we were most recently rendered with.
19277 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19278 // We intentionally do not inform the display map about the minimap style
19279 // so that wrapping is not recalculated and stays consistent for the editor
19280 // and its linked minimap.
19281 if !self.mode.is_minimap() {
19282 let font = style.text.font();
19283 let font_size = style.text.font_size.to_pixels(window.rem_size());
19284 let display_map = self
19285 .placeholder_display_map
19286 .as_ref()
19287 .filter(|_| self.is_empty(cx))
19288 .unwrap_or(&self.display_map);
19289
19290 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19291 }
19292 self.style = Some(style);
19293 }
19294
19295 pub fn style(&self) -> Option<&EditorStyle> {
19296 self.style.as_ref()
19297 }
19298
19299 // Called by the element. This method is not designed to be called outside of the editor
19300 // element's layout code because it does not notify when rewrapping is computed synchronously.
19301 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19302 if self.is_empty(cx) {
19303 self.placeholder_display_map
19304 .as_ref()
19305 .map_or(false, |display_map| {
19306 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19307 })
19308 } else {
19309 self.display_map
19310 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19311 }
19312 }
19313
19314 pub fn set_soft_wrap(&mut self) {
19315 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19316 }
19317
19318 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19319 if self.soft_wrap_mode_override.is_some() {
19320 self.soft_wrap_mode_override.take();
19321 } else {
19322 let soft_wrap = match self.soft_wrap_mode(cx) {
19323 SoftWrap::GitDiff => return,
19324 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19325 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19326 language_settings::SoftWrap::None
19327 }
19328 };
19329 self.soft_wrap_mode_override = Some(soft_wrap);
19330 }
19331 cx.notify();
19332 }
19333
19334 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19335 let Some(workspace) = self.workspace() else {
19336 return;
19337 };
19338 let fs = workspace.read(cx).app_state().fs.clone();
19339 let current_show = TabBarSettings::get_global(cx).show;
19340 update_settings_file(fs, cx, move |setting, _| {
19341 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19342 });
19343 }
19344
19345 pub fn toggle_indent_guides(
19346 &mut self,
19347 _: &ToggleIndentGuides,
19348 _: &mut Window,
19349 cx: &mut Context<Self>,
19350 ) {
19351 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19352 self.buffer
19353 .read(cx)
19354 .language_settings(cx)
19355 .indent_guides
19356 .enabled
19357 });
19358 self.show_indent_guides = Some(!currently_enabled);
19359 cx.notify();
19360 }
19361
19362 fn should_show_indent_guides(&self) -> Option<bool> {
19363 self.show_indent_guides
19364 }
19365
19366 pub fn toggle_line_numbers(
19367 &mut self,
19368 _: &ToggleLineNumbers,
19369 _: &mut Window,
19370 cx: &mut Context<Self>,
19371 ) {
19372 let mut editor_settings = EditorSettings::get_global(cx).clone();
19373 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19374 EditorSettings::override_global(editor_settings, cx);
19375 }
19376
19377 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19378 if let Some(show_line_numbers) = self.show_line_numbers {
19379 return show_line_numbers;
19380 }
19381 EditorSettings::get_global(cx).gutter.line_numbers
19382 }
19383
19384 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19385 self.use_relative_line_numbers
19386 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19387 }
19388
19389 pub fn toggle_relative_line_numbers(
19390 &mut self,
19391 _: &ToggleRelativeLineNumbers,
19392 _: &mut Window,
19393 cx: &mut Context<Self>,
19394 ) {
19395 let is_relative = self.should_use_relative_line_numbers(cx);
19396 self.set_relative_line_number(Some(!is_relative), cx)
19397 }
19398
19399 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19400 self.use_relative_line_numbers = is_relative;
19401 cx.notify();
19402 }
19403
19404 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19405 self.show_gutter = show_gutter;
19406 cx.notify();
19407 }
19408
19409 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19410 self.show_scrollbars = ScrollbarAxes {
19411 horizontal: show,
19412 vertical: show,
19413 };
19414 cx.notify();
19415 }
19416
19417 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19418 self.show_scrollbars.vertical = show;
19419 cx.notify();
19420 }
19421
19422 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19423 self.show_scrollbars.horizontal = show;
19424 cx.notify();
19425 }
19426
19427 pub fn set_minimap_visibility(
19428 &mut self,
19429 minimap_visibility: MinimapVisibility,
19430 window: &mut Window,
19431 cx: &mut Context<Self>,
19432 ) {
19433 if self.minimap_visibility != minimap_visibility {
19434 if minimap_visibility.visible() && self.minimap.is_none() {
19435 let minimap_settings = EditorSettings::get_global(cx).minimap;
19436 self.minimap =
19437 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19438 }
19439 self.minimap_visibility = minimap_visibility;
19440 cx.notify();
19441 }
19442 }
19443
19444 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19445 self.set_show_scrollbars(false, cx);
19446 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19447 }
19448
19449 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19450 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19451 }
19452
19453 /// Normally the text in full mode and auto height editors is padded on the
19454 /// left side by roughly half a character width for improved hit testing.
19455 ///
19456 /// Use this method to disable this for cases where this is not wanted (e.g.
19457 /// if you want to align the editor text with some other text above or below)
19458 /// or if you want to add this padding to single-line editors.
19459 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19460 self.offset_content = offset_content;
19461 cx.notify();
19462 }
19463
19464 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19465 self.show_line_numbers = Some(show_line_numbers);
19466 cx.notify();
19467 }
19468
19469 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19470 self.disable_expand_excerpt_buttons = true;
19471 cx.notify();
19472 }
19473
19474 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19475 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19476 cx.notify();
19477 }
19478
19479 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19480 self.show_code_actions = Some(show_code_actions);
19481 cx.notify();
19482 }
19483
19484 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19485 self.show_runnables = Some(show_runnables);
19486 cx.notify();
19487 }
19488
19489 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19490 self.show_breakpoints = Some(show_breakpoints);
19491 cx.notify();
19492 }
19493
19494 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19495 if self.display_map.read(cx).masked != masked {
19496 self.display_map.update(cx, |map, _| map.masked = masked);
19497 }
19498 cx.notify()
19499 }
19500
19501 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19502 self.show_wrap_guides = Some(show_wrap_guides);
19503 cx.notify();
19504 }
19505
19506 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19507 self.show_indent_guides = Some(show_indent_guides);
19508 cx.notify();
19509 }
19510
19511 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19512 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19513 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19514 && let Some(dir) = file.abs_path(cx).parent()
19515 {
19516 return Some(dir.to_owned());
19517 }
19518 }
19519
19520 None
19521 }
19522
19523 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19524 self.active_excerpt(cx)?
19525 .1
19526 .read(cx)
19527 .file()
19528 .and_then(|f| f.as_local())
19529 }
19530
19531 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19532 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19533 let buffer = buffer.read(cx);
19534 if let Some(project_path) = buffer.project_path(cx) {
19535 let project = self.project()?.read(cx);
19536 project.absolute_path(&project_path, cx)
19537 } else {
19538 buffer
19539 .file()
19540 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19541 }
19542 })
19543 }
19544
19545 pub fn reveal_in_finder(
19546 &mut self,
19547 _: &RevealInFileManager,
19548 _window: &mut Window,
19549 cx: &mut Context<Self>,
19550 ) {
19551 if let Some(target) = self.target_file(cx) {
19552 cx.reveal_path(&target.abs_path(cx));
19553 }
19554 }
19555
19556 pub fn copy_path(
19557 &mut self,
19558 _: &zed_actions::workspace::CopyPath,
19559 _window: &mut Window,
19560 cx: &mut Context<Self>,
19561 ) {
19562 if let Some(path) = self.target_file_abs_path(cx)
19563 && let Some(path) = path.to_str()
19564 {
19565 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19566 } else {
19567 cx.propagate();
19568 }
19569 }
19570
19571 pub fn copy_relative_path(
19572 &mut self,
19573 _: &zed_actions::workspace::CopyRelativePath,
19574 _window: &mut Window,
19575 cx: &mut Context<Self>,
19576 ) {
19577 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19578 let project = self.project()?.read(cx);
19579 let path = buffer.read(cx).file()?.path();
19580 let path = path.display(project.path_style(cx));
19581 Some(path)
19582 }) {
19583 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19584 } else {
19585 cx.propagate();
19586 }
19587 }
19588
19589 /// Returns the project path for the editor's buffer, if any buffer is
19590 /// opened in the editor.
19591 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19592 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19593 buffer.read(cx).project_path(cx)
19594 } else {
19595 None
19596 }
19597 }
19598
19599 // Returns true if the editor handled a go-to-line request
19600 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19601 maybe!({
19602 let breakpoint_store = self.breakpoint_store.as_ref()?;
19603
19604 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19605 else {
19606 self.clear_row_highlights::<ActiveDebugLine>();
19607 return None;
19608 };
19609
19610 let position = active_stack_frame.position;
19611 let buffer_id = position.buffer_id?;
19612 let snapshot = self
19613 .project
19614 .as_ref()?
19615 .read(cx)
19616 .buffer_for_id(buffer_id, cx)?
19617 .read(cx)
19618 .snapshot();
19619
19620 let mut handled = false;
19621 for (id, ExcerptRange { context, .. }) in
19622 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19623 {
19624 if context.start.cmp(&position, &snapshot).is_ge()
19625 || context.end.cmp(&position, &snapshot).is_lt()
19626 {
19627 continue;
19628 }
19629 let snapshot = self.buffer.read(cx).snapshot(cx);
19630 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19631
19632 handled = true;
19633 self.clear_row_highlights::<ActiveDebugLine>();
19634
19635 self.go_to_line::<ActiveDebugLine>(
19636 multibuffer_anchor,
19637 Some(cx.theme().colors().editor_debugger_active_line_background),
19638 window,
19639 cx,
19640 );
19641
19642 cx.notify();
19643 }
19644
19645 handled.then_some(())
19646 })
19647 .is_some()
19648 }
19649
19650 pub fn copy_file_name_without_extension(
19651 &mut self,
19652 _: &CopyFileNameWithoutExtension,
19653 _: &mut Window,
19654 cx: &mut Context<Self>,
19655 ) {
19656 if let Some(file) = self.target_file(cx)
19657 && let Some(file_stem) = file.path().file_stem()
19658 {
19659 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19660 }
19661 }
19662
19663 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19664 if let Some(file) = self.target_file(cx)
19665 && let Some(name) = file.path().file_name()
19666 {
19667 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19668 }
19669 }
19670
19671 pub fn toggle_git_blame(
19672 &mut self,
19673 _: &::git::Blame,
19674 window: &mut Window,
19675 cx: &mut Context<Self>,
19676 ) {
19677 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19678
19679 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19680 self.start_git_blame(true, window, cx);
19681 }
19682
19683 cx.notify();
19684 }
19685
19686 pub fn toggle_git_blame_inline(
19687 &mut self,
19688 _: &ToggleGitBlameInline,
19689 window: &mut Window,
19690 cx: &mut Context<Self>,
19691 ) {
19692 self.toggle_git_blame_inline_internal(true, window, cx);
19693 cx.notify();
19694 }
19695
19696 pub fn open_git_blame_commit(
19697 &mut self,
19698 _: &OpenGitBlameCommit,
19699 window: &mut Window,
19700 cx: &mut Context<Self>,
19701 ) {
19702 self.open_git_blame_commit_internal(window, cx);
19703 }
19704
19705 fn open_git_blame_commit_internal(
19706 &mut self,
19707 window: &mut Window,
19708 cx: &mut Context<Self>,
19709 ) -> Option<()> {
19710 let blame = self.blame.as_ref()?;
19711 let snapshot = self.snapshot(window, cx);
19712 let cursor = self.selections.newest::<Point>(cx).head();
19713 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19714 let (_, blame_entry) = blame
19715 .update(cx, |blame, cx| {
19716 blame
19717 .blame_for_rows(
19718 &[RowInfo {
19719 buffer_id: Some(buffer.remote_id()),
19720 buffer_row: Some(point.row),
19721 ..Default::default()
19722 }],
19723 cx,
19724 )
19725 .next()
19726 })
19727 .flatten()?;
19728 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19729 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19730 let workspace = self.workspace()?.downgrade();
19731 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19732 None
19733 }
19734
19735 pub fn git_blame_inline_enabled(&self) -> bool {
19736 self.git_blame_inline_enabled
19737 }
19738
19739 pub fn toggle_selection_menu(
19740 &mut self,
19741 _: &ToggleSelectionMenu,
19742 _: &mut Window,
19743 cx: &mut Context<Self>,
19744 ) {
19745 self.show_selection_menu = self
19746 .show_selection_menu
19747 .map(|show_selections_menu| !show_selections_menu)
19748 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19749
19750 cx.notify();
19751 }
19752
19753 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19754 self.show_selection_menu
19755 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19756 }
19757
19758 fn start_git_blame(
19759 &mut self,
19760 user_triggered: bool,
19761 window: &mut Window,
19762 cx: &mut Context<Self>,
19763 ) {
19764 if let Some(project) = self.project() {
19765 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19766 && buffer.read(cx).file().is_none()
19767 {
19768 return;
19769 }
19770
19771 let focused = self.focus_handle(cx).contains_focused(window, cx);
19772
19773 let project = project.clone();
19774 let blame = cx
19775 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19776 self.blame_subscription =
19777 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19778 self.blame = Some(blame);
19779 }
19780 }
19781
19782 fn toggle_git_blame_inline_internal(
19783 &mut self,
19784 user_triggered: bool,
19785 window: &mut Window,
19786 cx: &mut Context<Self>,
19787 ) {
19788 if self.git_blame_inline_enabled {
19789 self.git_blame_inline_enabled = false;
19790 self.show_git_blame_inline = false;
19791 self.show_git_blame_inline_delay_task.take();
19792 } else {
19793 self.git_blame_inline_enabled = true;
19794 self.start_git_blame_inline(user_triggered, window, cx);
19795 }
19796
19797 cx.notify();
19798 }
19799
19800 fn start_git_blame_inline(
19801 &mut self,
19802 user_triggered: bool,
19803 window: &mut Window,
19804 cx: &mut Context<Self>,
19805 ) {
19806 self.start_git_blame(user_triggered, window, cx);
19807
19808 if ProjectSettings::get_global(cx)
19809 .git
19810 .inline_blame_delay()
19811 .is_some()
19812 {
19813 self.start_inline_blame_timer(window, cx);
19814 } else {
19815 self.show_git_blame_inline = true
19816 }
19817 }
19818
19819 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19820 self.blame.as_ref()
19821 }
19822
19823 pub fn show_git_blame_gutter(&self) -> bool {
19824 self.show_git_blame_gutter
19825 }
19826
19827 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19828 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19829 }
19830
19831 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19832 self.show_git_blame_inline
19833 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19834 && !self.newest_selection_head_on_empty_line(cx)
19835 && self.has_blame_entries(cx)
19836 }
19837
19838 fn has_blame_entries(&self, cx: &App) -> bool {
19839 self.blame()
19840 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19841 }
19842
19843 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19844 let cursor_anchor = self.selections.newest_anchor().head();
19845
19846 let snapshot = self.buffer.read(cx).snapshot(cx);
19847 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19848
19849 snapshot.line_len(buffer_row) == 0
19850 }
19851
19852 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19853 let buffer_and_selection = maybe!({
19854 let selection = self.selections.newest::<Point>(cx);
19855 let selection_range = selection.range();
19856
19857 let multi_buffer = self.buffer().read(cx);
19858 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19859 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19860
19861 let (buffer, range, _) = if selection.reversed {
19862 buffer_ranges.first()
19863 } else {
19864 buffer_ranges.last()
19865 }?;
19866
19867 let selection = text::ToPoint::to_point(&range.start, buffer).row
19868 ..text::ToPoint::to_point(&range.end, buffer).row;
19869 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19870 });
19871
19872 let Some((buffer, selection)) = buffer_and_selection else {
19873 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19874 };
19875
19876 let Some(project) = self.project() else {
19877 return Task::ready(Err(anyhow!("editor does not have project")));
19878 };
19879
19880 project.update(cx, |project, cx| {
19881 project.get_permalink_to_line(&buffer, selection, cx)
19882 })
19883 }
19884
19885 pub fn copy_permalink_to_line(
19886 &mut self,
19887 _: &CopyPermalinkToLine,
19888 window: &mut Window,
19889 cx: &mut Context<Self>,
19890 ) {
19891 let permalink_task = self.get_permalink_to_line(cx);
19892 let workspace = self.workspace();
19893
19894 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19895 Ok(permalink) => {
19896 cx.update(|_, cx| {
19897 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19898 })
19899 .ok();
19900 }
19901 Err(err) => {
19902 let message = format!("Failed to copy permalink: {err}");
19903
19904 anyhow::Result::<()>::Err(err).log_err();
19905
19906 if let Some(workspace) = workspace {
19907 workspace
19908 .update_in(cx, |workspace, _, cx| {
19909 struct CopyPermalinkToLine;
19910
19911 workspace.show_toast(
19912 Toast::new(
19913 NotificationId::unique::<CopyPermalinkToLine>(),
19914 message,
19915 ),
19916 cx,
19917 )
19918 })
19919 .ok();
19920 }
19921 }
19922 })
19923 .detach();
19924 }
19925
19926 pub fn copy_file_location(
19927 &mut self,
19928 _: &CopyFileLocation,
19929 _: &mut Window,
19930 cx: &mut Context<Self>,
19931 ) {
19932 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19933 if let Some(file) = self.target_file(cx) {
19934 let path = file.path().display(file.path_style(cx));
19935 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19936 }
19937 }
19938
19939 pub fn open_permalink_to_line(
19940 &mut self,
19941 _: &OpenPermalinkToLine,
19942 window: &mut Window,
19943 cx: &mut Context<Self>,
19944 ) {
19945 let permalink_task = self.get_permalink_to_line(cx);
19946 let workspace = self.workspace();
19947
19948 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19949 Ok(permalink) => {
19950 cx.update(|_, cx| {
19951 cx.open_url(permalink.as_ref());
19952 })
19953 .ok();
19954 }
19955 Err(err) => {
19956 let message = format!("Failed to open permalink: {err}");
19957
19958 anyhow::Result::<()>::Err(err).log_err();
19959
19960 if let Some(workspace) = workspace {
19961 workspace
19962 .update(cx, |workspace, cx| {
19963 struct OpenPermalinkToLine;
19964
19965 workspace.show_toast(
19966 Toast::new(
19967 NotificationId::unique::<OpenPermalinkToLine>(),
19968 message,
19969 ),
19970 cx,
19971 )
19972 })
19973 .ok();
19974 }
19975 }
19976 })
19977 .detach();
19978 }
19979
19980 pub fn insert_uuid_v4(
19981 &mut self,
19982 _: &InsertUuidV4,
19983 window: &mut Window,
19984 cx: &mut Context<Self>,
19985 ) {
19986 self.insert_uuid(UuidVersion::V4, window, cx);
19987 }
19988
19989 pub fn insert_uuid_v7(
19990 &mut self,
19991 _: &InsertUuidV7,
19992 window: &mut Window,
19993 cx: &mut Context<Self>,
19994 ) {
19995 self.insert_uuid(UuidVersion::V7, window, cx);
19996 }
19997
19998 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19999 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20000 self.transact(window, cx, |this, window, cx| {
20001 let edits = this
20002 .selections
20003 .all::<Point>(cx)
20004 .into_iter()
20005 .map(|selection| {
20006 let uuid = match version {
20007 UuidVersion::V4 => uuid::Uuid::new_v4(),
20008 UuidVersion::V7 => uuid::Uuid::now_v7(),
20009 };
20010
20011 (selection.range(), uuid.to_string())
20012 });
20013 this.edit(edits, cx);
20014 this.refresh_edit_prediction(true, false, window, cx);
20015 });
20016 }
20017
20018 pub fn open_selections_in_multibuffer(
20019 &mut self,
20020 _: &OpenSelectionsInMultibuffer,
20021 window: &mut Window,
20022 cx: &mut Context<Self>,
20023 ) {
20024 let multibuffer = self.buffer.read(cx);
20025
20026 let Some(buffer) = multibuffer.as_singleton() else {
20027 return;
20028 };
20029
20030 let Some(workspace) = self.workspace() else {
20031 return;
20032 };
20033
20034 let title = multibuffer.title(cx).to_string();
20035
20036 let locations = self
20037 .selections
20038 .all_anchors(cx)
20039 .iter()
20040 .map(|selection| {
20041 (
20042 buffer.clone(),
20043 (selection.start.text_anchor..selection.end.text_anchor)
20044 .to_point(buffer.read(cx)),
20045 )
20046 })
20047 .into_group_map();
20048
20049 cx.spawn_in(window, async move |_, cx| {
20050 workspace.update_in(cx, |workspace, window, cx| {
20051 Self::open_locations_in_multibuffer(
20052 workspace,
20053 locations,
20054 format!("Selections for '{title}'"),
20055 false,
20056 MultibufferSelectionMode::All,
20057 window,
20058 cx,
20059 );
20060 })
20061 })
20062 .detach();
20063 }
20064
20065 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20066 /// last highlight added will be used.
20067 ///
20068 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20069 pub fn highlight_rows<T: 'static>(
20070 &mut self,
20071 range: Range<Anchor>,
20072 color: Hsla,
20073 options: RowHighlightOptions,
20074 cx: &mut Context<Self>,
20075 ) {
20076 let snapshot = self.buffer().read(cx).snapshot(cx);
20077 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20078 let ix = row_highlights.binary_search_by(|highlight| {
20079 Ordering::Equal
20080 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20081 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20082 });
20083
20084 if let Err(mut ix) = ix {
20085 let index = post_inc(&mut self.highlight_order);
20086
20087 // If this range intersects with the preceding highlight, then merge it with
20088 // the preceding highlight. Otherwise insert a new highlight.
20089 let mut merged = false;
20090 if ix > 0 {
20091 let prev_highlight = &mut row_highlights[ix - 1];
20092 if prev_highlight
20093 .range
20094 .end
20095 .cmp(&range.start, &snapshot)
20096 .is_ge()
20097 {
20098 ix -= 1;
20099 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20100 prev_highlight.range.end = range.end;
20101 }
20102 merged = true;
20103 prev_highlight.index = index;
20104 prev_highlight.color = color;
20105 prev_highlight.options = options;
20106 }
20107 }
20108
20109 if !merged {
20110 row_highlights.insert(
20111 ix,
20112 RowHighlight {
20113 range,
20114 index,
20115 color,
20116 options,
20117 type_id: TypeId::of::<T>(),
20118 },
20119 );
20120 }
20121
20122 // If any of the following highlights intersect with this one, merge them.
20123 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20124 let highlight = &row_highlights[ix];
20125 if next_highlight
20126 .range
20127 .start
20128 .cmp(&highlight.range.end, &snapshot)
20129 .is_le()
20130 {
20131 if next_highlight
20132 .range
20133 .end
20134 .cmp(&highlight.range.end, &snapshot)
20135 .is_gt()
20136 {
20137 row_highlights[ix].range.end = next_highlight.range.end;
20138 }
20139 row_highlights.remove(ix + 1);
20140 } else {
20141 break;
20142 }
20143 }
20144 }
20145 }
20146
20147 /// Remove any highlighted row ranges of the given type that intersect the
20148 /// given ranges.
20149 pub fn remove_highlighted_rows<T: 'static>(
20150 &mut self,
20151 ranges_to_remove: Vec<Range<Anchor>>,
20152 cx: &mut Context<Self>,
20153 ) {
20154 let snapshot = self.buffer().read(cx).snapshot(cx);
20155 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20156 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20157 row_highlights.retain(|highlight| {
20158 while let Some(range_to_remove) = ranges_to_remove.peek() {
20159 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20160 Ordering::Less | Ordering::Equal => {
20161 ranges_to_remove.next();
20162 }
20163 Ordering::Greater => {
20164 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20165 Ordering::Less | Ordering::Equal => {
20166 return false;
20167 }
20168 Ordering::Greater => break,
20169 }
20170 }
20171 }
20172 }
20173
20174 true
20175 })
20176 }
20177
20178 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20179 pub fn clear_row_highlights<T: 'static>(&mut self) {
20180 self.highlighted_rows.remove(&TypeId::of::<T>());
20181 }
20182
20183 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20184 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20185 self.highlighted_rows
20186 .get(&TypeId::of::<T>())
20187 .map_or(&[] as &[_], |vec| vec.as_slice())
20188 .iter()
20189 .map(|highlight| (highlight.range.clone(), highlight.color))
20190 }
20191
20192 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20193 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20194 /// Allows to ignore certain kinds of highlights.
20195 pub fn highlighted_display_rows(
20196 &self,
20197 window: &mut Window,
20198 cx: &mut App,
20199 ) -> BTreeMap<DisplayRow, LineHighlight> {
20200 let snapshot = self.snapshot(window, cx);
20201 let mut used_highlight_orders = HashMap::default();
20202 self.highlighted_rows
20203 .iter()
20204 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20205 .fold(
20206 BTreeMap::<DisplayRow, LineHighlight>::new(),
20207 |mut unique_rows, highlight| {
20208 let start = highlight.range.start.to_display_point(&snapshot);
20209 let end = highlight.range.end.to_display_point(&snapshot);
20210 let start_row = start.row().0;
20211 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20212 && end.column() == 0
20213 {
20214 end.row().0.saturating_sub(1)
20215 } else {
20216 end.row().0
20217 };
20218 for row in start_row..=end_row {
20219 let used_index =
20220 used_highlight_orders.entry(row).or_insert(highlight.index);
20221 if highlight.index >= *used_index {
20222 *used_index = highlight.index;
20223 unique_rows.insert(
20224 DisplayRow(row),
20225 LineHighlight {
20226 include_gutter: highlight.options.include_gutter,
20227 border: None,
20228 background: highlight.color.into(),
20229 type_id: Some(highlight.type_id),
20230 },
20231 );
20232 }
20233 }
20234 unique_rows
20235 },
20236 )
20237 }
20238
20239 pub fn highlighted_display_row_for_autoscroll(
20240 &self,
20241 snapshot: &DisplaySnapshot,
20242 ) -> Option<DisplayRow> {
20243 self.highlighted_rows
20244 .values()
20245 .flat_map(|highlighted_rows| highlighted_rows.iter())
20246 .filter_map(|highlight| {
20247 if highlight.options.autoscroll {
20248 Some(highlight.range.start.to_display_point(snapshot).row())
20249 } else {
20250 None
20251 }
20252 })
20253 .min()
20254 }
20255
20256 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20257 self.highlight_background::<SearchWithinRange>(
20258 ranges,
20259 |colors| colors.colors().editor_document_highlight_read_background,
20260 cx,
20261 )
20262 }
20263
20264 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20265 self.breadcrumb_header = Some(new_header);
20266 }
20267
20268 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20269 self.clear_background_highlights::<SearchWithinRange>(cx);
20270 }
20271
20272 pub fn highlight_background<T: 'static>(
20273 &mut self,
20274 ranges: &[Range<Anchor>],
20275 color_fetcher: fn(&Theme) -> Hsla,
20276 cx: &mut Context<Self>,
20277 ) {
20278 self.background_highlights.insert(
20279 HighlightKey::Type(TypeId::of::<T>()),
20280 (color_fetcher, Arc::from(ranges)),
20281 );
20282 self.scrollbar_marker_state.dirty = true;
20283 cx.notify();
20284 }
20285
20286 pub fn highlight_background_key<T: 'static>(
20287 &mut self,
20288 key: usize,
20289 ranges: &[Range<Anchor>],
20290 color_fetcher: fn(&Theme) -> Hsla,
20291 cx: &mut Context<Self>,
20292 ) {
20293 self.background_highlights.insert(
20294 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20295 (color_fetcher, Arc::from(ranges)),
20296 );
20297 self.scrollbar_marker_state.dirty = true;
20298 cx.notify();
20299 }
20300
20301 pub fn clear_background_highlights<T: 'static>(
20302 &mut self,
20303 cx: &mut Context<Self>,
20304 ) -> Option<BackgroundHighlight> {
20305 let text_highlights = self
20306 .background_highlights
20307 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20308 if !text_highlights.1.is_empty() {
20309 self.scrollbar_marker_state.dirty = true;
20310 cx.notify();
20311 }
20312 Some(text_highlights)
20313 }
20314
20315 pub fn highlight_gutter<T: 'static>(
20316 &mut self,
20317 ranges: impl Into<Vec<Range<Anchor>>>,
20318 color_fetcher: fn(&App) -> Hsla,
20319 cx: &mut Context<Self>,
20320 ) {
20321 self.gutter_highlights
20322 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20323 cx.notify();
20324 }
20325
20326 pub fn clear_gutter_highlights<T: 'static>(
20327 &mut self,
20328 cx: &mut Context<Self>,
20329 ) -> Option<GutterHighlight> {
20330 cx.notify();
20331 self.gutter_highlights.remove(&TypeId::of::<T>())
20332 }
20333
20334 pub fn insert_gutter_highlight<T: 'static>(
20335 &mut self,
20336 range: Range<Anchor>,
20337 color_fetcher: fn(&App) -> Hsla,
20338 cx: &mut Context<Self>,
20339 ) {
20340 let snapshot = self.buffer().read(cx).snapshot(cx);
20341 let mut highlights = self
20342 .gutter_highlights
20343 .remove(&TypeId::of::<T>())
20344 .map(|(_, highlights)| highlights)
20345 .unwrap_or_default();
20346 let ix = highlights.binary_search_by(|highlight| {
20347 Ordering::Equal
20348 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20349 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20350 });
20351 if let Err(ix) = ix {
20352 highlights.insert(ix, range);
20353 }
20354 self.gutter_highlights
20355 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20356 }
20357
20358 pub fn remove_gutter_highlights<T: 'static>(
20359 &mut self,
20360 ranges_to_remove: Vec<Range<Anchor>>,
20361 cx: &mut Context<Self>,
20362 ) {
20363 let snapshot = self.buffer().read(cx).snapshot(cx);
20364 let Some((color_fetcher, mut gutter_highlights)) =
20365 self.gutter_highlights.remove(&TypeId::of::<T>())
20366 else {
20367 return;
20368 };
20369 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20370 gutter_highlights.retain(|highlight| {
20371 while let Some(range_to_remove) = ranges_to_remove.peek() {
20372 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20373 Ordering::Less | Ordering::Equal => {
20374 ranges_to_remove.next();
20375 }
20376 Ordering::Greater => {
20377 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20378 Ordering::Less | Ordering::Equal => {
20379 return false;
20380 }
20381 Ordering::Greater => break,
20382 }
20383 }
20384 }
20385 }
20386
20387 true
20388 });
20389 self.gutter_highlights
20390 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20391 }
20392
20393 #[cfg(feature = "test-support")]
20394 pub fn all_text_highlights(
20395 &self,
20396 window: &mut Window,
20397 cx: &mut Context<Self>,
20398 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20399 let snapshot = self.snapshot(window, cx);
20400 self.display_map.update(cx, |display_map, _| {
20401 display_map
20402 .all_text_highlights()
20403 .map(|highlight| {
20404 let (style, ranges) = highlight.as_ref();
20405 (
20406 *style,
20407 ranges
20408 .iter()
20409 .map(|range| range.clone().to_display_points(&snapshot))
20410 .collect(),
20411 )
20412 })
20413 .collect()
20414 })
20415 }
20416
20417 #[cfg(feature = "test-support")]
20418 pub fn all_text_background_highlights(
20419 &self,
20420 window: &mut Window,
20421 cx: &mut Context<Self>,
20422 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20423 let snapshot = self.snapshot(window, cx);
20424 let buffer = &snapshot.buffer_snapshot();
20425 let start = buffer.anchor_before(0);
20426 let end = buffer.anchor_after(buffer.len());
20427 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20428 }
20429
20430 #[cfg(any(test, feature = "test-support"))]
20431 pub fn sorted_background_highlights_in_range(
20432 &self,
20433 search_range: Range<Anchor>,
20434 display_snapshot: &DisplaySnapshot,
20435 theme: &Theme,
20436 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20437 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20438 res.sort_by(|a, b| {
20439 a.0.start
20440 .cmp(&b.0.start)
20441 .then_with(|| a.0.end.cmp(&b.0.end))
20442 .then_with(|| a.1.cmp(&b.1))
20443 });
20444 res
20445 }
20446
20447 #[cfg(feature = "test-support")]
20448 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20449 let snapshot = self.buffer().read(cx).snapshot(cx);
20450
20451 let highlights = self
20452 .background_highlights
20453 .get(&HighlightKey::Type(TypeId::of::<
20454 items::BufferSearchHighlights,
20455 >()));
20456
20457 if let Some((_color, ranges)) = highlights {
20458 ranges
20459 .iter()
20460 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20461 .collect_vec()
20462 } else {
20463 vec![]
20464 }
20465 }
20466
20467 fn document_highlights_for_position<'a>(
20468 &'a self,
20469 position: Anchor,
20470 buffer: &'a MultiBufferSnapshot,
20471 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20472 let read_highlights = self
20473 .background_highlights
20474 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20475 .map(|h| &h.1);
20476 let write_highlights = self
20477 .background_highlights
20478 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20479 .map(|h| &h.1);
20480 let left_position = position.bias_left(buffer);
20481 let right_position = position.bias_right(buffer);
20482 read_highlights
20483 .into_iter()
20484 .chain(write_highlights)
20485 .flat_map(move |ranges| {
20486 let start_ix = match ranges.binary_search_by(|probe| {
20487 let cmp = probe.end.cmp(&left_position, buffer);
20488 if cmp.is_ge() {
20489 Ordering::Greater
20490 } else {
20491 Ordering::Less
20492 }
20493 }) {
20494 Ok(i) | Err(i) => i,
20495 };
20496
20497 ranges[start_ix..]
20498 .iter()
20499 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20500 })
20501 }
20502
20503 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20504 self.background_highlights
20505 .get(&HighlightKey::Type(TypeId::of::<T>()))
20506 .is_some_and(|(_, highlights)| !highlights.is_empty())
20507 }
20508
20509 /// Returns all background highlights for a given range.
20510 ///
20511 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20512 pub fn background_highlights_in_range(
20513 &self,
20514 search_range: Range<Anchor>,
20515 display_snapshot: &DisplaySnapshot,
20516 theme: &Theme,
20517 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20518 let mut results = Vec::new();
20519 for (color_fetcher, ranges) in self.background_highlights.values() {
20520 let color = color_fetcher(theme);
20521 let start_ix = match ranges.binary_search_by(|probe| {
20522 let cmp = probe
20523 .end
20524 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20525 if cmp.is_gt() {
20526 Ordering::Greater
20527 } else {
20528 Ordering::Less
20529 }
20530 }) {
20531 Ok(i) | Err(i) => i,
20532 };
20533 for range in &ranges[start_ix..] {
20534 if range
20535 .start
20536 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20537 .is_ge()
20538 {
20539 break;
20540 }
20541
20542 let start = range.start.to_display_point(display_snapshot);
20543 let end = range.end.to_display_point(display_snapshot);
20544 results.push((start..end, color))
20545 }
20546 }
20547 results
20548 }
20549
20550 pub fn gutter_highlights_in_range(
20551 &self,
20552 search_range: Range<Anchor>,
20553 display_snapshot: &DisplaySnapshot,
20554 cx: &App,
20555 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20556 let mut results = Vec::new();
20557 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20558 let color = color_fetcher(cx);
20559 let start_ix = match ranges.binary_search_by(|probe| {
20560 let cmp = probe
20561 .end
20562 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20563 if cmp.is_gt() {
20564 Ordering::Greater
20565 } else {
20566 Ordering::Less
20567 }
20568 }) {
20569 Ok(i) | Err(i) => i,
20570 };
20571 for range in &ranges[start_ix..] {
20572 if range
20573 .start
20574 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20575 .is_ge()
20576 {
20577 break;
20578 }
20579
20580 let start = range.start.to_display_point(display_snapshot);
20581 let end = range.end.to_display_point(display_snapshot);
20582 results.push((start..end, color))
20583 }
20584 }
20585 results
20586 }
20587
20588 /// Get the text ranges corresponding to the redaction query
20589 pub fn redacted_ranges(
20590 &self,
20591 search_range: Range<Anchor>,
20592 display_snapshot: &DisplaySnapshot,
20593 cx: &App,
20594 ) -> Vec<Range<DisplayPoint>> {
20595 display_snapshot
20596 .buffer_snapshot()
20597 .redacted_ranges(search_range, |file| {
20598 if let Some(file) = file {
20599 file.is_private()
20600 && EditorSettings::get(
20601 Some(SettingsLocation {
20602 worktree_id: file.worktree_id(cx),
20603 path: file.path().as_ref(),
20604 }),
20605 cx,
20606 )
20607 .redact_private_values
20608 } else {
20609 false
20610 }
20611 })
20612 .map(|range| {
20613 range.start.to_display_point(display_snapshot)
20614 ..range.end.to_display_point(display_snapshot)
20615 })
20616 .collect()
20617 }
20618
20619 pub fn highlight_text_key<T: 'static>(
20620 &mut self,
20621 key: usize,
20622 ranges: Vec<Range<Anchor>>,
20623 style: HighlightStyle,
20624 cx: &mut Context<Self>,
20625 ) {
20626 self.display_map.update(cx, |map, _| {
20627 map.highlight_text(
20628 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20629 ranges,
20630 style,
20631 );
20632 });
20633 cx.notify();
20634 }
20635
20636 pub fn highlight_text<T: 'static>(
20637 &mut self,
20638 ranges: Vec<Range<Anchor>>,
20639 style: HighlightStyle,
20640 cx: &mut Context<Self>,
20641 ) {
20642 self.display_map.update(cx, |map, _| {
20643 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20644 });
20645 cx.notify();
20646 }
20647
20648 pub(crate) fn highlight_inlays<T: 'static>(
20649 &mut self,
20650 highlights: Vec<InlayHighlight>,
20651 style: HighlightStyle,
20652 cx: &mut Context<Self>,
20653 ) {
20654 self.display_map.update(cx, |map, _| {
20655 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20656 });
20657 cx.notify();
20658 }
20659
20660 pub fn text_highlights<'a, T: 'static>(
20661 &'a self,
20662 cx: &'a App,
20663 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20664 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20665 }
20666
20667 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20668 let cleared = self
20669 .display_map
20670 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20671 if cleared {
20672 cx.notify();
20673 }
20674 }
20675
20676 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20677 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20678 && self.focus_handle.is_focused(window)
20679 }
20680
20681 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20682 self.show_cursor_when_unfocused = is_enabled;
20683 cx.notify();
20684 }
20685
20686 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20687 cx.notify();
20688 }
20689
20690 fn on_debug_session_event(
20691 &mut self,
20692 _session: Entity<Session>,
20693 event: &SessionEvent,
20694 cx: &mut Context<Self>,
20695 ) {
20696 if let SessionEvent::InvalidateInlineValue = event {
20697 self.refresh_inline_values(cx);
20698 }
20699 }
20700
20701 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20702 let Some(project) = self.project.clone() else {
20703 return;
20704 };
20705
20706 if !self.inline_value_cache.enabled {
20707 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20708 self.splice_inlays(&inlays, Vec::new(), cx);
20709 return;
20710 }
20711
20712 let current_execution_position = self
20713 .highlighted_rows
20714 .get(&TypeId::of::<ActiveDebugLine>())
20715 .and_then(|lines| lines.last().map(|line| line.range.end));
20716
20717 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20718 let inline_values = editor
20719 .update(cx, |editor, cx| {
20720 let Some(current_execution_position) = current_execution_position else {
20721 return Some(Task::ready(Ok(Vec::new())));
20722 };
20723
20724 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20725 let snapshot = buffer.snapshot(cx);
20726
20727 let excerpt = snapshot.excerpt_containing(
20728 current_execution_position..current_execution_position,
20729 )?;
20730
20731 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20732 })?;
20733
20734 let range =
20735 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20736
20737 project.inline_values(buffer, range, cx)
20738 })
20739 .ok()
20740 .flatten()?
20741 .await
20742 .context("refreshing debugger inlays")
20743 .log_err()?;
20744
20745 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20746
20747 for (buffer_id, inline_value) in inline_values
20748 .into_iter()
20749 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20750 {
20751 buffer_inline_values
20752 .entry(buffer_id)
20753 .or_default()
20754 .push(inline_value);
20755 }
20756
20757 editor
20758 .update(cx, |editor, cx| {
20759 let snapshot = editor.buffer.read(cx).snapshot(cx);
20760 let mut new_inlays = Vec::default();
20761
20762 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20763 let buffer_id = buffer_snapshot.remote_id();
20764 buffer_inline_values
20765 .get(&buffer_id)
20766 .into_iter()
20767 .flatten()
20768 .for_each(|hint| {
20769 let inlay = Inlay::debugger(
20770 post_inc(&mut editor.next_inlay_id),
20771 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20772 hint.text(),
20773 );
20774 if !inlay.text().chars().contains(&'\n') {
20775 new_inlays.push(inlay);
20776 }
20777 });
20778 }
20779
20780 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20781 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20782
20783 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20784 })
20785 .ok()?;
20786 Some(())
20787 });
20788 }
20789
20790 fn on_buffer_event(
20791 &mut self,
20792 multibuffer: &Entity<MultiBuffer>,
20793 event: &multi_buffer::Event,
20794 window: &mut Window,
20795 cx: &mut Context<Self>,
20796 ) {
20797 match event {
20798 multi_buffer::Event::Edited {
20799 singleton_buffer_edited,
20800 edited_buffer,
20801 } => {
20802 self.scrollbar_marker_state.dirty = true;
20803 self.active_indent_guides_state.dirty = true;
20804 self.refresh_active_diagnostics(cx);
20805 self.refresh_code_actions(window, cx);
20806 self.refresh_selected_text_highlights(true, window, cx);
20807 self.refresh_single_line_folds(window, cx);
20808 refresh_matching_bracket_highlights(self, window, cx);
20809 if self.has_active_edit_prediction() {
20810 self.update_visible_edit_prediction(window, cx);
20811 }
20812 if let Some(project) = self.project.as_ref()
20813 && let Some(edited_buffer) = edited_buffer
20814 {
20815 project.update(cx, |project, cx| {
20816 self.registered_buffers
20817 .entry(edited_buffer.read(cx).remote_id())
20818 .or_insert_with(|| {
20819 project.register_buffer_with_language_servers(edited_buffer, cx)
20820 });
20821 });
20822 }
20823 cx.emit(EditorEvent::BufferEdited);
20824 cx.emit(SearchEvent::MatchesInvalidated);
20825
20826 if let Some(buffer) = edited_buffer {
20827 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20828 }
20829
20830 if *singleton_buffer_edited {
20831 if let Some(buffer) = edited_buffer
20832 && buffer.read(cx).file().is_none()
20833 {
20834 cx.emit(EditorEvent::TitleChanged);
20835 }
20836 if let Some(project) = &self.project {
20837 #[allow(clippy::mutable_key_type)]
20838 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20839 multibuffer
20840 .all_buffers()
20841 .into_iter()
20842 .filter_map(|buffer| {
20843 buffer.update(cx, |buffer, cx| {
20844 let language = buffer.language()?;
20845 let should_discard = project.update(cx, |project, cx| {
20846 project.is_local()
20847 && !project.has_language_servers_for(buffer, cx)
20848 });
20849 should_discard.not().then_some(language.clone())
20850 })
20851 })
20852 .collect::<HashSet<_>>()
20853 });
20854 if !languages_affected.is_empty() {
20855 self.refresh_inlay_hints(
20856 InlayHintRefreshReason::BufferEdited(languages_affected),
20857 cx,
20858 );
20859 }
20860 }
20861 }
20862
20863 let Some(project) = &self.project else { return };
20864 let (telemetry, is_via_ssh) = {
20865 let project = project.read(cx);
20866 let telemetry = project.client().telemetry().clone();
20867 let is_via_ssh = project.is_via_remote_server();
20868 (telemetry, is_via_ssh)
20869 };
20870 refresh_linked_ranges(self, window, cx);
20871 telemetry.log_edit_event("editor", is_via_ssh);
20872 }
20873 multi_buffer::Event::ExcerptsAdded {
20874 buffer,
20875 predecessor,
20876 excerpts,
20877 } => {
20878 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20879 let buffer_id = buffer.read(cx).remote_id();
20880 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20881 && let Some(project) = &self.project
20882 {
20883 update_uncommitted_diff_for_buffer(
20884 cx.entity(),
20885 project,
20886 [buffer.clone()],
20887 self.buffer.clone(),
20888 cx,
20889 )
20890 .detach();
20891 }
20892 if self.active_diagnostics != ActiveDiagnostic::All {
20893 self.update_lsp_data(false, Some(buffer_id), window, cx);
20894 }
20895 cx.emit(EditorEvent::ExcerptsAdded {
20896 buffer: buffer.clone(),
20897 predecessor: *predecessor,
20898 excerpts: excerpts.clone(),
20899 });
20900 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20901 }
20902 multi_buffer::Event::ExcerptsRemoved {
20903 ids,
20904 removed_buffer_ids,
20905 } => {
20906 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20907 let buffer = self.buffer.read(cx);
20908 self.registered_buffers
20909 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20910 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20911 cx.emit(EditorEvent::ExcerptsRemoved {
20912 ids: ids.clone(),
20913 removed_buffer_ids: removed_buffer_ids.clone(),
20914 });
20915 }
20916 multi_buffer::Event::ExcerptsEdited {
20917 excerpt_ids,
20918 buffer_ids,
20919 } => {
20920 self.display_map.update(cx, |map, cx| {
20921 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20922 });
20923 cx.emit(EditorEvent::ExcerptsEdited {
20924 ids: excerpt_ids.clone(),
20925 });
20926 }
20927 multi_buffer::Event::ExcerptsExpanded { ids } => {
20928 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20929 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20930 }
20931 multi_buffer::Event::Reparsed(buffer_id) => {
20932 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20933 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20934
20935 cx.emit(EditorEvent::Reparsed(*buffer_id));
20936 }
20937 multi_buffer::Event::DiffHunksToggled => {
20938 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20939 }
20940 multi_buffer::Event::LanguageChanged(buffer_id) => {
20941 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20942 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20943 cx.emit(EditorEvent::Reparsed(*buffer_id));
20944 cx.notify();
20945 }
20946 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20947 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20948 multi_buffer::Event::FileHandleChanged
20949 | multi_buffer::Event::Reloaded
20950 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20951 multi_buffer::Event::DiagnosticsUpdated => {
20952 self.update_diagnostics_state(window, cx);
20953 }
20954 _ => {}
20955 };
20956 }
20957
20958 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20959 if !self.diagnostics_enabled() {
20960 return;
20961 }
20962 self.refresh_active_diagnostics(cx);
20963 self.refresh_inline_diagnostics(true, window, cx);
20964 self.scrollbar_marker_state.dirty = true;
20965 cx.notify();
20966 }
20967
20968 pub fn start_temporary_diff_override(&mut self) {
20969 self.load_diff_task.take();
20970 self.temporary_diff_override = true;
20971 }
20972
20973 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20974 self.temporary_diff_override = false;
20975 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20976 self.buffer.update(cx, |buffer, cx| {
20977 buffer.set_all_diff_hunks_collapsed(cx);
20978 });
20979
20980 if let Some(project) = self.project.clone() {
20981 self.load_diff_task = Some(
20982 update_uncommitted_diff_for_buffer(
20983 cx.entity(),
20984 &project,
20985 self.buffer.read(cx).all_buffers(),
20986 self.buffer.clone(),
20987 cx,
20988 )
20989 .shared(),
20990 );
20991 }
20992 }
20993
20994 fn on_display_map_changed(
20995 &mut self,
20996 _: Entity<DisplayMap>,
20997 _: &mut Window,
20998 cx: &mut Context<Self>,
20999 ) {
21000 cx.notify();
21001 }
21002
21003 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21004 if self.diagnostics_enabled() {
21005 let new_severity = EditorSettings::get_global(cx)
21006 .diagnostics_max_severity
21007 .unwrap_or(DiagnosticSeverity::Hint);
21008 self.set_max_diagnostics_severity(new_severity, cx);
21009 }
21010 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21011 self.update_edit_prediction_settings(cx);
21012 self.refresh_edit_prediction(true, false, window, cx);
21013 self.refresh_inline_values(cx);
21014 self.refresh_inlay_hints(
21015 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21016 self.selections.newest_anchor().head(),
21017 &self.buffer.read(cx).snapshot(cx),
21018 cx,
21019 )),
21020 cx,
21021 );
21022
21023 let old_cursor_shape = self.cursor_shape;
21024 let old_show_breadcrumbs = self.show_breadcrumbs;
21025
21026 {
21027 let editor_settings = EditorSettings::get_global(cx);
21028 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21029 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21030 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21031 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21032 }
21033
21034 if old_cursor_shape != self.cursor_shape {
21035 cx.emit(EditorEvent::CursorShapeChanged);
21036 }
21037
21038 if old_show_breadcrumbs != self.show_breadcrumbs {
21039 cx.emit(EditorEvent::BreadcrumbsChanged);
21040 }
21041
21042 let project_settings = ProjectSettings::get_global(cx);
21043 self.serialize_dirty_buffers =
21044 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21045
21046 if self.mode.is_full() {
21047 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21048 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21049 if self.show_inline_diagnostics != show_inline_diagnostics {
21050 self.show_inline_diagnostics = show_inline_diagnostics;
21051 self.refresh_inline_diagnostics(false, window, cx);
21052 }
21053
21054 if self.git_blame_inline_enabled != inline_blame_enabled {
21055 self.toggle_git_blame_inline_internal(false, window, cx);
21056 }
21057
21058 let minimap_settings = EditorSettings::get_global(cx).minimap;
21059 if self.minimap_visibility != MinimapVisibility::Disabled {
21060 if self.minimap_visibility.settings_visibility()
21061 != minimap_settings.minimap_enabled()
21062 {
21063 self.set_minimap_visibility(
21064 MinimapVisibility::for_mode(self.mode(), cx),
21065 window,
21066 cx,
21067 );
21068 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21069 minimap_entity.update(cx, |minimap_editor, cx| {
21070 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21071 })
21072 }
21073 }
21074 }
21075
21076 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21077 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21078 }) {
21079 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
21080 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21081 }
21082 self.refresh_colors(false, None, window, cx);
21083 }
21084
21085 cx.notify();
21086 }
21087
21088 pub fn set_searchable(&mut self, searchable: bool) {
21089 self.searchable = searchable;
21090 }
21091
21092 pub fn searchable(&self) -> bool {
21093 self.searchable
21094 }
21095
21096 fn open_proposed_changes_editor(
21097 &mut self,
21098 _: &OpenProposedChangesEditor,
21099 window: &mut Window,
21100 cx: &mut Context<Self>,
21101 ) {
21102 let Some(workspace) = self.workspace() else {
21103 cx.propagate();
21104 return;
21105 };
21106
21107 let selections = self.selections.all::<usize>(cx);
21108 let multi_buffer = self.buffer.read(cx);
21109 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21110 let mut new_selections_by_buffer = HashMap::default();
21111 for selection in selections {
21112 for (buffer, range, _) in
21113 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21114 {
21115 let mut range = range.to_point(buffer);
21116 range.start.column = 0;
21117 range.end.column = buffer.line_len(range.end.row);
21118 new_selections_by_buffer
21119 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21120 .or_insert(Vec::new())
21121 .push(range)
21122 }
21123 }
21124
21125 let proposed_changes_buffers = new_selections_by_buffer
21126 .into_iter()
21127 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21128 .collect::<Vec<_>>();
21129 let proposed_changes_editor = cx.new(|cx| {
21130 ProposedChangesEditor::new(
21131 "Proposed changes",
21132 proposed_changes_buffers,
21133 self.project.clone(),
21134 window,
21135 cx,
21136 )
21137 });
21138
21139 window.defer(cx, move |window, cx| {
21140 workspace.update(cx, |workspace, cx| {
21141 workspace.active_pane().update(cx, |pane, cx| {
21142 pane.add_item(
21143 Box::new(proposed_changes_editor),
21144 true,
21145 true,
21146 None,
21147 window,
21148 cx,
21149 );
21150 });
21151 });
21152 });
21153 }
21154
21155 pub fn open_excerpts_in_split(
21156 &mut self,
21157 _: &OpenExcerptsSplit,
21158 window: &mut Window,
21159 cx: &mut Context<Self>,
21160 ) {
21161 self.open_excerpts_common(None, true, window, cx)
21162 }
21163
21164 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21165 self.open_excerpts_common(None, false, window, cx)
21166 }
21167
21168 fn open_excerpts_common(
21169 &mut self,
21170 jump_data: Option<JumpData>,
21171 split: bool,
21172 window: &mut Window,
21173 cx: &mut Context<Self>,
21174 ) {
21175 let Some(workspace) = self.workspace() else {
21176 cx.propagate();
21177 return;
21178 };
21179
21180 if self.buffer.read(cx).is_singleton() {
21181 cx.propagate();
21182 return;
21183 }
21184
21185 let mut new_selections_by_buffer = HashMap::default();
21186 match &jump_data {
21187 Some(JumpData::MultiBufferPoint {
21188 excerpt_id,
21189 position,
21190 anchor,
21191 line_offset_from_top,
21192 }) => {
21193 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21194 if let Some(buffer) = multi_buffer_snapshot
21195 .buffer_id_for_excerpt(*excerpt_id)
21196 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21197 {
21198 let buffer_snapshot = buffer.read(cx).snapshot();
21199 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21200 language::ToPoint::to_point(anchor, &buffer_snapshot)
21201 } else {
21202 buffer_snapshot.clip_point(*position, Bias::Left)
21203 };
21204 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21205 new_selections_by_buffer.insert(
21206 buffer,
21207 (
21208 vec![jump_to_offset..jump_to_offset],
21209 Some(*line_offset_from_top),
21210 ),
21211 );
21212 }
21213 }
21214 Some(JumpData::MultiBufferRow {
21215 row,
21216 line_offset_from_top,
21217 }) => {
21218 let point = MultiBufferPoint::new(row.0, 0);
21219 if let Some((buffer, buffer_point, _)) =
21220 self.buffer.read(cx).point_to_buffer_point(point, cx)
21221 {
21222 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21223 new_selections_by_buffer
21224 .entry(buffer)
21225 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21226 .0
21227 .push(buffer_offset..buffer_offset)
21228 }
21229 }
21230 None => {
21231 let selections = self.selections.all::<usize>(cx);
21232 let multi_buffer = self.buffer.read(cx);
21233 for selection in selections {
21234 for (snapshot, range, _, anchor) in multi_buffer
21235 .snapshot(cx)
21236 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21237 {
21238 if let Some(anchor) = anchor {
21239 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21240 else {
21241 continue;
21242 };
21243 let offset = text::ToOffset::to_offset(
21244 &anchor.text_anchor,
21245 &buffer_handle.read(cx).snapshot(),
21246 );
21247 let range = offset..offset;
21248 new_selections_by_buffer
21249 .entry(buffer_handle)
21250 .or_insert((Vec::new(), None))
21251 .0
21252 .push(range)
21253 } else {
21254 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21255 else {
21256 continue;
21257 };
21258 new_selections_by_buffer
21259 .entry(buffer_handle)
21260 .or_insert((Vec::new(), None))
21261 .0
21262 .push(range)
21263 }
21264 }
21265 }
21266 }
21267 }
21268
21269 new_selections_by_buffer
21270 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21271
21272 if new_selections_by_buffer.is_empty() {
21273 return;
21274 }
21275
21276 // We defer the pane interaction because we ourselves are a workspace item
21277 // and activating a new item causes the pane to call a method on us reentrantly,
21278 // which panics if we're on the stack.
21279 window.defer(cx, move |window, cx| {
21280 workspace.update(cx, |workspace, cx| {
21281 let pane = if split {
21282 workspace.adjacent_pane(window, cx)
21283 } else {
21284 workspace.active_pane().clone()
21285 };
21286
21287 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21288 let editor = buffer
21289 .read(cx)
21290 .file()
21291 .is_none()
21292 .then(|| {
21293 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21294 // so `workspace.open_project_item` will never find them, always opening a new editor.
21295 // Instead, we try to activate the existing editor in the pane first.
21296 let (editor, pane_item_index) =
21297 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21298 let editor = item.downcast::<Editor>()?;
21299 let singleton_buffer =
21300 editor.read(cx).buffer().read(cx).as_singleton()?;
21301 if singleton_buffer == buffer {
21302 Some((editor, i))
21303 } else {
21304 None
21305 }
21306 })?;
21307 pane.update(cx, |pane, cx| {
21308 pane.activate_item(pane_item_index, true, true, window, cx)
21309 });
21310 Some(editor)
21311 })
21312 .flatten()
21313 .unwrap_or_else(|| {
21314 workspace.open_project_item::<Self>(
21315 pane.clone(),
21316 buffer,
21317 true,
21318 true,
21319 window,
21320 cx,
21321 )
21322 });
21323
21324 editor.update(cx, |editor, cx| {
21325 let autoscroll = match scroll_offset {
21326 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21327 None => Autoscroll::newest(),
21328 };
21329 let nav_history = editor.nav_history.take();
21330 editor.change_selections(
21331 SelectionEffects::scroll(autoscroll),
21332 window,
21333 cx,
21334 |s| {
21335 s.select_ranges(ranges);
21336 },
21337 );
21338 editor.nav_history = nav_history;
21339 });
21340 }
21341 })
21342 });
21343 }
21344
21345 // For now, don't allow opening excerpts in buffers that aren't backed by
21346 // regular project files.
21347 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21348 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21349 }
21350
21351 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21352 let snapshot = self.buffer.read(cx).read(cx);
21353 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21354 Some(
21355 ranges
21356 .iter()
21357 .map(move |range| {
21358 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21359 })
21360 .collect(),
21361 )
21362 }
21363
21364 fn selection_replacement_ranges(
21365 &self,
21366 range: Range<OffsetUtf16>,
21367 cx: &mut App,
21368 ) -> Vec<Range<OffsetUtf16>> {
21369 let selections = self.selections.all::<OffsetUtf16>(cx);
21370 let newest_selection = selections
21371 .iter()
21372 .max_by_key(|selection| selection.id)
21373 .unwrap();
21374 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21375 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21376 let snapshot = self.buffer.read(cx).read(cx);
21377 selections
21378 .into_iter()
21379 .map(|mut selection| {
21380 selection.start.0 =
21381 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21382 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21383 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21384 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21385 })
21386 .collect()
21387 }
21388
21389 fn report_editor_event(
21390 &self,
21391 reported_event: ReportEditorEvent,
21392 file_extension: Option<String>,
21393 cx: &App,
21394 ) {
21395 if cfg!(any(test, feature = "test-support")) {
21396 return;
21397 }
21398
21399 let Some(project) = &self.project else { return };
21400
21401 // If None, we are in a file without an extension
21402 let file = self
21403 .buffer
21404 .read(cx)
21405 .as_singleton()
21406 .and_then(|b| b.read(cx).file());
21407 let file_extension = file_extension.or(file
21408 .as_ref()
21409 .and_then(|file| Path::new(file.file_name(cx)).extension())
21410 .and_then(|e| e.to_str())
21411 .map(|a| a.to_string()));
21412
21413 let vim_mode = vim_enabled(cx);
21414
21415 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21416 let copilot_enabled = edit_predictions_provider
21417 == language::language_settings::EditPredictionProvider::Copilot;
21418 let copilot_enabled_for_language = self
21419 .buffer
21420 .read(cx)
21421 .language_settings(cx)
21422 .show_edit_predictions;
21423
21424 let project = project.read(cx);
21425 let event_type = reported_event.event_type();
21426
21427 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21428 telemetry::event!(
21429 event_type,
21430 type = if auto_saved {"autosave"} else {"manual"},
21431 file_extension,
21432 vim_mode,
21433 copilot_enabled,
21434 copilot_enabled_for_language,
21435 edit_predictions_provider,
21436 is_via_ssh = project.is_via_remote_server(),
21437 );
21438 } else {
21439 telemetry::event!(
21440 event_type,
21441 file_extension,
21442 vim_mode,
21443 copilot_enabled,
21444 copilot_enabled_for_language,
21445 edit_predictions_provider,
21446 is_via_ssh = project.is_via_remote_server(),
21447 );
21448 };
21449 }
21450
21451 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21452 /// with each line being an array of {text, highlight} objects.
21453 fn copy_highlight_json(
21454 &mut self,
21455 _: &CopyHighlightJson,
21456 window: &mut Window,
21457 cx: &mut Context<Self>,
21458 ) {
21459 #[derive(Serialize)]
21460 struct Chunk<'a> {
21461 text: String,
21462 highlight: Option<&'a str>,
21463 }
21464
21465 let snapshot = self.buffer.read(cx).snapshot(cx);
21466 let range = self
21467 .selected_text_range(false, window, cx)
21468 .and_then(|selection| {
21469 if selection.range.is_empty() {
21470 None
21471 } else {
21472 Some(selection.range)
21473 }
21474 })
21475 .unwrap_or_else(|| 0..snapshot.len());
21476
21477 let chunks = snapshot.chunks(range, true);
21478 let mut lines = Vec::new();
21479 let mut line: VecDeque<Chunk> = VecDeque::new();
21480
21481 let Some(style) = self.style.as_ref() else {
21482 return;
21483 };
21484
21485 for chunk in chunks {
21486 let highlight = chunk
21487 .syntax_highlight_id
21488 .and_then(|id| id.name(&style.syntax));
21489 let mut chunk_lines = chunk.text.split('\n').peekable();
21490 while let Some(text) = chunk_lines.next() {
21491 let mut merged_with_last_token = false;
21492 if let Some(last_token) = line.back_mut()
21493 && last_token.highlight == highlight
21494 {
21495 last_token.text.push_str(text);
21496 merged_with_last_token = true;
21497 }
21498
21499 if !merged_with_last_token {
21500 line.push_back(Chunk {
21501 text: text.into(),
21502 highlight,
21503 });
21504 }
21505
21506 if chunk_lines.peek().is_some() {
21507 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21508 line.pop_front();
21509 }
21510 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21511 line.pop_back();
21512 }
21513
21514 lines.push(mem::take(&mut line));
21515 }
21516 }
21517 }
21518
21519 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21520 return;
21521 };
21522 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21523 }
21524
21525 pub fn open_context_menu(
21526 &mut self,
21527 _: &OpenContextMenu,
21528 window: &mut Window,
21529 cx: &mut Context<Self>,
21530 ) {
21531 self.request_autoscroll(Autoscroll::newest(), cx);
21532 let position = self.selections.newest_display(cx).start;
21533 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21534 }
21535
21536 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21537 &self.inlay_hint_cache
21538 }
21539
21540 pub fn replay_insert_event(
21541 &mut self,
21542 text: &str,
21543 relative_utf16_range: Option<Range<isize>>,
21544 window: &mut Window,
21545 cx: &mut Context<Self>,
21546 ) {
21547 if !self.input_enabled {
21548 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21549 return;
21550 }
21551 if let Some(relative_utf16_range) = relative_utf16_range {
21552 let selections = self.selections.all::<OffsetUtf16>(cx);
21553 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21554 let new_ranges = selections.into_iter().map(|range| {
21555 let start = OffsetUtf16(
21556 range
21557 .head()
21558 .0
21559 .saturating_add_signed(relative_utf16_range.start),
21560 );
21561 let end = OffsetUtf16(
21562 range
21563 .head()
21564 .0
21565 .saturating_add_signed(relative_utf16_range.end),
21566 );
21567 start..end
21568 });
21569 s.select_ranges(new_ranges);
21570 });
21571 }
21572
21573 self.handle_input(text, window, cx);
21574 }
21575
21576 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21577 let Some(provider) = self.semantics_provider.as_ref() else {
21578 return false;
21579 };
21580
21581 let mut supports = false;
21582 self.buffer().update(cx, |this, cx| {
21583 this.for_each_buffer(|buffer| {
21584 supports |= provider.supports_inlay_hints(buffer, cx);
21585 });
21586 });
21587
21588 supports
21589 }
21590
21591 pub fn is_focused(&self, window: &Window) -> bool {
21592 self.focus_handle.is_focused(window)
21593 }
21594
21595 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21596 cx.emit(EditorEvent::Focused);
21597
21598 if let Some(descendant) = self
21599 .last_focused_descendant
21600 .take()
21601 .and_then(|descendant| descendant.upgrade())
21602 {
21603 window.focus(&descendant);
21604 } else {
21605 if let Some(blame) = self.blame.as_ref() {
21606 blame.update(cx, GitBlame::focus)
21607 }
21608
21609 self.blink_manager.update(cx, BlinkManager::enable);
21610 self.show_cursor_names(window, cx);
21611 self.buffer.update(cx, |buffer, cx| {
21612 buffer.finalize_last_transaction(cx);
21613 if self.leader_id.is_none() {
21614 buffer.set_active_selections(
21615 &self.selections.disjoint_anchors_arc(),
21616 self.selections.line_mode(),
21617 self.cursor_shape,
21618 cx,
21619 );
21620 }
21621 });
21622 }
21623 }
21624
21625 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21626 cx.emit(EditorEvent::FocusedIn)
21627 }
21628
21629 fn handle_focus_out(
21630 &mut self,
21631 event: FocusOutEvent,
21632 _window: &mut Window,
21633 cx: &mut Context<Self>,
21634 ) {
21635 if event.blurred != self.focus_handle {
21636 self.last_focused_descendant = Some(event.blurred);
21637 }
21638 self.selection_drag_state = SelectionDragState::None;
21639 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21640 }
21641
21642 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21643 self.blink_manager.update(cx, BlinkManager::disable);
21644 self.buffer
21645 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21646
21647 if let Some(blame) = self.blame.as_ref() {
21648 blame.update(cx, GitBlame::blur)
21649 }
21650 if !self.hover_state.focused(window, cx) {
21651 hide_hover(self, cx);
21652 }
21653 if !self
21654 .context_menu
21655 .borrow()
21656 .as_ref()
21657 .is_some_and(|context_menu| context_menu.focused(window, cx))
21658 {
21659 self.hide_context_menu(window, cx);
21660 }
21661 self.take_active_edit_prediction(cx);
21662 cx.emit(EditorEvent::Blurred);
21663 cx.notify();
21664 }
21665
21666 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21667 let mut pending: String = window
21668 .pending_input_keystrokes()
21669 .into_iter()
21670 .flatten()
21671 .filter_map(|keystroke| {
21672 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21673 keystroke.key_char.clone()
21674 } else {
21675 None
21676 }
21677 })
21678 .collect();
21679
21680 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21681 pending = "".to_string();
21682 }
21683
21684 let existing_pending = self
21685 .text_highlights::<PendingInput>(cx)
21686 .map(|(_, ranges)| ranges.to_vec());
21687 if existing_pending.is_none() && pending.is_empty() {
21688 return;
21689 }
21690 let transaction =
21691 self.transact(window, cx, |this, window, cx| {
21692 let selections = this.selections.all::<usize>(cx);
21693 let edits = selections
21694 .iter()
21695 .map(|selection| (selection.end..selection.end, pending.clone()));
21696 this.edit(edits, cx);
21697 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21698 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21699 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21700 }));
21701 });
21702 if let Some(existing_ranges) = existing_pending {
21703 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21704 this.edit(edits, cx);
21705 }
21706 });
21707
21708 let snapshot = self.snapshot(window, cx);
21709 let ranges = self
21710 .selections
21711 .all::<usize>(cx)
21712 .into_iter()
21713 .map(|selection| {
21714 snapshot.buffer_snapshot().anchor_after(selection.end)
21715 ..snapshot
21716 .buffer_snapshot()
21717 .anchor_before(selection.end + pending.len())
21718 })
21719 .collect();
21720
21721 if pending.is_empty() {
21722 self.clear_highlights::<PendingInput>(cx);
21723 } else {
21724 self.highlight_text::<PendingInput>(
21725 ranges,
21726 HighlightStyle {
21727 underline: Some(UnderlineStyle {
21728 thickness: px(1.),
21729 color: None,
21730 wavy: false,
21731 }),
21732 ..Default::default()
21733 },
21734 cx,
21735 );
21736 }
21737
21738 self.ime_transaction = self.ime_transaction.or(transaction);
21739 if let Some(transaction) = self.ime_transaction {
21740 self.buffer.update(cx, |buffer, cx| {
21741 buffer.group_until_transaction(transaction, cx);
21742 });
21743 }
21744
21745 if self.text_highlights::<PendingInput>(cx).is_none() {
21746 self.ime_transaction.take();
21747 }
21748 }
21749
21750 pub fn register_action_renderer(
21751 &mut self,
21752 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21753 ) -> Subscription {
21754 let id = self.next_editor_action_id.post_inc();
21755 self.editor_actions
21756 .borrow_mut()
21757 .insert(id, Box::new(listener));
21758
21759 let editor_actions = self.editor_actions.clone();
21760 Subscription::new(move || {
21761 editor_actions.borrow_mut().remove(&id);
21762 })
21763 }
21764
21765 pub fn register_action<A: Action>(
21766 &mut self,
21767 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21768 ) -> Subscription {
21769 let id = self.next_editor_action_id.post_inc();
21770 let listener = Arc::new(listener);
21771 self.editor_actions.borrow_mut().insert(
21772 id,
21773 Box::new(move |_, window, _| {
21774 let listener = listener.clone();
21775 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21776 let action = action.downcast_ref().unwrap();
21777 if phase == DispatchPhase::Bubble {
21778 listener(action, window, cx)
21779 }
21780 })
21781 }),
21782 );
21783
21784 let editor_actions = self.editor_actions.clone();
21785 Subscription::new(move || {
21786 editor_actions.borrow_mut().remove(&id);
21787 })
21788 }
21789
21790 pub fn file_header_size(&self) -> u32 {
21791 FILE_HEADER_HEIGHT
21792 }
21793
21794 pub fn restore(
21795 &mut self,
21796 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21797 window: &mut Window,
21798 cx: &mut Context<Self>,
21799 ) {
21800 let workspace = self.workspace();
21801 let project = self.project();
21802 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21803 let mut tasks = Vec::new();
21804 for (buffer_id, changes) in revert_changes {
21805 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21806 buffer.update(cx, |buffer, cx| {
21807 buffer.edit(
21808 changes
21809 .into_iter()
21810 .map(|(range, text)| (range, text.to_string())),
21811 None,
21812 cx,
21813 );
21814 });
21815
21816 if let Some(project) =
21817 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21818 {
21819 project.update(cx, |project, cx| {
21820 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21821 })
21822 }
21823 }
21824 }
21825 tasks
21826 });
21827 cx.spawn_in(window, async move |_, cx| {
21828 for (buffer, task) in save_tasks {
21829 let result = task.await;
21830 if result.is_err() {
21831 let Some(path) = buffer
21832 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21833 .ok()
21834 else {
21835 continue;
21836 };
21837 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21838 let Some(task) = cx
21839 .update_window_entity(workspace, |workspace, window, cx| {
21840 workspace
21841 .open_path_preview(path, None, false, false, false, window, cx)
21842 })
21843 .ok()
21844 else {
21845 continue;
21846 };
21847 task.await.log_err();
21848 }
21849 }
21850 }
21851 })
21852 .detach();
21853 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21854 selections.refresh()
21855 });
21856 }
21857
21858 pub fn to_pixel_point(
21859 &self,
21860 source: multi_buffer::Anchor,
21861 editor_snapshot: &EditorSnapshot,
21862 window: &mut Window,
21863 ) -> Option<gpui::Point<Pixels>> {
21864 let source_point = source.to_display_point(editor_snapshot);
21865 self.display_to_pixel_point(source_point, editor_snapshot, window)
21866 }
21867
21868 pub fn display_to_pixel_point(
21869 &self,
21870 source: DisplayPoint,
21871 editor_snapshot: &EditorSnapshot,
21872 window: &mut Window,
21873 ) -> Option<gpui::Point<Pixels>> {
21874 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21875 let text_layout_details = self.text_layout_details(window);
21876 let scroll_top = text_layout_details
21877 .scroll_anchor
21878 .scroll_position(editor_snapshot)
21879 .y;
21880
21881 if source.row().as_f64() < scroll_top.floor() {
21882 return None;
21883 }
21884 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21885 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21886 Some(gpui::Point::new(source_x, source_y))
21887 }
21888
21889 pub fn has_visible_completions_menu(&self) -> bool {
21890 !self.edit_prediction_preview_is_active()
21891 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21892 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21893 })
21894 }
21895
21896 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21897 if self.mode.is_minimap() {
21898 return;
21899 }
21900 self.addons
21901 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21902 }
21903
21904 pub fn unregister_addon<T: Addon>(&mut self) {
21905 self.addons.remove(&std::any::TypeId::of::<T>());
21906 }
21907
21908 pub fn addon<T: Addon>(&self) -> Option<&T> {
21909 let type_id = std::any::TypeId::of::<T>();
21910 self.addons
21911 .get(&type_id)
21912 .and_then(|item| item.to_any().downcast_ref::<T>())
21913 }
21914
21915 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21916 let type_id = std::any::TypeId::of::<T>();
21917 self.addons
21918 .get_mut(&type_id)
21919 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21920 }
21921
21922 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21923 let text_layout_details = self.text_layout_details(window);
21924 let style = &text_layout_details.editor_style;
21925 let font_id = window.text_system().resolve_font(&style.text.font());
21926 let font_size = style.text.font_size.to_pixels(window.rem_size());
21927 let line_height = style.text.line_height_in_pixels(window.rem_size());
21928 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21929 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21930
21931 CharacterDimensions {
21932 em_width,
21933 em_advance,
21934 line_height,
21935 }
21936 }
21937
21938 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21939 self.load_diff_task.clone()
21940 }
21941
21942 fn read_metadata_from_db(
21943 &mut self,
21944 item_id: u64,
21945 workspace_id: WorkspaceId,
21946 window: &mut Window,
21947 cx: &mut Context<Editor>,
21948 ) {
21949 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21950 && !self.mode.is_minimap()
21951 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21952 {
21953 let buffer_snapshot = OnceCell::new();
21954
21955 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21956 && !folds.is_empty()
21957 {
21958 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21959 self.fold_ranges(
21960 folds
21961 .into_iter()
21962 .map(|(start, end)| {
21963 snapshot.clip_offset(start, Bias::Left)
21964 ..snapshot.clip_offset(end, Bias::Right)
21965 })
21966 .collect(),
21967 false,
21968 window,
21969 cx,
21970 );
21971 }
21972
21973 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21974 && !selections.is_empty()
21975 {
21976 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21977 // skip adding the initial selection to selection history
21978 self.selection_history.mode = SelectionHistoryMode::Skipping;
21979 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21980 s.select_ranges(selections.into_iter().map(|(start, end)| {
21981 snapshot.clip_offset(start, Bias::Left)
21982 ..snapshot.clip_offset(end, Bias::Right)
21983 }));
21984 });
21985 self.selection_history.mode = SelectionHistoryMode::Normal;
21986 };
21987 }
21988
21989 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21990 }
21991
21992 fn update_lsp_data(
21993 &mut self,
21994 ignore_cache: bool,
21995 for_buffer: Option<BufferId>,
21996 window: &mut Window,
21997 cx: &mut Context<'_, Self>,
21998 ) {
21999 self.pull_diagnostics(for_buffer, window, cx);
22000 self.refresh_colors(ignore_cache, for_buffer, window, cx);
22001 }
22002}
22003
22004fn edit_for_markdown_paste<'a>(
22005 buffer: &MultiBufferSnapshot,
22006 range: Range<usize>,
22007 to_insert: &'a str,
22008 url: Option<url::Url>,
22009) -> (Range<usize>, Cow<'a, str>) {
22010 if url.is_none() {
22011 return (range, Cow::Borrowed(to_insert));
22012 };
22013
22014 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22015
22016 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22017 Cow::Borrowed(to_insert)
22018 } else {
22019 Cow::Owned(format!("[{old_text}]({to_insert})"))
22020 };
22021 (range, new_text)
22022}
22023
22024fn vim_enabled(cx: &App) -> bool {
22025 vim_mode_setting::VimModeSetting::try_get(cx)
22026 .map(|vim_mode| vim_mode.0)
22027 .unwrap_or(false)
22028}
22029
22030fn process_completion_for_edit(
22031 completion: &Completion,
22032 intent: CompletionIntent,
22033 buffer: &Entity<Buffer>,
22034 cursor_position: &text::Anchor,
22035 cx: &mut Context<Editor>,
22036) -> CompletionEdit {
22037 let buffer = buffer.read(cx);
22038 let buffer_snapshot = buffer.snapshot();
22039 let (snippet, new_text) = if completion.is_snippet() {
22040 let mut snippet_source = completion.new_text.clone();
22041 // Workaround for typescript language server issues so that methods don't expand within
22042 // strings and functions with type expressions. The previous point is used because the query
22043 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22044 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22045 let previous_point = if previous_point.column > 0 {
22046 cursor_position.to_previous_offset(&buffer_snapshot)
22047 } else {
22048 cursor_position.to_offset(&buffer_snapshot)
22049 };
22050 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22051 && scope.prefers_label_for_snippet_in_completion()
22052 && let Some(label) = completion.label()
22053 && matches!(
22054 completion.kind(),
22055 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22056 )
22057 {
22058 snippet_source = label;
22059 }
22060 match Snippet::parse(&snippet_source).log_err() {
22061 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22062 None => (None, completion.new_text.clone()),
22063 }
22064 } else {
22065 (None, completion.new_text.clone())
22066 };
22067
22068 let mut range_to_replace = {
22069 let replace_range = &completion.replace_range;
22070 if let CompletionSource::Lsp {
22071 insert_range: Some(insert_range),
22072 ..
22073 } = &completion.source
22074 {
22075 debug_assert_eq!(
22076 insert_range.start, replace_range.start,
22077 "insert_range and replace_range should start at the same position"
22078 );
22079 debug_assert!(
22080 insert_range
22081 .start
22082 .cmp(cursor_position, &buffer_snapshot)
22083 .is_le(),
22084 "insert_range should start before or at cursor position"
22085 );
22086 debug_assert!(
22087 replace_range
22088 .start
22089 .cmp(cursor_position, &buffer_snapshot)
22090 .is_le(),
22091 "replace_range should start before or at cursor position"
22092 );
22093
22094 let should_replace = match intent {
22095 CompletionIntent::CompleteWithInsert => false,
22096 CompletionIntent::CompleteWithReplace => true,
22097 CompletionIntent::Complete | CompletionIntent::Compose => {
22098 let insert_mode =
22099 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22100 .completions
22101 .lsp_insert_mode;
22102 match insert_mode {
22103 LspInsertMode::Insert => false,
22104 LspInsertMode::Replace => true,
22105 LspInsertMode::ReplaceSubsequence => {
22106 let mut text_to_replace = buffer.chars_for_range(
22107 buffer.anchor_before(replace_range.start)
22108 ..buffer.anchor_after(replace_range.end),
22109 );
22110 let mut current_needle = text_to_replace.next();
22111 for haystack_ch in completion.label.text.chars() {
22112 if let Some(needle_ch) = current_needle
22113 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22114 {
22115 current_needle = text_to_replace.next();
22116 }
22117 }
22118 current_needle.is_none()
22119 }
22120 LspInsertMode::ReplaceSuffix => {
22121 if replace_range
22122 .end
22123 .cmp(cursor_position, &buffer_snapshot)
22124 .is_gt()
22125 {
22126 let range_after_cursor = *cursor_position..replace_range.end;
22127 let text_after_cursor = buffer
22128 .text_for_range(
22129 buffer.anchor_before(range_after_cursor.start)
22130 ..buffer.anchor_after(range_after_cursor.end),
22131 )
22132 .collect::<String>()
22133 .to_ascii_lowercase();
22134 completion
22135 .label
22136 .text
22137 .to_ascii_lowercase()
22138 .ends_with(&text_after_cursor)
22139 } else {
22140 true
22141 }
22142 }
22143 }
22144 }
22145 };
22146
22147 if should_replace {
22148 replace_range.clone()
22149 } else {
22150 insert_range.clone()
22151 }
22152 } else {
22153 replace_range.clone()
22154 }
22155 };
22156
22157 if range_to_replace
22158 .end
22159 .cmp(cursor_position, &buffer_snapshot)
22160 .is_lt()
22161 {
22162 range_to_replace.end = *cursor_position;
22163 }
22164
22165 CompletionEdit {
22166 new_text,
22167 replace_range: range_to_replace.to_offset(buffer),
22168 snippet,
22169 }
22170}
22171
22172struct CompletionEdit {
22173 new_text: String,
22174 replace_range: Range<usize>,
22175 snippet: Option<Snippet>,
22176}
22177
22178fn insert_extra_newline_brackets(
22179 buffer: &MultiBufferSnapshot,
22180 range: Range<usize>,
22181 language: &language::LanguageScope,
22182) -> bool {
22183 let leading_whitespace_len = buffer
22184 .reversed_chars_at(range.start)
22185 .take_while(|c| c.is_whitespace() && *c != '\n')
22186 .map(|c| c.len_utf8())
22187 .sum::<usize>();
22188 let trailing_whitespace_len = buffer
22189 .chars_at(range.end)
22190 .take_while(|c| c.is_whitespace() && *c != '\n')
22191 .map(|c| c.len_utf8())
22192 .sum::<usize>();
22193 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22194
22195 language.brackets().any(|(pair, enabled)| {
22196 let pair_start = pair.start.trim_end();
22197 let pair_end = pair.end.trim_start();
22198
22199 enabled
22200 && pair.newline
22201 && buffer.contains_str_at(range.end, pair_end)
22202 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22203 })
22204}
22205
22206fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22207 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22208 [(buffer, range, _)] => (*buffer, range.clone()),
22209 _ => return false,
22210 };
22211 let pair = {
22212 let mut result: Option<BracketMatch> = None;
22213
22214 for pair in buffer
22215 .all_bracket_ranges(range.clone())
22216 .filter(move |pair| {
22217 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22218 })
22219 {
22220 let len = pair.close_range.end - pair.open_range.start;
22221
22222 if let Some(existing) = &result {
22223 let existing_len = existing.close_range.end - existing.open_range.start;
22224 if len > existing_len {
22225 continue;
22226 }
22227 }
22228
22229 result = Some(pair);
22230 }
22231
22232 result
22233 };
22234 let Some(pair) = pair else {
22235 return false;
22236 };
22237 pair.newline_only
22238 && buffer
22239 .chars_for_range(pair.open_range.end..range.start)
22240 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22241 .all(|c| c.is_whitespace() && c != '\n')
22242}
22243
22244fn update_uncommitted_diff_for_buffer(
22245 editor: Entity<Editor>,
22246 project: &Entity<Project>,
22247 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22248 buffer: Entity<MultiBuffer>,
22249 cx: &mut App,
22250) -> Task<()> {
22251 let mut tasks = Vec::new();
22252 project.update(cx, |project, cx| {
22253 for buffer in buffers {
22254 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22255 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22256 }
22257 }
22258 });
22259 cx.spawn(async move |cx| {
22260 let diffs = future::join_all(tasks).await;
22261 if editor
22262 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22263 .unwrap_or(false)
22264 {
22265 return;
22266 }
22267
22268 buffer
22269 .update(cx, |buffer, cx| {
22270 for diff in diffs.into_iter().flatten() {
22271 buffer.add_diff(diff, cx);
22272 }
22273 })
22274 .ok();
22275 })
22276}
22277
22278fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22279 let tab_size = tab_size.get() as usize;
22280 let mut width = offset;
22281
22282 for ch in text.chars() {
22283 width += if ch == '\t' {
22284 tab_size - (width % tab_size)
22285 } else {
22286 1
22287 };
22288 }
22289
22290 width - offset
22291}
22292
22293#[cfg(test)]
22294mod tests {
22295 use super::*;
22296
22297 #[test]
22298 fn test_string_size_with_expanded_tabs() {
22299 let nz = |val| NonZeroU32::new(val).unwrap();
22300 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22301 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22302 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22303 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22304 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22305 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22306 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22307 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22308 }
22309}
22310
22311/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22312struct WordBreakingTokenizer<'a> {
22313 input: &'a str,
22314}
22315
22316impl<'a> WordBreakingTokenizer<'a> {
22317 fn new(input: &'a str) -> Self {
22318 Self { input }
22319 }
22320}
22321
22322fn is_char_ideographic(ch: char) -> bool {
22323 use unicode_script::Script::*;
22324 use unicode_script::UnicodeScript;
22325 matches!(ch.script(), Han | Tangut | Yi)
22326}
22327
22328fn is_grapheme_ideographic(text: &str) -> bool {
22329 text.chars().any(is_char_ideographic)
22330}
22331
22332fn is_grapheme_whitespace(text: &str) -> bool {
22333 text.chars().any(|x| x.is_whitespace())
22334}
22335
22336fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22337 text.chars()
22338 .next()
22339 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22340}
22341
22342#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22343enum WordBreakToken<'a> {
22344 Word { token: &'a str, grapheme_len: usize },
22345 InlineWhitespace { token: &'a str, grapheme_len: usize },
22346 Newline,
22347}
22348
22349impl<'a> Iterator for WordBreakingTokenizer<'a> {
22350 /// Yields a span, the count of graphemes in the token, and whether it was
22351 /// whitespace. Note that it also breaks at word boundaries.
22352 type Item = WordBreakToken<'a>;
22353
22354 fn next(&mut self) -> Option<Self::Item> {
22355 use unicode_segmentation::UnicodeSegmentation;
22356 if self.input.is_empty() {
22357 return None;
22358 }
22359
22360 let mut iter = self.input.graphemes(true).peekable();
22361 let mut offset = 0;
22362 let mut grapheme_len = 0;
22363 if let Some(first_grapheme) = iter.next() {
22364 let is_newline = first_grapheme == "\n";
22365 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22366 offset += first_grapheme.len();
22367 grapheme_len += 1;
22368 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22369 if let Some(grapheme) = iter.peek().copied()
22370 && should_stay_with_preceding_ideograph(grapheme)
22371 {
22372 offset += grapheme.len();
22373 grapheme_len += 1;
22374 }
22375 } else {
22376 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22377 let mut next_word_bound = words.peek().copied();
22378 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22379 next_word_bound = words.next();
22380 }
22381 while let Some(grapheme) = iter.peek().copied() {
22382 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22383 break;
22384 };
22385 if is_grapheme_whitespace(grapheme) != is_whitespace
22386 || (grapheme == "\n") != is_newline
22387 {
22388 break;
22389 };
22390 offset += grapheme.len();
22391 grapheme_len += 1;
22392 iter.next();
22393 }
22394 }
22395 let token = &self.input[..offset];
22396 self.input = &self.input[offset..];
22397 if token == "\n" {
22398 Some(WordBreakToken::Newline)
22399 } else if is_whitespace {
22400 Some(WordBreakToken::InlineWhitespace {
22401 token,
22402 grapheme_len,
22403 })
22404 } else {
22405 Some(WordBreakToken::Word {
22406 token,
22407 grapheme_len,
22408 })
22409 }
22410 } else {
22411 None
22412 }
22413 }
22414}
22415
22416#[test]
22417fn test_word_breaking_tokenizer() {
22418 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22419 ("", &[]),
22420 (" ", &[whitespace(" ", 2)]),
22421 ("Ʒ", &[word("Ʒ", 1)]),
22422 ("Ǽ", &[word("Ǽ", 1)]),
22423 ("⋑", &[word("⋑", 1)]),
22424 ("⋑⋑", &[word("⋑⋑", 2)]),
22425 (
22426 "原理,进而",
22427 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22428 ),
22429 (
22430 "hello world",
22431 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22432 ),
22433 (
22434 "hello, world",
22435 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22436 ),
22437 (
22438 " hello world",
22439 &[
22440 whitespace(" ", 2),
22441 word("hello", 5),
22442 whitespace(" ", 1),
22443 word("world", 5),
22444 ],
22445 ),
22446 (
22447 "这是什么 \n 钢笔",
22448 &[
22449 word("这", 1),
22450 word("是", 1),
22451 word("什", 1),
22452 word("么", 1),
22453 whitespace(" ", 1),
22454 newline(),
22455 whitespace(" ", 1),
22456 word("钢", 1),
22457 word("笔", 1),
22458 ],
22459 ),
22460 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22461 ];
22462
22463 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22464 WordBreakToken::Word {
22465 token,
22466 grapheme_len,
22467 }
22468 }
22469
22470 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22471 WordBreakToken::InlineWhitespace {
22472 token,
22473 grapheme_len,
22474 }
22475 }
22476
22477 fn newline() -> WordBreakToken<'static> {
22478 WordBreakToken::Newline
22479 }
22480
22481 for (input, result) in tests {
22482 assert_eq!(
22483 WordBreakingTokenizer::new(input)
22484 .collect::<Vec<_>>()
22485 .as_slice(),
22486 *result,
22487 );
22488 }
22489}
22490
22491fn wrap_with_prefix(
22492 first_line_prefix: String,
22493 subsequent_lines_prefix: String,
22494 unwrapped_text: String,
22495 wrap_column: usize,
22496 tab_size: NonZeroU32,
22497 preserve_existing_whitespace: bool,
22498) -> String {
22499 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22500 let subsequent_lines_prefix_len =
22501 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22502 let mut wrapped_text = String::new();
22503 let mut current_line = first_line_prefix;
22504 let mut is_first_line = true;
22505
22506 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22507 let mut current_line_len = first_line_prefix_len;
22508 let mut in_whitespace = false;
22509 for token in tokenizer {
22510 let have_preceding_whitespace = in_whitespace;
22511 match token {
22512 WordBreakToken::Word {
22513 token,
22514 grapheme_len,
22515 } => {
22516 in_whitespace = false;
22517 let current_prefix_len = if is_first_line {
22518 first_line_prefix_len
22519 } else {
22520 subsequent_lines_prefix_len
22521 };
22522 if current_line_len + grapheme_len > wrap_column
22523 && current_line_len != current_prefix_len
22524 {
22525 wrapped_text.push_str(current_line.trim_end());
22526 wrapped_text.push('\n');
22527 is_first_line = false;
22528 current_line = subsequent_lines_prefix.clone();
22529 current_line_len = subsequent_lines_prefix_len;
22530 }
22531 current_line.push_str(token);
22532 current_line_len += grapheme_len;
22533 }
22534 WordBreakToken::InlineWhitespace {
22535 mut token,
22536 mut grapheme_len,
22537 } => {
22538 in_whitespace = true;
22539 if have_preceding_whitespace && !preserve_existing_whitespace {
22540 continue;
22541 }
22542 if !preserve_existing_whitespace {
22543 // Keep a single whitespace grapheme as-is
22544 if let Some(first) =
22545 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22546 {
22547 token = first;
22548 } else {
22549 token = " ";
22550 }
22551 grapheme_len = 1;
22552 }
22553 let current_prefix_len = if is_first_line {
22554 first_line_prefix_len
22555 } else {
22556 subsequent_lines_prefix_len
22557 };
22558 if current_line_len + grapheme_len > wrap_column {
22559 wrapped_text.push_str(current_line.trim_end());
22560 wrapped_text.push('\n');
22561 is_first_line = false;
22562 current_line = subsequent_lines_prefix.clone();
22563 current_line_len = subsequent_lines_prefix_len;
22564 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22565 current_line.push_str(token);
22566 current_line_len += grapheme_len;
22567 }
22568 }
22569 WordBreakToken::Newline => {
22570 in_whitespace = true;
22571 let current_prefix_len = if is_first_line {
22572 first_line_prefix_len
22573 } else {
22574 subsequent_lines_prefix_len
22575 };
22576 if preserve_existing_whitespace {
22577 wrapped_text.push_str(current_line.trim_end());
22578 wrapped_text.push('\n');
22579 is_first_line = false;
22580 current_line = subsequent_lines_prefix.clone();
22581 current_line_len = subsequent_lines_prefix_len;
22582 } else if have_preceding_whitespace {
22583 continue;
22584 } else if current_line_len + 1 > wrap_column
22585 && current_line_len != current_prefix_len
22586 {
22587 wrapped_text.push_str(current_line.trim_end());
22588 wrapped_text.push('\n');
22589 is_first_line = false;
22590 current_line = subsequent_lines_prefix.clone();
22591 current_line_len = subsequent_lines_prefix_len;
22592 } else if current_line_len != current_prefix_len {
22593 current_line.push(' ');
22594 current_line_len += 1;
22595 }
22596 }
22597 }
22598 }
22599
22600 if !current_line.is_empty() {
22601 wrapped_text.push_str(¤t_line);
22602 }
22603 wrapped_text
22604}
22605
22606#[test]
22607fn test_wrap_with_prefix() {
22608 assert_eq!(
22609 wrap_with_prefix(
22610 "# ".to_string(),
22611 "# ".to_string(),
22612 "abcdefg".to_string(),
22613 4,
22614 NonZeroU32::new(4).unwrap(),
22615 false,
22616 ),
22617 "# abcdefg"
22618 );
22619 assert_eq!(
22620 wrap_with_prefix(
22621 "".to_string(),
22622 "".to_string(),
22623 "\thello world".to_string(),
22624 8,
22625 NonZeroU32::new(4).unwrap(),
22626 false,
22627 ),
22628 "hello\nworld"
22629 );
22630 assert_eq!(
22631 wrap_with_prefix(
22632 "// ".to_string(),
22633 "// ".to_string(),
22634 "xx \nyy zz aa bb cc".to_string(),
22635 12,
22636 NonZeroU32::new(4).unwrap(),
22637 false,
22638 ),
22639 "// xx yy zz\n// aa bb cc"
22640 );
22641 assert_eq!(
22642 wrap_with_prefix(
22643 String::new(),
22644 String::new(),
22645 "这是什么 \n 钢笔".to_string(),
22646 3,
22647 NonZeroU32::new(4).unwrap(),
22648 false,
22649 ),
22650 "这是什\n么 钢\n笔"
22651 );
22652 assert_eq!(
22653 wrap_with_prefix(
22654 String::new(),
22655 String::new(),
22656 format!("foo{}bar", '\u{2009}'), // thin space
22657 80,
22658 NonZeroU32::new(4).unwrap(),
22659 false,
22660 ),
22661 format!("foo{}bar", '\u{2009}')
22662 );
22663}
22664
22665pub trait CollaborationHub {
22666 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22667 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22668 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22669}
22670
22671impl CollaborationHub for Entity<Project> {
22672 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22673 self.read(cx).collaborators()
22674 }
22675
22676 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22677 self.read(cx).user_store().read(cx).participant_indices()
22678 }
22679
22680 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22681 let this = self.read(cx);
22682 let user_ids = this.collaborators().values().map(|c| c.user_id);
22683 this.user_store().read(cx).participant_names(user_ids, cx)
22684 }
22685}
22686
22687pub trait SemanticsProvider {
22688 fn hover(
22689 &self,
22690 buffer: &Entity<Buffer>,
22691 position: text::Anchor,
22692 cx: &mut App,
22693 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22694
22695 fn inline_values(
22696 &self,
22697 buffer_handle: Entity<Buffer>,
22698 range: Range<text::Anchor>,
22699 cx: &mut App,
22700 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22701
22702 fn inlay_hints(
22703 &self,
22704 buffer_handle: Entity<Buffer>,
22705 range: Range<text::Anchor>,
22706 cx: &mut App,
22707 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22708
22709 fn resolve_inlay_hint(
22710 &self,
22711 hint: InlayHint,
22712 buffer_handle: Entity<Buffer>,
22713 server_id: LanguageServerId,
22714 cx: &mut App,
22715 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22716
22717 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22718
22719 fn document_highlights(
22720 &self,
22721 buffer: &Entity<Buffer>,
22722 position: text::Anchor,
22723 cx: &mut App,
22724 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22725
22726 fn definitions(
22727 &self,
22728 buffer: &Entity<Buffer>,
22729 position: text::Anchor,
22730 kind: GotoDefinitionKind,
22731 cx: &mut App,
22732 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22733
22734 fn range_for_rename(
22735 &self,
22736 buffer: &Entity<Buffer>,
22737 position: text::Anchor,
22738 cx: &mut App,
22739 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22740
22741 fn perform_rename(
22742 &self,
22743 buffer: &Entity<Buffer>,
22744 position: text::Anchor,
22745 new_name: String,
22746 cx: &mut App,
22747 ) -> Option<Task<Result<ProjectTransaction>>>;
22748}
22749
22750pub trait CompletionProvider {
22751 fn completions(
22752 &self,
22753 excerpt_id: ExcerptId,
22754 buffer: &Entity<Buffer>,
22755 buffer_position: text::Anchor,
22756 trigger: CompletionContext,
22757 window: &mut Window,
22758 cx: &mut Context<Editor>,
22759 ) -> Task<Result<Vec<CompletionResponse>>>;
22760
22761 fn resolve_completions(
22762 &self,
22763 _buffer: Entity<Buffer>,
22764 _completion_indices: Vec<usize>,
22765 _completions: Rc<RefCell<Box<[Completion]>>>,
22766 _cx: &mut Context<Editor>,
22767 ) -> Task<Result<bool>> {
22768 Task::ready(Ok(false))
22769 }
22770
22771 fn apply_additional_edits_for_completion(
22772 &self,
22773 _buffer: Entity<Buffer>,
22774 _completions: Rc<RefCell<Box<[Completion]>>>,
22775 _completion_index: usize,
22776 _push_to_history: bool,
22777 _cx: &mut Context<Editor>,
22778 ) -> Task<Result<Option<language::Transaction>>> {
22779 Task::ready(Ok(None))
22780 }
22781
22782 fn is_completion_trigger(
22783 &self,
22784 buffer: &Entity<Buffer>,
22785 position: language::Anchor,
22786 text: &str,
22787 trigger_in_words: bool,
22788 menu_is_open: bool,
22789 cx: &mut Context<Editor>,
22790 ) -> bool;
22791
22792 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22793
22794 fn sort_completions(&self) -> bool {
22795 true
22796 }
22797
22798 fn filter_completions(&self) -> bool {
22799 true
22800 }
22801}
22802
22803pub trait CodeActionProvider {
22804 fn id(&self) -> Arc<str>;
22805
22806 fn code_actions(
22807 &self,
22808 buffer: &Entity<Buffer>,
22809 range: Range<text::Anchor>,
22810 window: &mut Window,
22811 cx: &mut App,
22812 ) -> Task<Result<Vec<CodeAction>>>;
22813
22814 fn apply_code_action(
22815 &self,
22816 buffer_handle: Entity<Buffer>,
22817 action: CodeAction,
22818 excerpt_id: ExcerptId,
22819 push_to_history: bool,
22820 window: &mut Window,
22821 cx: &mut App,
22822 ) -> Task<Result<ProjectTransaction>>;
22823}
22824
22825impl CodeActionProvider for Entity<Project> {
22826 fn id(&self) -> Arc<str> {
22827 "project".into()
22828 }
22829
22830 fn code_actions(
22831 &self,
22832 buffer: &Entity<Buffer>,
22833 range: Range<text::Anchor>,
22834 _window: &mut Window,
22835 cx: &mut App,
22836 ) -> Task<Result<Vec<CodeAction>>> {
22837 self.update(cx, |project, cx| {
22838 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22839 let code_actions = project.code_actions(buffer, range, None, cx);
22840 cx.background_spawn(async move {
22841 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22842 Ok(code_lens_actions
22843 .context("code lens fetch")?
22844 .into_iter()
22845 .flatten()
22846 .chain(
22847 code_actions
22848 .context("code action fetch")?
22849 .into_iter()
22850 .flatten(),
22851 )
22852 .collect())
22853 })
22854 })
22855 }
22856
22857 fn apply_code_action(
22858 &self,
22859 buffer_handle: Entity<Buffer>,
22860 action: CodeAction,
22861 _excerpt_id: ExcerptId,
22862 push_to_history: bool,
22863 _window: &mut Window,
22864 cx: &mut App,
22865 ) -> Task<Result<ProjectTransaction>> {
22866 self.update(cx, |project, cx| {
22867 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22868 })
22869 }
22870}
22871
22872fn snippet_completions(
22873 project: &Project,
22874 buffer: &Entity<Buffer>,
22875 buffer_position: text::Anchor,
22876 cx: &mut App,
22877) -> Task<Result<CompletionResponse>> {
22878 let languages = buffer.read(cx).languages_at(buffer_position);
22879 let snippet_store = project.snippets().read(cx);
22880
22881 let scopes: Vec<_> = languages
22882 .iter()
22883 .filter_map(|language| {
22884 let language_name = language.lsp_id();
22885 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22886
22887 if snippets.is_empty() {
22888 None
22889 } else {
22890 Some((language.default_scope(), snippets))
22891 }
22892 })
22893 .collect();
22894
22895 if scopes.is_empty() {
22896 return Task::ready(Ok(CompletionResponse {
22897 completions: vec![],
22898 display_options: CompletionDisplayOptions::default(),
22899 is_incomplete: false,
22900 }));
22901 }
22902
22903 let snapshot = buffer.read(cx).text_snapshot();
22904 let executor = cx.background_executor().clone();
22905
22906 cx.background_spawn(async move {
22907 let mut is_incomplete = false;
22908 let mut completions: Vec<Completion> = Vec::new();
22909 for (scope, snippets) in scopes.into_iter() {
22910 let classifier =
22911 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22912
22913 const MAX_WORD_PREFIX_LEN: usize = 128;
22914 let last_word: String = snapshot
22915 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22916 .take(MAX_WORD_PREFIX_LEN)
22917 .take_while(|c| classifier.is_word(*c))
22918 .collect::<String>()
22919 .chars()
22920 .rev()
22921 .collect();
22922
22923 if last_word.is_empty() {
22924 return Ok(CompletionResponse {
22925 completions: vec![],
22926 display_options: CompletionDisplayOptions::default(),
22927 is_incomplete: true,
22928 });
22929 }
22930
22931 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22932 let to_lsp = |point: &text::Anchor| {
22933 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22934 point_to_lsp(end)
22935 };
22936 let lsp_end = to_lsp(&buffer_position);
22937
22938 let candidates = snippets
22939 .iter()
22940 .enumerate()
22941 .flat_map(|(ix, snippet)| {
22942 snippet
22943 .prefix
22944 .iter()
22945 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22946 })
22947 .collect::<Vec<StringMatchCandidate>>();
22948
22949 const MAX_RESULTS: usize = 100;
22950 let mut matches = fuzzy::match_strings(
22951 &candidates,
22952 &last_word,
22953 last_word.chars().any(|c| c.is_uppercase()),
22954 true,
22955 MAX_RESULTS,
22956 &Default::default(),
22957 executor.clone(),
22958 )
22959 .await;
22960
22961 if matches.len() >= MAX_RESULTS {
22962 is_incomplete = true;
22963 }
22964
22965 // Remove all candidates where the query's start does not match the start of any word in the candidate
22966 if let Some(query_start) = last_word.chars().next() {
22967 matches.retain(|string_match| {
22968 split_words(&string_match.string).any(|word| {
22969 // Check that the first codepoint of the word as lowercase matches the first
22970 // codepoint of the query as lowercase
22971 word.chars()
22972 .flat_map(|codepoint| codepoint.to_lowercase())
22973 .zip(query_start.to_lowercase())
22974 .all(|(word_cp, query_cp)| word_cp == query_cp)
22975 })
22976 });
22977 }
22978
22979 let matched_strings = matches
22980 .into_iter()
22981 .map(|m| m.string)
22982 .collect::<HashSet<_>>();
22983
22984 completions.extend(snippets.iter().filter_map(|snippet| {
22985 let matching_prefix = snippet
22986 .prefix
22987 .iter()
22988 .find(|prefix| matched_strings.contains(*prefix))?;
22989 let start = as_offset - last_word.len();
22990 let start = snapshot.anchor_before(start);
22991 let range = start..buffer_position;
22992 let lsp_start = to_lsp(&start);
22993 let lsp_range = lsp::Range {
22994 start: lsp_start,
22995 end: lsp_end,
22996 };
22997 Some(Completion {
22998 replace_range: range,
22999 new_text: snippet.body.clone(),
23000 source: CompletionSource::Lsp {
23001 insert_range: None,
23002 server_id: LanguageServerId(usize::MAX),
23003 resolved: true,
23004 lsp_completion: Box::new(lsp::CompletionItem {
23005 label: snippet.prefix.first().unwrap().clone(),
23006 kind: Some(CompletionItemKind::SNIPPET),
23007 label_details: snippet.description.as_ref().map(|description| {
23008 lsp::CompletionItemLabelDetails {
23009 detail: Some(description.clone()),
23010 description: None,
23011 }
23012 }),
23013 insert_text_format: Some(InsertTextFormat::SNIPPET),
23014 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23015 lsp::InsertReplaceEdit {
23016 new_text: snippet.body.clone(),
23017 insert: lsp_range,
23018 replace: lsp_range,
23019 },
23020 )),
23021 filter_text: Some(snippet.body.clone()),
23022 sort_text: Some(char::MAX.to_string()),
23023 ..lsp::CompletionItem::default()
23024 }),
23025 lsp_defaults: None,
23026 },
23027 label: CodeLabel {
23028 text: matching_prefix.clone(),
23029 runs: Vec::new(),
23030 filter_range: 0..matching_prefix.len(),
23031 },
23032 icon_path: None,
23033 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23034 single_line: snippet.name.clone().into(),
23035 plain_text: snippet
23036 .description
23037 .clone()
23038 .map(|description| description.into()),
23039 }),
23040 insert_text_mode: None,
23041 confirm: None,
23042 })
23043 }))
23044 }
23045
23046 Ok(CompletionResponse {
23047 completions,
23048 display_options: CompletionDisplayOptions::default(),
23049 is_incomplete,
23050 })
23051 })
23052}
23053
23054impl CompletionProvider for Entity<Project> {
23055 fn completions(
23056 &self,
23057 _excerpt_id: ExcerptId,
23058 buffer: &Entity<Buffer>,
23059 buffer_position: text::Anchor,
23060 options: CompletionContext,
23061 _window: &mut Window,
23062 cx: &mut Context<Editor>,
23063 ) -> Task<Result<Vec<CompletionResponse>>> {
23064 self.update(cx, |project, cx| {
23065 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23066 let project_completions = project.completions(buffer, buffer_position, options, cx);
23067 cx.background_spawn(async move {
23068 let mut responses = project_completions.await?;
23069 let snippets = snippets.await?;
23070 if !snippets.completions.is_empty() {
23071 responses.push(snippets);
23072 }
23073 Ok(responses)
23074 })
23075 })
23076 }
23077
23078 fn resolve_completions(
23079 &self,
23080 buffer: Entity<Buffer>,
23081 completion_indices: Vec<usize>,
23082 completions: Rc<RefCell<Box<[Completion]>>>,
23083 cx: &mut Context<Editor>,
23084 ) -> Task<Result<bool>> {
23085 self.update(cx, |project, cx| {
23086 project.lsp_store().update(cx, |lsp_store, cx| {
23087 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23088 })
23089 })
23090 }
23091
23092 fn apply_additional_edits_for_completion(
23093 &self,
23094 buffer: Entity<Buffer>,
23095 completions: Rc<RefCell<Box<[Completion]>>>,
23096 completion_index: usize,
23097 push_to_history: bool,
23098 cx: &mut Context<Editor>,
23099 ) -> Task<Result<Option<language::Transaction>>> {
23100 self.update(cx, |project, cx| {
23101 project.lsp_store().update(cx, |lsp_store, cx| {
23102 lsp_store.apply_additional_edits_for_completion(
23103 buffer,
23104 completions,
23105 completion_index,
23106 push_to_history,
23107 cx,
23108 )
23109 })
23110 })
23111 }
23112
23113 fn is_completion_trigger(
23114 &self,
23115 buffer: &Entity<Buffer>,
23116 position: language::Anchor,
23117 text: &str,
23118 trigger_in_words: bool,
23119 menu_is_open: bool,
23120 cx: &mut Context<Editor>,
23121 ) -> bool {
23122 let mut chars = text.chars();
23123 let char = if let Some(char) = chars.next() {
23124 char
23125 } else {
23126 return false;
23127 };
23128 if chars.next().is_some() {
23129 return false;
23130 }
23131
23132 let buffer = buffer.read(cx);
23133 let snapshot = buffer.snapshot();
23134 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23135 return false;
23136 }
23137 let classifier = snapshot
23138 .char_classifier_at(position)
23139 .scope_context(Some(CharScopeContext::Completion));
23140 if trigger_in_words && classifier.is_word(char) {
23141 return true;
23142 }
23143
23144 buffer.completion_triggers().contains(text)
23145 }
23146}
23147
23148impl SemanticsProvider for Entity<Project> {
23149 fn hover(
23150 &self,
23151 buffer: &Entity<Buffer>,
23152 position: text::Anchor,
23153 cx: &mut App,
23154 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23155 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23156 }
23157
23158 fn document_highlights(
23159 &self,
23160 buffer: &Entity<Buffer>,
23161 position: text::Anchor,
23162 cx: &mut App,
23163 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23164 Some(self.update(cx, |project, cx| {
23165 project.document_highlights(buffer, position, cx)
23166 }))
23167 }
23168
23169 fn definitions(
23170 &self,
23171 buffer: &Entity<Buffer>,
23172 position: text::Anchor,
23173 kind: GotoDefinitionKind,
23174 cx: &mut App,
23175 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23176 Some(self.update(cx, |project, cx| match kind {
23177 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23178 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23179 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23180 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23181 }))
23182 }
23183
23184 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23185 self.update(cx, |project, cx| {
23186 if project
23187 .active_debug_session(cx)
23188 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23189 {
23190 return true;
23191 }
23192
23193 buffer.update(cx, |buffer, cx| {
23194 project.any_language_server_supports_inlay_hints(buffer, cx)
23195 })
23196 })
23197 }
23198
23199 fn inline_values(
23200 &self,
23201 buffer_handle: Entity<Buffer>,
23202 range: Range<text::Anchor>,
23203 cx: &mut App,
23204 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23205 self.update(cx, |project, cx| {
23206 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23207
23208 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23209 })
23210 }
23211
23212 fn inlay_hints(
23213 &self,
23214 buffer_handle: Entity<Buffer>,
23215 range: Range<text::Anchor>,
23216 cx: &mut App,
23217 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23218 Some(self.update(cx, |project, cx| {
23219 project.inlay_hints(buffer_handle, range, cx)
23220 }))
23221 }
23222
23223 fn resolve_inlay_hint(
23224 &self,
23225 hint: InlayHint,
23226 buffer_handle: Entity<Buffer>,
23227 server_id: LanguageServerId,
23228 cx: &mut App,
23229 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23230 Some(self.update(cx, |project, cx| {
23231 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23232 }))
23233 }
23234
23235 fn range_for_rename(
23236 &self,
23237 buffer: &Entity<Buffer>,
23238 position: text::Anchor,
23239 cx: &mut App,
23240 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23241 Some(self.update(cx, |project, cx| {
23242 let buffer = buffer.clone();
23243 let task = project.prepare_rename(buffer.clone(), position, cx);
23244 cx.spawn(async move |_, cx| {
23245 Ok(match task.await? {
23246 PrepareRenameResponse::Success(range) => Some(range),
23247 PrepareRenameResponse::InvalidPosition => None,
23248 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23249 // Fallback on using TreeSitter info to determine identifier range
23250 buffer.read_with(cx, |buffer, _| {
23251 let snapshot = buffer.snapshot();
23252 let (range, kind) = snapshot.surrounding_word(position, None);
23253 if kind != Some(CharKind::Word) {
23254 return None;
23255 }
23256 Some(
23257 snapshot.anchor_before(range.start)
23258 ..snapshot.anchor_after(range.end),
23259 )
23260 })?
23261 }
23262 })
23263 })
23264 }))
23265 }
23266
23267 fn perform_rename(
23268 &self,
23269 buffer: &Entity<Buffer>,
23270 position: text::Anchor,
23271 new_name: String,
23272 cx: &mut App,
23273 ) -> Option<Task<Result<ProjectTransaction>>> {
23274 Some(self.update(cx, |project, cx| {
23275 project.perform_rename(buffer.clone(), position, new_name, cx)
23276 }))
23277 }
23278}
23279
23280fn inlay_hint_settings(
23281 location: Anchor,
23282 snapshot: &MultiBufferSnapshot,
23283 cx: &mut Context<Editor>,
23284) -> InlayHintSettings {
23285 let file = snapshot.file_at(location);
23286 let language = snapshot.language_at(location).map(|l| l.name());
23287 language_settings(language, file, cx).inlay_hints
23288}
23289
23290fn consume_contiguous_rows(
23291 contiguous_row_selections: &mut Vec<Selection<Point>>,
23292 selection: &Selection<Point>,
23293 display_map: &DisplaySnapshot,
23294 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23295) -> (MultiBufferRow, MultiBufferRow) {
23296 contiguous_row_selections.push(selection.clone());
23297 let start_row = starting_row(selection, display_map);
23298 let mut end_row = ending_row(selection, display_map);
23299
23300 while let Some(next_selection) = selections.peek() {
23301 if next_selection.start.row <= end_row.0 {
23302 end_row = ending_row(next_selection, display_map);
23303 contiguous_row_selections.push(selections.next().unwrap().clone());
23304 } else {
23305 break;
23306 }
23307 }
23308 (start_row, end_row)
23309}
23310
23311fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23312 if selection.start.column > 0 {
23313 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23314 } else {
23315 MultiBufferRow(selection.start.row)
23316 }
23317}
23318
23319fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23320 if next_selection.end.column > 0 || next_selection.is_empty() {
23321 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23322 } else {
23323 MultiBufferRow(next_selection.end.row)
23324 }
23325}
23326
23327impl EditorSnapshot {
23328 pub fn remote_selections_in_range<'a>(
23329 &'a self,
23330 range: &'a Range<Anchor>,
23331 collaboration_hub: &dyn CollaborationHub,
23332 cx: &'a App,
23333 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23334 let participant_names = collaboration_hub.user_names(cx);
23335 let participant_indices = collaboration_hub.user_participant_indices(cx);
23336 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23337 let collaborators_by_replica_id = collaborators_by_peer_id
23338 .values()
23339 .map(|collaborator| (collaborator.replica_id, collaborator))
23340 .collect::<HashMap<_, _>>();
23341 self.buffer_snapshot()
23342 .selections_in_range(range, false)
23343 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23344 if replica_id == AGENT_REPLICA_ID {
23345 Some(RemoteSelection {
23346 replica_id,
23347 selection,
23348 cursor_shape,
23349 line_mode,
23350 collaborator_id: CollaboratorId::Agent,
23351 user_name: Some("Agent".into()),
23352 color: cx.theme().players().agent(),
23353 })
23354 } else {
23355 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23356 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23357 let user_name = participant_names.get(&collaborator.user_id).cloned();
23358 Some(RemoteSelection {
23359 replica_id,
23360 selection,
23361 cursor_shape,
23362 line_mode,
23363 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23364 user_name,
23365 color: if let Some(index) = participant_index {
23366 cx.theme().players().color_for_participant(index.0)
23367 } else {
23368 cx.theme().players().absent()
23369 },
23370 })
23371 }
23372 })
23373 }
23374
23375 pub fn hunks_for_ranges(
23376 &self,
23377 ranges: impl IntoIterator<Item = Range<Point>>,
23378 ) -> Vec<MultiBufferDiffHunk> {
23379 let mut hunks = Vec::new();
23380 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23381 HashMap::default();
23382 for query_range in ranges {
23383 let query_rows =
23384 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23385 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23386 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23387 ) {
23388 // Include deleted hunks that are adjacent to the query range, because
23389 // otherwise they would be missed.
23390 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23391 if hunk.status().is_deleted() {
23392 intersects_range |= hunk.row_range.start == query_rows.end;
23393 intersects_range |= hunk.row_range.end == query_rows.start;
23394 }
23395 if intersects_range {
23396 if !processed_buffer_rows
23397 .entry(hunk.buffer_id)
23398 .or_default()
23399 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23400 {
23401 continue;
23402 }
23403 hunks.push(hunk);
23404 }
23405 }
23406 }
23407
23408 hunks
23409 }
23410
23411 fn display_diff_hunks_for_rows<'a>(
23412 &'a self,
23413 display_rows: Range<DisplayRow>,
23414 folded_buffers: &'a HashSet<BufferId>,
23415 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23416 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23417 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23418
23419 self.buffer_snapshot()
23420 .diff_hunks_in_range(buffer_start..buffer_end)
23421 .filter_map(|hunk| {
23422 if folded_buffers.contains(&hunk.buffer_id) {
23423 return None;
23424 }
23425
23426 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23427 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23428
23429 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23430 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23431
23432 let display_hunk = if hunk_display_start.column() != 0 {
23433 DisplayDiffHunk::Folded {
23434 display_row: hunk_display_start.row(),
23435 }
23436 } else {
23437 let mut end_row = hunk_display_end.row();
23438 if hunk_display_end.column() > 0 {
23439 end_row.0 += 1;
23440 }
23441 let is_created_file = hunk.is_created_file();
23442 DisplayDiffHunk::Unfolded {
23443 status: hunk.status(),
23444 diff_base_byte_range: hunk.diff_base_byte_range,
23445 display_row_range: hunk_display_start.row()..end_row,
23446 multi_buffer_range: Anchor::range_in_buffer(
23447 hunk.excerpt_id,
23448 hunk.buffer_id,
23449 hunk.buffer_range,
23450 ),
23451 is_created_file,
23452 }
23453 };
23454
23455 Some(display_hunk)
23456 })
23457 }
23458
23459 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23460 self.display_snapshot
23461 .buffer_snapshot()
23462 .language_at(position)
23463 }
23464
23465 pub fn is_focused(&self) -> bool {
23466 self.is_focused
23467 }
23468
23469 pub fn placeholder_text(&self) -> Option<String> {
23470 self.placeholder_display_snapshot
23471 .as_ref()
23472 .map(|display_map| display_map.text())
23473 }
23474
23475 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23476 self.scroll_anchor.scroll_position(&self.display_snapshot)
23477 }
23478
23479 fn gutter_dimensions(
23480 &self,
23481 font_id: FontId,
23482 font_size: Pixels,
23483 max_line_number_width: Pixels,
23484 cx: &App,
23485 ) -> Option<GutterDimensions> {
23486 if !self.show_gutter {
23487 return None;
23488 }
23489
23490 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23491 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23492
23493 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23494 matches!(
23495 ProjectSettings::get_global(cx).git.git_gutter,
23496 GitGutterSetting::TrackedFiles
23497 )
23498 });
23499 let gutter_settings = EditorSettings::get_global(cx).gutter;
23500 let show_line_numbers = self
23501 .show_line_numbers
23502 .unwrap_or(gutter_settings.line_numbers);
23503 let line_gutter_width = if show_line_numbers {
23504 // Avoid flicker-like gutter resizes when the line number gains another digit by
23505 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23506 let min_width_for_number_on_gutter =
23507 ch_advance * gutter_settings.min_line_number_digits as f32;
23508 max_line_number_width.max(min_width_for_number_on_gutter)
23509 } else {
23510 0.0.into()
23511 };
23512
23513 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23514 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23515
23516 let git_blame_entries_width =
23517 self.git_blame_gutter_max_author_length
23518 .map(|max_author_length| {
23519 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23520 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23521
23522 /// The number of characters to dedicate to gaps and margins.
23523 const SPACING_WIDTH: usize = 4;
23524
23525 let max_char_count = max_author_length.min(renderer.max_author_length())
23526 + ::git::SHORT_SHA_LENGTH
23527 + MAX_RELATIVE_TIMESTAMP.len()
23528 + SPACING_WIDTH;
23529
23530 ch_advance * max_char_count
23531 });
23532
23533 let is_singleton = self.buffer_snapshot().is_singleton();
23534
23535 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23536 left_padding += if !is_singleton {
23537 ch_width * 4.0
23538 } else if show_runnables || show_breakpoints {
23539 ch_width * 3.0
23540 } else if show_git_gutter && show_line_numbers {
23541 ch_width * 2.0
23542 } else if show_git_gutter || show_line_numbers {
23543 ch_width
23544 } else {
23545 px(0.)
23546 };
23547
23548 let shows_folds = is_singleton && gutter_settings.folds;
23549
23550 let right_padding = if shows_folds && show_line_numbers {
23551 ch_width * 4.0
23552 } else if shows_folds || (!is_singleton && show_line_numbers) {
23553 ch_width * 3.0
23554 } else if show_line_numbers {
23555 ch_width
23556 } else {
23557 px(0.)
23558 };
23559
23560 Some(GutterDimensions {
23561 left_padding,
23562 right_padding,
23563 width: line_gutter_width + left_padding + right_padding,
23564 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23565 git_blame_entries_width,
23566 })
23567 }
23568
23569 pub fn render_crease_toggle(
23570 &self,
23571 buffer_row: MultiBufferRow,
23572 row_contains_cursor: bool,
23573 editor: Entity<Editor>,
23574 window: &mut Window,
23575 cx: &mut App,
23576 ) -> Option<AnyElement> {
23577 let folded = self.is_line_folded(buffer_row);
23578 let mut is_foldable = false;
23579
23580 if let Some(crease) = self
23581 .crease_snapshot
23582 .query_row(buffer_row, self.buffer_snapshot())
23583 {
23584 is_foldable = true;
23585 match crease {
23586 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23587 if let Some(render_toggle) = render_toggle {
23588 let toggle_callback =
23589 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23590 if folded {
23591 editor.update(cx, |editor, cx| {
23592 editor.fold_at(buffer_row, window, cx)
23593 });
23594 } else {
23595 editor.update(cx, |editor, cx| {
23596 editor.unfold_at(buffer_row, window, cx)
23597 });
23598 }
23599 });
23600 return Some((render_toggle)(
23601 buffer_row,
23602 folded,
23603 toggle_callback,
23604 window,
23605 cx,
23606 ));
23607 }
23608 }
23609 }
23610 }
23611
23612 is_foldable |= self.starts_indent(buffer_row);
23613
23614 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23615 Some(
23616 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23617 .toggle_state(folded)
23618 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23619 if folded {
23620 this.unfold_at(buffer_row, window, cx);
23621 } else {
23622 this.fold_at(buffer_row, window, cx);
23623 }
23624 }))
23625 .into_any_element(),
23626 )
23627 } else {
23628 None
23629 }
23630 }
23631
23632 pub fn render_crease_trailer(
23633 &self,
23634 buffer_row: MultiBufferRow,
23635 window: &mut Window,
23636 cx: &mut App,
23637 ) -> Option<AnyElement> {
23638 let folded = self.is_line_folded(buffer_row);
23639 if let Crease::Inline { render_trailer, .. } = self
23640 .crease_snapshot
23641 .query_row(buffer_row, self.buffer_snapshot())?
23642 {
23643 let render_trailer = render_trailer.as_ref()?;
23644 Some(render_trailer(buffer_row, folded, window, cx))
23645 } else {
23646 None
23647 }
23648 }
23649}
23650
23651impl Deref for EditorSnapshot {
23652 type Target = DisplaySnapshot;
23653
23654 fn deref(&self) -> &Self::Target {
23655 &self.display_snapshot
23656 }
23657}
23658
23659#[derive(Clone, Debug, PartialEq, Eq)]
23660pub enum EditorEvent {
23661 InputIgnored {
23662 text: Arc<str>,
23663 },
23664 InputHandled {
23665 utf16_range_to_replace: Option<Range<isize>>,
23666 text: Arc<str>,
23667 },
23668 ExcerptsAdded {
23669 buffer: Entity<Buffer>,
23670 predecessor: ExcerptId,
23671 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23672 },
23673 ExcerptsRemoved {
23674 ids: Vec<ExcerptId>,
23675 removed_buffer_ids: Vec<BufferId>,
23676 },
23677 BufferFoldToggled {
23678 ids: Vec<ExcerptId>,
23679 folded: bool,
23680 },
23681 ExcerptsEdited {
23682 ids: Vec<ExcerptId>,
23683 },
23684 ExcerptsExpanded {
23685 ids: Vec<ExcerptId>,
23686 },
23687 BufferEdited,
23688 Edited {
23689 transaction_id: clock::Lamport,
23690 },
23691 Reparsed(BufferId),
23692 Focused,
23693 FocusedIn,
23694 Blurred,
23695 DirtyChanged,
23696 Saved,
23697 TitleChanged,
23698 SelectionsChanged {
23699 local: bool,
23700 },
23701 ScrollPositionChanged {
23702 local: bool,
23703 autoscroll: bool,
23704 },
23705 TransactionUndone {
23706 transaction_id: clock::Lamport,
23707 },
23708 TransactionBegun {
23709 transaction_id: clock::Lamport,
23710 },
23711 CursorShapeChanged,
23712 BreadcrumbsChanged,
23713 PushedToNavHistory {
23714 anchor: Anchor,
23715 is_deactivate: bool,
23716 },
23717}
23718
23719impl EventEmitter<EditorEvent> for Editor {}
23720
23721impl Focusable for Editor {
23722 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23723 self.focus_handle.clone()
23724 }
23725}
23726
23727impl Render for Editor {
23728 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23729 let settings = ThemeSettings::get_global(cx);
23730
23731 let mut text_style = match self.mode {
23732 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23733 color: cx.theme().colors().editor_foreground,
23734 font_family: settings.ui_font.family.clone(),
23735 font_features: settings.ui_font.features.clone(),
23736 font_fallbacks: settings.ui_font.fallbacks.clone(),
23737 font_size: rems(0.875).into(),
23738 font_weight: settings.ui_font.weight,
23739 line_height: relative(settings.buffer_line_height.value()),
23740 ..Default::default()
23741 },
23742 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23743 color: cx.theme().colors().editor_foreground,
23744 font_family: settings.buffer_font.family.clone(),
23745 font_features: settings.buffer_font.features.clone(),
23746 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23747 font_size: settings.buffer_font_size(cx).into(),
23748 font_weight: settings.buffer_font.weight,
23749 line_height: relative(settings.buffer_line_height.value()),
23750 ..Default::default()
23751 },
23752 };
23753 if let Some(text_style_refinement) = &self.text_style_refinement {
23754 text_style.refine(text_style_refinement)
23755 }
23756
23757 let background = match self.mode {
23758 EditorMode::SingleLine => cx.theme().system().transparent,
23759 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23760 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23761 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23762 };
23763
23764 EditorElement::new(
23765 &cx.entity(),
23766 EditorStyle {
23767 background,
23768 border: cx.theme().colors().border,
23769 local_player: cx.theme().players().local(),
23770 text: text_style,
23771 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23772 syntax: cx.theme().syntax().clone(),
23773 status: cx.theme().status().clone(),
23774 inlay_hints_style: make_inlay_hints_style(cx),
23775 edit_prediction_styles: make_suggestion_styles(cx),
23776 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23777 show_underlines: self.diagnostics_enabled(),
23778 },
23779 )
23780 }
23781}
23782
23783impl EntityInputHandler for Editor {
23784 fn text_for_range(
23785 &mut self,
23786 range_utf16: Range<usize>,
23787 adjusted_range: &mut Option<Range<usize>>,
23788 _: &mut Window,
23789 cx: &mut Context<Self>,
23790 ) -> Option<String> {
23791 let snapshot = self.buffer.read(cx).read(cx);
23792 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23793 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23794 if (start.0..end.0) != range_utf16 {
23795 adjusted_range.replace(start.0..end.0);
23796 }
23797 Some(snapshot.text_for_range(start..end).collect())
23798 }
23799
23800 fn selected_text_range(
23801 &mut self,
23802 ignore_disabled_input: bool,
23803 _: &mut Window,
23804 cx: &mut Context<Self>,
23805 ) -> Option<UTF16Selection> {
23806 // Prevent the IME menu from appearing when holding down an alphabetic key
23807 // while input is disabled.
23808 if !ignore_disabled_input && !self.input_enabled {
23809 return None;
23810 }
23811
23812 let selection = self.selections.newest::<OffsetUtf16>(cx);
23813 let range = selection.range();
23814
23815 Some(UTF16Selection {
23816 range: range.start.0..range.end.0,
23817 reversed: selection.reversed,
23818 })
23819 }
23820
23821 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23822 let snapshot = self.buffer.read(cx).read(cx);
23823 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23824 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23825 }
23826
23827 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23828 self.clear_highlights::<InputComposition>(cx);
23829 self.ime_transaction.take();
23830 }
23831
23832 fn replace_text_in_range(
23833 &mut self,
23834 range_utf16: Option<Range<usize>>,
23835 text: &str,
23836 window: &mut Window,
23837 cx: &mut Context<Self>,
23838 ) {
23839 if !self.input_enabled {
23840 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23841 return;
23842 }
23843
23844 self.transact(window, cx, |this, window, cx| {
23845 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23846 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23847 Some(this.selection_replacement_ranges(range_utf16, cx))
23848 } else {
23849 this.marked_text_ranges(cx)
23850 };
23851
23852 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23853 let newest_selection_id = this.selections.newest_anchor().id;
23854 this.selections
23855 .all::<OffsetUtf16>(cx)
23856 .iter()
23857 .zip(ranges_to_replace.iter())
23858 .find_map(|(selection, range)| {
23859 if selection.id == newest_selection_id {
23860 Some(
23861 (range.start.0 as isize - selection.head().0 as isize)
23862 ..(range.end.0 as isize - selection.head().0 as isize),
23863 )
23864 } else {
23865 None
23866 }
23867 })
23868 });
23869
23870 cx.emit(EditorEvent::InputHandled {
23871 utf16_range_to_replace: range_to_replace,
23872 text: text.into(),
23873 });
23874
23875 if let Some(new_selected_ranges) = new_selected_ranges {
23876 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23877 selections.select_ranges(new_selected_ranges)
23878 });
23879 this.backspace(&Default::default(), window, cx);
23880 }
23881
23882 this.handle_input(text, window, cx);
23883 });
23884
23885 if let Some(transaction) = self.ime_transaction {
23886 self.buffer.update(cx, |buffer, cx| {
23887 buffer.group_until_transaction(transaction, cx);
23888 });
23889 }
23890
23891 self.unmark_text(window, cx);
23892 }
23893
23894 fn replace_and_mark_text_in_range(
23895 &mut self,
23896 range_utf16: Option<Range<usize>>,
23897 text: &str,
23898 new_selected_range_utf16: Option<Range<usize>>,
23899 window: &mut Window,
23900 cx: &mut Context<Self>,
23901 ) {
23902 if !self.input_enabled {
23903 return;
23904 }
23905
23906 let transaction = self.transact(window, cx, |this, window, cx| {
23907 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23908 let snapshot = this.buffer.read(cx).read(cx);
23909 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23910 for marked_range in &mut marked_ranges {
23911 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23912 marked_range.start.0 += relative_range_utf16.start;
23913 marked_range.start =
23914 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23915 marked_range.end =
23916 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23917 }
23918 }
23919 Some(marked_ranges)
23920 } else if let Some(range_utf16) = range_utf16 {
23921 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23922 Some(this.selection_replacement_ranges(range_utf16, cx))
23923 } else {
23924 None
23925 };
23926
23927 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23928 let newest_selection_id = this.selections.newest_anchor().id;
23929 this.selections
23930 .all::<OffsetUtf16>(cx)
23931 .iter()
23932 .zip(ranges_to_replace.iter())
23933 .find_map(|(selection, range)| {
23934 if selection.id == newest_selection_id {
23935 Some(
23936 (range.start.0 as isize - selection.head().0 as isize)
23937 ..(range.end.0 as isize - selection.head().0 as isize),
23938 )
23939 } else {
23940 None
23941 }
23942 })
23943 });
23944
23945 cx.emit(EditorEvent::InputHandled {
23946 utf16_range_to_replace: range_to_replace,
23947 text: text.into(),
23948 });
23949
23950 if let Some(ranges) = ranges_to_replace {
23951 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23952 s.select_ranges(ranges)
23953 });
23954 }
23955
23956 let marked_ranges = {
23957 let snapshot = this.buffer.read(cx).read(cx);
23958 this.selections
23959 .disjoint_anchors_arc()
23960 .iter()
23961 .map(|selection| {
23962 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23963 })
23964 .collect::<Vec<_>>()
23965 };
23966
23967 if text.is_empty() {
23968 this.unmark_text(window, cx);
23969 } else {
23970 this.highlight_text::<InputComposition>(
23971 marked_ranges.clone(),
23972 HighlightStyle {
23973 underline: Some(UnderlineStyle {
23974 thickness: px(1.),
23975 color: None,
23976 wavy: false,
23977 }),
23978 ..Default::default()
23979 },
23980 cx,
23981 );
23982 }
23983
23984 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23985 let use_autoclose = this.use_autoclose;
23986 let use_auto_surround = this.use_auto_surround;
23987 this.set_use_autoclose(false);
23988 this.set_use_auto_surround(false);
23989 this.handle_input(text, window, cx);
23990 this.set_use_autoclose(use_autoclose);
23991 this.set_use_auto_surround(use_auto_surround);
23992
23993 if let Some(new_selected_range) = new_selected_range_utf16 {
23994 let snapshot = this.buffer.read(cx).read(cx);
23995 let new_selected_ranges = marked_ranges
23996 .into_iter()
23997 .map(|marked_range| {
23998 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23999 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24000 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24001 snapshot.clip_offset_utf16(new_start, Bias::Left)
24002 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24003 })
24004 .collect::<Vec<_>>();
24005
24006 drop(snapshot);
24007 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24008 selections.select_ranges(new_selected_ranges)
24009 });
24010 }
24011 });
24012
24013 self.ime_transaction = self.ime_transaction.or(transaction);
24014 if let Some(transaction) = self.ime_transaction {
24015 self.buffer.update(cx, |buffer, cx| {
24016 buffer.group_until_transaction(transaction, cx);
24017 });
24018 }
24019
24020 if self.text_highlights::<InputComposition>(cx).is_none() {
24021 self.ime_transaction.take();
24022 }
24023 }
24024
24025 fn bounds_for_range(
24026 &mut self,
24027 range_utf16: Range<usize>,
24028 element_bounds: gpui::Bounds<Pixels>,
24029 window: &mut Window,
24030 cx: &mut Context<Self>,
24031 ) -> Option<gpui::Bounds<Pixels>> {
24032 let text_layout_details = self.text_layout_details(window);
24033 let CharacterDimensions {
24034 em_width,
24035 em_advance,
24036 line_height,
24037 } = self.character_dimensions(window);
24038
24039 let snapshot = self.snapshot(window, cx);
24040 let scroll_position = snapshot.scroll_position();
24041 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24042
24043 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24044 let x = Pixels::from(
24045 ScrollOffset::from(
24046 snapshot.x_for_display_point(start, &text_layout_details)
24047 + self.gutter_dimensions.full_width(),
24048 ) - scroll_left,
24049 );
24050 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24051
24052 Some(Bounds {
24053 origin: element_bounds.origin + point(x, y),
24054 size: size(em_width, line_height),
24055 })
24056 }
24057
24058 fn character_index_for_point(
24059 &mut self,
24060 point: gpui::Point<Pixels>,
24061 _window: &mut Window,
24062 _cx: &mut Context<Self>,
24063 ) -> Option<usize> {
24064 let position_map = self.last_position_map.as_ref()?;
24065 if !position_map.text_hitbox.contains(&point) {
24066 return None;
24067 }
24068 let display_point = position_map.point_for_position(point).previous_valid;
24069 let anchor = position_map
24070 .snapshot
24071 .display_point_to_anchor(display_point, Bias::Left);
24072 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24073 Some(utf16_offset.0)
24074 }
24075}
24076
24077trait SelectionExt {
24078 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24079 fn spanned_rows(
24080 &self,
24081 include_end_if_at_line_start: bool,
24082 map: &DisplaySnapshot,
24083 ) -> Range<MultiBufferRow>;
24084}
24085
24086impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24087 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24088 let start = self
24089 .start
24090 .to_point(map.buffer_snapshot())
24091 .to_display_point(map);
24092 let end = self
24093 .end
24094 .to_point(map.buffer_snapshot())
24095 .to_display_point(map);
24096 if self.reversed {
24097 end..start
24098 } else {
24099 start..end
24100 }
24101 }
24102
24103 fn spanned_rows(
24104 &self,
24105 include_end_if_at_line_start: bool,
24106 map: &DisplaySnapshot,
24107 ) -> Range<MultiBufferRow> {
24108 let start = self.start.to_point(map.buffer_snapshot());
24109 let mut end = self.end.to_point(map.buffer_snapshot());
24110 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24111 end.row -= 1;
24112 }
24113
24114 let buffer_start = map.prev_line_boundary(start).0;
24115 let buffer_end = map.next_line_boundary(end).0;
24116 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24117 }
24118}
24119
24120impl<T: InvalidationRegion> InvalidationStack<T> {
24121 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24122 where
24123 S: Clone + ToOffset,
24124 {
24125 while let Some(region) = self.last() {
24126 let all_selections_inside_invalidation_ranges =
24127 if selections.len() == region.ranges().len() {
24128 selections
24129 .iter()
24130 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24131 .all(|(selection, invalidation_range)| {
24132 let head = selection.head().to_offset(buffer);
24133 invalidation_range.start <= head && invalidation_range.end >= head
24134 })
24135 } else {
24136 false
24137 };
24138
24139 if all_selections_inside_invalidation_ranges {
24140 break;
24141 } else {
24142 self.pop();
24143 }
24144 }
24145 }
24146}
24147
24148impl<T> Default for InvalidationStack<T> {
24149 fn default() -> Self {
24150 Self(Default::default())
24151 }
24152}
24153
24154impl<T> Deref for InvalidationStack<T> {
24155 type Target = Vec<T>;
24156
24157 fn deref(&self) -> &Self::Target {
24158 &self.0
24159 }
24160}
24161
24162impl<T> DerefMut for InvalidationStack<T> {
24163 fn deref_mut(&mut self) -> &mut Self::Target {
24164 &mut self.0
24165 }
24166}
24167
24168impl InvalidationRegion for SnippetState {
24169 fn ranges(&self) -> &[Range<Anchor>] {
24170 &self.ranges[self.active_index]
24171 }
24172}
24173
24174fn edit_prediction_edit_text(
24175 current_snapshot: &BufferSnapshot,
24176 edits: &[(Range<Anchor>, String)],
24177 edit_preview: &EditPreview,
24178 include_deletions: bool,
24179 cx: &App,
24180) -> HighlightedText {
24181 let edits = edits
24182 .iter()
24183 .map(|(anchor, text)| {
24184 (
24185 anchor.start.text_anchor..anchor.end.text_anchor,
24186 text.clone(),
24187 )
24188 })
24189 .collect::<Vec<_>>();
24190
24191 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24192}
24193
24194fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24195 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24196 // Just show the raw edit text with basic styling
24197 let mut text = String::new();
24198 let mut highlights = Vec::new();
24199
24200 let insertion_highlight_style = HighlightStyle {
24201 color: Some(cx.theme().colors().text),
24202 ..Default::default()
24203 };
24204
24205 for (_, edit_text) in edits {
24206 let start_offset = text.len();
24207 text.push_str(edit_text);
24208 let end_offset = text.len();
24209
24210 if start_offset < end_offset {
24211 highlights.push((start_offset..end_offset, insertion_highlight_style));
24212 }
24213 }
24214
24215 HighlightedText {
24216 text: text.into(),
24217 highlights,
24218 }
24219}
24220
24221pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24222 match severity {
24223 lsp::DiagnosticSeverity::ERROR => colors.error,
24224 lsp::DiagnosticSeverity::WARNING => colors.warning,
24225 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24226 lsp::DiagnosticSeverity::HINT => colors.info,
24227 _ => colors.ignored,
24228 }
24229}
24230
24231pub fn styled_runs_for_code_label<'a>(
24232 label: &'a CodeLabel,
24233 syntax_theme: &'a theme::SyntaxTheme,
24234) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24235 let fade_out = HighlightStyle {
24236 fade_out: Some(0.35),
24237 ..Default::default()
24238 };
24239
24240 let mut prev_end = label.filter_range.end;
24241 label
24242 .runs
24243 .iter()
24244 .enumerate()
24245 .flat_map(move |(ix, (range, highlight_id))| {
24246 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24247 style
24248 } else {
24249 return Default::default();
24250 };
24251 let muted_style = style.highlight(fade_out);
24252
24253 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24254 if range.start >= label.filter_range.end {
24255 if range.start > prev_end {
24256 runs.push((prev_end..range.start, fade_out));
24257 }
24258 runs.push((range.clone(), muted_style));
24259 } else if range.end <= label.filter_range.end {
24260 runs.push((range.clone(), style));
24261 } else {
24262 runs.push((range.start..label.filter_range.end, style));
24263 runs.push((label.filter_range.end..range.end, muted_style));
24264 }
24265 prev_end = cmp::max(prev_end, range.end);
24266
24267 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24268 runs.push((prev_end..label.text.len(), fade_out));
24269 }
24270
24271 runs
24272 })
24273}
24274
24275pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24276 let mut prev_index = 0;
24277 let mut prev_codepoint: Option<char> = None;
24278 text.char_indices()
24279 .chain([(text.len(), '\0')])
24280 .filter_map(move |(index, codepoint)| {
24281 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24282 let is_boundary = index == text.len()
24283 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24284 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24285 if is_boundary {
24286 let chunk = &text[prev_index..index];
24287 prev_index = index;
24288 Some(chunk)
24289 } else {
24290 None
24291 }
24292 })
24293}
24294
24295pub trait RangeToAnchorExt: Sized {
24296 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24297
24298 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24299 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24300 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24301 }
24302}
24303
24304impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24305 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24306 let start_offset = self.start.to_offset(snapshot);
24307 let end_offset = self.end.to_offset(snapshot);
24308 if start_offset == end_offset {
24309 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24310 } else {
24311 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24312 }
24313 }
24314}
24315
24316pub trait RowExt {
24317 fn as_f64(&self) -> f64;
24318
24319 fn next_row(&self) -> Self;
24320
24321 fn previous_row(&self) -> Self;
24322
24323 fn minus(&self, other: Self) -> u32;
24324}
24325
24326impl RowExt for DisplayRow {
24327 fn as_f64(&self) -> f64 {
24328 self.0 as _
24329 }
24330
24331 fn next_row(&self) -> Self {
24332 Self(self.0 + 1)
24333 }
24334
24335 fn previous_row(&self) -> Self {
24336 Self(self.0.saturating_sub(1))
24337 }
24338
24339 fn minus(&self, other: Self) -> u32 {
24340 self.0 - other.0
24341 }
24342}
24343
24344impl RowExt for MultiBufferRow {
24345 fn as_f64(&self) -> f64 {
24346 self.0 as _
24347 }
24348
24349 fn next_row(&self) -> Self {
24350 Self(self.0 + 1)
24351 }
24352
24353 fn previous_row(&self) -> Self {
24354 Self(self.0.saturating_sub(1))
24355 }
24356
24357 fn minus(&self, other: Self) -> u32 {
24358 self.0 - other.0
24359 }
24360}
24361
24362trait RowRangeExt {
24363 type Row;
24364
24365 fn len(&self) -> usize;
24366
24367 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24368}
24369
24370impl RowRangeExt for Range<MultiBufferRow> {
24371 type Row = MultiBufferRow;
24372
24373 fn len(&self) -> usize {
24374 (self.end.0 - self.start.0) as usize
24375 }
24376
24377 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24378 (self.start.0..self.end.0).map(MultiBufferRow)
24379 }
24380}
24381
24382impl RowRangeExt for Range<DisplayRow> {
24383 type Row = DisplayRow;
24384
24385 fn len(&self) -> usize {
24386 (self.end.0 - self.start.0) as usize
24387 }
24388
24389 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24390 (self.start.0..self.end.0).map(DisplayRow)
24391 }
24392}
24393
24394/// If select range has more than one line, we
24395/// just point the cursor to range.start.
24396fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24397 if range.start.row == range.end.row {
24398 range
24399 } else {
24400 range.start..range.start
24401 }
24402}
24403pub struct KillRing(ClipboardItem);
24404impl Global for KillRing {}
24405
24406const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24407
24408enum BreakpointPromptEditAction {
24409 Log,
24410 Condition,
24411 HitCondition,
24412}
24413
24414struct BreakpointPromptEditor {
24415 pub(crate) prompt: Entity<Editor>,
24416 editor: WeakEntity<Editor>,
24417 breakpoint_anchor: Anchor,
24418 breakpoint: Breakpoint,
24419 edit_action: BreakpointPromptEditAction,
24420 block_ids: HashSet<CustomBlockId>,
24421 editor_margins: Arc<Mutex<EditorMargins>>,
24422 _subscriptions: Vec<Subscription>,
24423}
24424
24425impl BreakpointPromptEditor {
24426 const MAX_LINES: u8 = 4;
24427
24428 fn new(
24429 editor: WeakEntity<Editor>,
24430 breakpoint_anchor: Anchor,
24431 breakpoint: Breakpoint,
24432 edit_action: BreakpointPromptEditAction,
24433 window: &mut Window,
24434 cx: &mut Context<Self>,
24435 ) -> Self {
24436 let base_text = match edit_action {
24437 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24438 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24439 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24440 }
24441 .map(|msg| msg.to_string())
24442 .unwrap_or_default();
24443
24444 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24445 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24446
24447 let prompt = cx.new(|cx| {
24448 let mut prompt = Editor::new(
24449 EditorMode::AutoHeight {
24450 min_lines: 1,
24451 max_lines: Some(Self::MAX_LINES as usize),
24452 },
24453 buffer,
24454 None,
24455 window,
24456 cx,
24457 );
24458 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24459 prompt.set_show_cursor_when_unfocused(false, cx);
24460 prompt.set_placeholder_text(
24461 match edit_action {
24462 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24463 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24464 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24465 },
24466 window,
24467 cx,
24468 );
24469
24470 prompt
24471 });
24472
24473 Self {
24474 prompt,
24475 editor,
24476 breakpoint_anchor,
24477 breakpoint,
24478 edit_action,
24479 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24480 block_ids: Default::default(),
24481 _subscriptions: vec![],
24482 }
24483 }
24484
24485 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24486 self.block_ids.extend(block_ids)
24487 }
24488
24489 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24490 if let Some(editor) = self.editor.upgrade() {
24491 let message = self
24492 .prompt
24493 .read(cx)
24494 .buffer
24495 .read(cx)
24496 .as_singleton()
24497 .expect("A multi buffer in breakpoint prompt isn't possible")
24498 .read(cx)
24499 .as_rope()
24500 .to_string();
24501
24502 editor.update(cx, |editor, cx| {
24503 editor.edit_breakpoint_at_anchor(
24504 self.breakpoint_anchor,
24505 self.breakpoint.clone(),
24506 match self.edit_action {
24507 BreakpointPromptEditAction::Log => {
24508 BreakpointEditAction::EditLogMessage(message.into())
24509 }
24510 BreakpointPromptEditAction::Condition => {
24511 BreakpointEditAction::EditCondition(message.into())
24512 }
24513 BreakpointPromptEditAction::HitCondition => {
24514 BreakpointEditAction::EditHitCondition(message.into())
24515 }
24516 },
24517 cx,
24518 );
24519
24520 editor.remove_blocks(self.block_ids.clone(), None, cx);
24521 cx.focus_self(window);
24522 });
24523 }
24524 }
24525
24526 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24527 self.editor
24528 .update(cx, |editor, cx| {
24529 editor.remove_blocks(self.block_ids.clone(), None, cx);
24530 window.focus(&editor.focus_handle);
24531 })
24532 .log_err();
24533 }
24534
24535 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24536 let settings = ThemeSettings::get_global(cx);
24537 let text_style = TextStyle {
24538 color: if self.prompt.read(cx).read_only(cx) {
24539 cx.theme().colors().text_disabled
24540 } else {
24541 cx.theme().colors().text
24542 },
24543 font_family: settings.buffer_font.family.clone(),
24544 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24545 font_size: settings.buffer_font_size(cx).into(),
24546 font_weight: settings.buffer_font.weight,
24547 line_height: relative(settings.buffer_line_height.value()),
24548 ..Default::default()
24549 };
24550 EditorElement::new(
24551 &self.prompt,
24552 EditorStyle {
24553 background: cx.theme().colors().editor_background,
24554 local_player: cx.theme().players().local(),
24555 text: text_style,
24556 ..Default::default()
24557 },
24558 )
24559 }
24560}
24561
24562impl Render for BreakpointPromptEditor {
24563 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24564 let editor_margins = *self.editor_margins.lock();
24565 let gutter_dimensions = editor_margins.gutter;
24566 h_flex()
24567 .key_context("Editor")
24568 .bg(cx.theme().colors().editor_background)
24569 .border_y_1()
24570 .border_color(cx.theme().status().info_border)
24571 .size_full()
24572 .py(window.line_height() / 2.5)
24573 .on_action(cx.listener(Self::confirm))
24574 .on_action(cx.listener(Self::cancel))
24575 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24576 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24577 }
24578}
24579
24580impl Focusable for BreakpointPromptEditor {
24581 fn focus_handle(&self, cx: &App) -> FocusHandle {
24582 self.prompt.focus_handle(cx)
24583 }
24584}
24585
24586fn all_edits_insertions_or_deletions(
24587 edits: &Vec<(Range<Anchor>, String)>,
24588 snapshot: &MultiBufferSnapshot,
24589) -> bool {
24590 let mut all_insertions = true;
24591 let mut all_deletions = true;
24592
24593 for (range, new_text) in edits.iter() {
24594 let range_is_empty = range.to_offset(snapshot).is_empty();
24595 let text_is_empty = new_text.is_empty();
24596
24597 if range_is_empty != text_is_empty {
24598 if range_is_empty {
24599 all_deletions = false;
24600 } else {
24601 all_insertions = false;
24602 }
24603 } else {
24604 return false;
24605 }
24606
24607 if !all_insertions && !all_deletions {
24608 return false;
24609 }
24610 }
24611 all_insertions || all_deletions
24612}
24613
24614struct MissingEditPredictionKeybindingTooltip;
24615
24616impl Render for MissingEditPredictionKeybindingTooltip {
24617 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24618 ui::tooltip_container(cx, |container, cx| {
24619 container
24620 .flex_shrink_0()
24621 .max_w_80()
24622 .min_h(rems_from_px(124.))
24623 .justify_between()
24624 .child(
24625 v_flex()
24626 .flex_1()
24627 .text_ui_sm(cx)
24628 .child(Label::new("Conflict with Accept Keybinding"))
24629 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24630 )
24631 .child(
24632 h_flex()
24633 .pb_1()
24634 .gap_1()
24635 .items_end()
24636 .w_full()
24637 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24638 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24639 }))
24640 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24641 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24642 })),
24643 )
24644 })
24645 }
24646}
24647
24648#[derive(Debug, Clone, Copy, PartialEq)]
24649pub struct LineHighlight {
24650 pub background: Background,
24651 pub border: Option<gpui::Hsla>,
24652 pub include_gutter: bool,
24653 pub type_id: Option<TypeId>,
24654}
24655
24656struct LineManipulationResult {
24657 pub new_text: String,
24658 pub line_count_before: usize,
24659 pub line_count_after: usize,
24660}
24661
24662fn render_diff_hunk_controls(
24663 row: u32,
24664 status: &DiffHunkStatus,
24665 hunk_range: Range<Anchor>,
24666 is_created_file: bool,
24667 line_height: Pixels,
24668 editor: &Entity<Editor>,
24669 _window: &mut Window,
24670 cx: &mut App,
24671) -> AnyElement {
24672 h_flex()
24673 .h(line_height)
24674 .mr_1()
24675 .gap_1()
24676 .px_0p5()
24677 .pb_1()
24678 .border_x_1()
24679 .border_b_1()
24680 .border_color(cx.theme().colors().border_variant)
24681 .rounded_b_lg()
24682 .bg(cx.theme().colors().editor_background)
24683 .gap_1()
24684 .block_mouse_except_scroll()
24685 .shadow_md()
24686 .child(if status.has_secondary_hunk() {
24687 Button::new(("stage", row as u64), "Stage")
24688 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24689 .tooltip({
24690 let focus_handle = editor.focus_handle(cx);
24691 move |window, cx| {
24692 Tooltip::for_action_in(
24693 "Stage Hunk",
24694 &::git::ToggleStaged,
24695 &focus_handle,
24696 window,
24697 cx,
24698 )
24699 }
24700 })
24701 .on_click({
24702 let editor = editor.clone();
24703 move |_event, _window, cx| {
24704 editor.update(cx, |editor, cx| {
24705 editor.stage_or_unstage_diff_hunks(
24706 true,
24707 vec![hunk_range.start..hunk_range.start],
24708 cx,
24709 );
24710 });
24711 }
24712 })
24713 } else {
24714 Button::new(("unstage", row as u64), "Unstage")
24715 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24716 .tooltip({
24717 let focus_handle = editor.focus_handle(cx);
24718 move |window, cx| {
24719 Tooltip::for_action_in(
24720 "Unstage Hunk",
24721 &::git::ToggleStaged,
24722 &focus_handle,
24723 window,
24724 cx,
24725 )
24726 }
24727 })
24728 .on_click({
24729 let editor = editor.clone();
24730 move |_event, _window, cx| {
24731 editor.update(cx, |editor, cx| {
24732 editor.stage_or_unstage_diff_hunks(
24733 false,
24734 vec![hunk_range.start..hunk_range.start],
24735 cx,
24736 );
24737 });
24738 }
24739 })
24740 })
24741 .child(
24742 Button::new(("restore", row as u64), "Restore")
24743 .tooltip({
24744 let focus_handle = editor.focus_handle(cx);
24745 move |window, cx| {
24746 Tooltip::for_action_in(
24747 "Restore Hunk",
24748 &::git::Restore,
24749 &focus_handle,
24750 window,
24751 cx,
24752 )
24753 }
24754 })
24755 .on_click({
24756 let editor = editor.clone();
24757 move |_event, window, cx| {
24758 editor.update(cx, |editor, cx| {
24759 let snapshot = editor.snapshot(window, cx);
24760 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24761 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24762 });
24763 }
24764 })
24765 .disabled(is_created_file),
24766 )
24767 .when(
24768 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24769 |el| {
24770 el.child(
24771 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24772 .shape(IconButtonShape::Square)
24773 .icon_size(IconSize::Small)
24774 // .disabled(!has_multiple_hunks)
24775 .tooltip({
24776 let focus_handle = editor.focus_handle(cx);
24777 move |window, cx| {
24778 Tooltip::for_action_in(
24779 "Next Hunk",
24780 &GoToHunk,
24781 &focus_handle,
24782 window,
24783 cx,
24784 )
24785 }
24786 })
24787 .on_click({
24788 let editor = editor.clone();
24789 move |_event, window, cx| {
24790 editor.update(cx, |editor, cx| {
24791 let snapshot = editor.snapshot(window, cx);
24792 let position =
24793 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24794 editor.go_to_hunk_before_or_after_position(
24795 &snapshot,
24796 position,
24797 Direction::Next,
24798 window,
24799 cx,
24800 );
24801 editor.expand_selected_diff_hunks(cx);
24802 });
24803 }
24804 }),
24805 )
24806 .child(
24807 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24808 .shape(IconButtonShape::Square)
24809 .icon_size(IconSize::Small)
24810 // .disabled(!has_multiple_hunks)
24811 .tooltip({
24812 let focus_handle = editor.focus_handle(cx);
24813 move |window, cx| {
24814 Tooltip::for_action_in(
24815 "Previous Hunk",
24816 &GoToPreviousHunk,
24817 &focus_handle,
24818 window,
24819 cx,
24820 )
24821 }
24822 })
24823 .on_click({
24824 let editor = editor.clone();
24825 move |_event, window, cx| {
24826 editor.update(cx, |editor, cx| {
24827 let snapshot = editor.snapshot(window, cx);
24828 let point =
24829 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24830 editor.go_to_hunk_before_or_after_position(
24831 &snapshot,
24832 point,
24833 Direction::Prev,
24834 window,
24835 cx,
24836 );
24837 editor.expand_selected_diff_hunks(cx);
24838 });
24839 }
24840 }),
24841 )
24842 },
24843 )
24844 .into_any_element()
24845}
24846
24847pub fn multibuffer_context_lines(cx: &App) -> u32 {
24848 EditorSettings::try_get(cx)
24849 .map(|settings| settings.excerpt_context_lines)
24850 .unwrap_or(2)
24851 .min(32)
24852}