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};
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 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3251 return;
3252 };
3253
3254 let snapshot = singleton.read(cx).snapshot();
3255 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3256 let display_snapshot = display_map.snapshot(cx);
3257
3258 display_snapshot
3259 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3260 .map(|fold| {
3261 fold.range.start.text_anchor.to_point(&snapshot)
3262 ..fold.range.end.text_anchor.to_point(&snapshot)
3263 })
3264 .collect()
3265 });
3266 self.update_restoration_data(cx, |data| {
3267 data.folds = inmemory_folds;
3268 });
3269
3270 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3271 return;
3272 };
3273 let background_executor = cx.background_executor().clone();
3274 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3275 let db_folds = self.display_map.update(cx, |display_map, cx| {
3276 display_map
3277 .snapshot(cx)
3278 .folds_in_range(0..snapshot.len())
3279 .map(|fold| {
3280 (
3281 fold.range.start.text_anchor.to_offset(&snapshot),
3282 fold.range.end.text_anchor.to_offset(&snapshot),
3283 )
3284 })
3285 .collect()
3286 });
3287 self.serialize_folds = cx.background_spawn(async move {
3288 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3289 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3290 .await
3291 .with_context(|| {
3292 format!(
3293 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3294 )
3295 })
3296 .log_err();
3297 });
3298 }
3299
3300 pub fn sync_selections(
3301 &mut self,
3302 other: Entity<Editor>,
3303 cx: &mut Context<Self>,
3304 ) -> gpui::Subscription {
3305 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3306 if !other_selections.is_empty() {
3307 self.selections.change_with(cx, |selections| {
3308 selections.select_anchors(other_selections);
3309 });
3310 }
3311
3312 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3313 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3314 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3315 if other_selections.is_empty() {
3316 return;
3317 }
3318 this.selections.change_with(cx, |selections| {
3319 selections.select_anchors(other_selections);
3320 });
3321 }
3322 });
3323
3324 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3325 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3326 let these_selections = this.selections.disjoint_anchors().to_vec();
3327 if these_selections.is_empty() {
3328 return;
3329 }
3330 other.update(cx, |other_editor, cx| {
3331 other_editor.selections.change_with(cx, |selections| {
3332 selections.select_anchors(these_selections);
3333 })
3334 });
3335 }
3336 });
3337
3338 Subscription::join(other_subscription, this_subscription)
3339 }
3340
3341 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3342 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3343 /// effects of selection change occur at the end of the transaction.
3344 pub fn change_selections<R>(
3345 &mut self,
3346 effects: SelectionEffects,
3347 window: &mut Window,
3348 cx: &mut Context<Self>,
3349 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3350 ) -> R {
3351 if let Some(state) = &mut self.deferred_selection_effects_state {
3352 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3353 state.effects.completions = effects.completions;
3354 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3355 let (changed, result) = self.selections.change_with(cx, change);
3356 state.changed |= changed;
3357 return result;
3358 }
3359 let mut state = DeferredSelectionEffectsState {
3360 changed: false,
3361 effects,
3362 old_cursor_position: self.selections.newest_anchor().head(),
3363 history_entry: SelectionHistoryEntry {
3364 selections: self.selections.disjoint_anchors_arc(),
3365 select_next_state: self.select_next_state.clone(),
3366 select_prev_state: self.select_prev_state.clone(),
3367 add_selections_state: self.add_selections_state.clone(),
3368 },
3369 };
3370 let (changed, result) = self.selections.change_with(cx, change);
3371 state.changed = state.changed || changed;
3372 if self.defer_selection_effects {
3373 self.deferred_selection_effects_state = Some(state);
3374 } else {
3375 self.apply_selection_effects(state, window, cx);
3376 }
3377 result
3378 }
3379
3380 /// Defers the effects of selection change, so that the effects of multiple calls to
3381 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3382 /// to selection history and the state of popovers based on selection position aren't
3383 /// erroneously updated.
3384 pub fn with_selection_effects_deferred<R>(
3385 &mut self,
3386 window: &mut Window,
3387 cx: &mut Context<Self>,
3388 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3389 ) -> R {
3390 let already_deferred = self.defer_selection_effects;
3391 self.defer_selection_effects = true;
3392 let result = update(self, window, cx);
3393 if !already_deferred {
3394 self.defer_selection_effects = false;
3395 if let Some(state) = self.deferred_selection_effects_state.take() {
3396 self.apply_selection_effects(state, window, cx);
3397 }
3398 }
3399 result
3400 }
3401
3402 fn apply_selection_effects(
3403 &mut self,
3404 state: DeferredSelectionEffectsState,
3405 window: &mut Window,
3406 cx: &mut Context<Self>,
3407 ) {
3408 if state.changed {
3409 self.selection_history.push(state.history_entry);
3410
3411 if let Some(autoscroll) = state.effects.scroll {
3412 self.request_autoscroll(autoscroll, cx);
3413 }
3414
3415 let old_cursor_position = &state.old_cursor_position;
3416
3417 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3418
3419 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3420 self.show_signature_help(&ShowSignatureHelp, window, cx);
3421 }
3422 }
3423 }
3424
3425 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3426 where
3427 I: IntoIterator<Item = (Range<S>, T)>,
3428 S: ToOffset,
3429 T: Into<Arc<str>>,
3430 {
3431 if self.read_only(cx) {
3432 return;
3433 }
3434
3435 self.buffer
3436 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3437 }
3438
3439 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3440 where
3441 I: IntoIterator<Item = (Range<S>, T)>,
3442 S: ToOffset,
3443 T: Into<Arc<str>>,
3444 {
3445 if self.read_only(cx) {
3446 return;
3447 }
3448
3449 self.buffer.update(cx, |buffer, cx| {
3450 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3451 });
3452 }
3453
3454 pub fn edit_with_block_indent<I, S, T>(
3455 &mut self,
3456 edits: I,
3457 original_indent_columns: Vec<Option<u32>>,
3458 cx: &mut Context<Self>,
3459 ) where
3460 I: IntoIterator<Item = (Range<S>, T)>,
3461 S: ToOffset,
3462 T: Into<Arc<str>>,
3463 {
3464 if self.read_only(cx) {
3465 return;
3466 }
3467
3468 self.buffer.update(cx, |buffer, cx| {
3469 buffer.edit(
3470 edits,
3471 Some(AutoindentMode::Block {
3472 original_indent_columns,
3473 }),
3474 cx,
3475 )
3476 });
3477 }
3478
3479 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3480 self.hide_context_menu(window, cx);
3481
3482 match phase {
3483 SelectPhase::Begin {
3484 position,
3485 add,
3486 click_count,
3487 } => self.begin_selection(position, add, click_count, window, cx),
3488 SelectPhase::BeginColumnar {
3489 position,
3490 goal_column,
3491 reset,
3492 mode,
3493 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3494 SelectPhase::Extend {
3495 position,
3496 click_count,
3497 } => self.extend_selection(position, click_count, window, cx),
3498 SelectPhase::Update {
3499 position,
3500 goal_column,
3501 scroll_delta,
3502 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3503 SelectPhase::End => self.end_selection(window, cx),
3504 }
3505 }
3506
3507 fn extend_selection(
3508 &mut self,
3509 position: DisplayPoint,
3510 click_count: usize,
3511 window: &mut Window,
3512 cx: &mut Context<Self>,
3513 ) {
3514 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3515 let tail = self.selections.newest::<usize>(cx).tail();
3516 self.begin_selection(position, false, click_count, window, cx);
3517
3518 let position = position.to_offset(&display_map, Bias::Left);
3519 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3520
3521 let mut pending_selection = self
3522 .selections
3523 .pending_anchor()
3524 .cloned()
3525 .expect("extend_selection not called with pending selection");
3526 if position >= tail {
3527 pending_selection.start = tail_anchor;
3528 } else {
3529 pending_selection.end = tail_anchor;
3530 pending_selection.reversed = true;
3531 }
3532
3533 let mut pending_mode = self.selections.pending_mode().unwrap();
3534 match &mut pending_mode {
3535 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3536 _ => {}
3537 }
3538
3539 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3540 SelectionEffects::scroll(Autoscroll::fit())
3541 } else {
3542 SelectionEffects::no_scroll()
3543 };
3544
3545 self.change_selections(effects, window, cx, |s| {
3546 s.set_pending(pending_selection.clone(), pending_mode)
3547 });
3548 }
3549
3550 fn begin_selection(
3551 &mut self,
3552 position: DisplayPoint,
3553 add: bool,
3554 click_count: usize,
3555 window: &mut Window,
3556 cx: &mut Context<Self>,
3557 ) {
3558 if !self.focus_handle.is_focused(window) {
3559 self.last_focused_descendant = None;
3560 window.focus(&self.focus_handle);
3561 }
3562
3563 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3564 let buffer = &display_map.buffer_snapshot;
3565 let position = display_map.clip_point(position, Bias::Left);
3566
3567 let start;
3568 let end;
3569 let mode;
3570 let mut auto_scroll;
3571 match click_count {
3572 1 => {
3573 start = buffer.anchor_before(position.to_point(&display_map));
3574 end = start;
3575 mode = SelectMode::Character;
3576 auto_scroll = true;
3577 }
3578 2 => {
3579 let position = display_map
3580 .clip_point(position, Bias::Left)
3581 .to_offset(&display_map, Bias::Left);
3582 let (range, _) = buffer.surrounding_word(position, None);
3583 start = buffer.anchor_before(range.start);
3584 end = buffer.anchor_before(range.end);
3585 mode = SelectMode::Word(start..end);
3586 auto_scroll = true;
3587 }
3588 3 => {
3589 let position = display_map
3590 .clip_point(position, Bias::Left)
3591 .to_point(&display_map);
3592 let line_start = display_map.prev_line_boundary(position).0;
3593 let next_line_start = buffer.clip_point(
3594 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3595 Bias::Left,
3596 );
3597 start = buffer.anchor_before(line_start);
3598 end = buffer.anchor_before(next_line_start);
3599 mode = SelectMode::Line(start..end);
3600 auto_scroll = true;
3601 }
3602 _ => {
3603 start = buffer.anchor_before(0);
3604 end = buffer.anchor_before(buffer.len());
3605 mode = SelectMode::All;
3606 auto_scroll = false;
3607 }
3608 }
3609 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3610
3611 let point_to_delete: Option<usize> = {
3612 let selected_points: Vec<Selection<Point>> =
3613 self.selections.disjoint_in_range(start..end, cx);
3614
3615 if !add || click_count > 1 {
3616 None
3617 } else if !selected_points.is_empty() {
3618 Some(selected_points[0].id)
3619 } else {
3620 let clicked_point_already_selected =
3621 self.selections.disjoint_anchors().iter().find(|selection| {
3622 selection.start.to_point(buffer) == start.to_point(buffer)
3623 || selection.end.to_point(buffer) == end.to_point(buffer)
3624 });
3625
3626 clicked_point_already_selected.map(|selection| selection.id)
3627 }
3628 };
3629
3630 let selections_count = self.selections.count();
3631 let effects = if auto_scroll {
3632 SelectionEffects::default()
3633 } else {
3634 SelectionEffects::no_scroll()
3635 };
3636
3637 self.change_selections(effects, window, cx, |s| {
3638 if let Some(point_to_delete) = point_to_delete {
3639 s.delete(point_to_delete);
3640
3641 if selections_count == 1 {
3642 s.set_pending_anchor_range(start..end, mode);
3643 }
3644 } else {
3645 if !add {
3646 s.clear_disjoint();
3647 }
3648
3649 s.set_pending_anchor_range(start..end, mode);
3650 }
3651 });
3652 }
3653
3654 fn begin_columnar_selection(
3655 &mut self,
3656 position: DisplayPoint,
3657 goal_column: u32,
3658 reset: bool,
3659 mode: ColumnarMode,
3660 window: &mut Window,
3661 cx: &mut Context<Self>,
3662 ) {
3663 if !self.focus_handle.is_focused(window) {
3664 self.last_focused_descendant = None;
3665 window.focus(&self.focus_handle);
3666 }
3667
3668 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3669
3670 if reset {
3671 let pointer_position = display_map
3672 .buffer_snapshot
3673 .anchor_before(position.to_point(&display_map));
3674
3675 self.change_selections(
3676 SelectionEffects::scroll(Autoscroll::newest()),
3677 window,
3678 cx,
3679 |s| {
3680 s.clear_disjoint();
3681 s.set_pending_anchor_range(
3682 pointer_position..pointer_position,
3683 SelectMode::Character,
3684 );
3685 },
3686 );
3687 };
3688
3689 let tail = self.selections.newest::<Point>(cx).tail();
3690 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3691 self.columnar_selection_state = match mode {
3692 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3693 selection_tail: selection_anchor,
3694 display_point: if reset {
3695 if position.column() != goal_column {
3696 Some(DisplayPoint::new(position.row(), goal_column))
3697 } else {
3698 None
3699 }
3700 } else {
3701 None
3702 },
3703 }),
3704 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3705 selection_tail: selection_anchor,
3706 }),
3707 };
3708
3709 if !reset {
3710 self.select_columns(position, goal_column, &display_map, window, cx);
3711 }
3712 }
3713
3714 fn update_selection(
3715 &mut self,
3716 position: DisplayPoint,
3717 goal_column: u32,
3718 scroll_delta: gpui::Point<f32>,
3719 window: &mut Window,
3720 cx: &mut Context<Self>,
3721 ) {
3722 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3723
3724 if self.columnar_selection_state.is_some() {
3725 self.select_columns(position, goal_column, &display_map, window, cx);
3726 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3727 let buffer = &display_map.buffer_snapshot;
3728 let head;
3729 let tail;
3730 let mode = self.selections.pending_mode().unwrap();
3731 match &mode {
3732 SelectMode::Character => {
3733 head = position.to_point(&display_map);
3734 tail = pending.tail().to_point(buffer);
3735 }
3736 SelectMode::Word(original_range) => {
3737 let offset = display_map
3738 .clip_point(position, Bias::Left)
3739 .to_offset(&display_map, Bias::Left);
3740 let original_range = original_range.to_offset(buffer);
3741
3742 let head_offset = if buffer.is_inside_word(offset, None)
3743 || original_range.contains(&offset)
3744 {
3745 let (word_range, _) = buffer.surrounding_word(offset, None);
3746 if word_range.start < original_range.start {
3747 word_range.start
3748 } else {
3749 word_range.end
3750 }
3751 } else {
3752 offset
3753 };
3754
3755 head = head_offset.to_point(buffer);
3756 if head_offset <= original_range.start {
3757 tail = original_range.end.to_point(buffer);
3758 } else {
3759 tail = original_range.start.to_point(buffer);
3760 }
3761 }
3762 SelectMode::Line(original_range) => {
3763 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3764
3765 let position = display_map
3766 .clip_point(position, Bias::Left)
3767 .to_point(&display_map);
3768 let line_start = display_map.prev_line_boundary(position).0;
3769 let next_line_start = buffer.clip_point(
3770 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3771 Bias::Left,
3772 );
3773
3774 if line_start < original_range.start {
3775 head = line_start
3776 } else {
3777 head = next_line_start
3778 }
3779
3780 if head <= original_range.start {
3781 tail = original_range.end;
3782 } else {
3783 tail = original_range.start;
3784 }
3785 }
3786 SelectMode::All => {
3787 return;
3788 }
3789 };
3790
3791 if head < tail {
3792 pending.start = buffer.anchor_before(head);
3793 pending.end = buffer.anchor_before(tail);
3794 pending.reversed = true;
3795 } else {
3796 pending.start = buffer.anchor_before(tail);
3797 pending.end = buffer.anchor_before(head);
3798 pending.reversed = false;
3799 }
3800
3801 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3802 s.set_pending(pending.clone(), mode);
3803 });
3804 } else {
3805 log::error!("update_selection dispatched with no pending selection");
3806 return;
3807 }
3808
3809 self.apply_scroll_delta(scroll_delta, window, cx);
3810 cx.notify();
3811 }
3812
3813 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3814 self.columnar_selection_state.take();
3815 if self.selections.pending_anchor().is_some() {
3816 let selections = self.selections.all::<usize>(cx);
3817 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3818 s.select(selections);
3819 s.clear_pending();
3820 });
3821 }
3822 }
3823
3824 fn select_columns(
3825 &mut self,
3826 head: DisplayPoint,
3827 goal_column: u32,
3828 display_map: &DisplaySnapshot,
3829 window: &mut Window,
3830 cx: &mut Context<Self>,
3831 ) {
3832 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3833 return;
3834 };
3835
3836 let tail = match columnar_state {
3837 ColumnarSelectionState::FromMouse {
3838 selection_tail,
3839 display_point,
3840 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3841 ColumnarSelectionState::FromSelection { selection_tail } => {
3842 selection_tail.to_display_point(display_map)
3843 }
3844 };
3845
3846 let start_row = cmp::min(tail.row(), head.row());
3847 let end_row = cmp::max(tail.row(), head.row());
3848 let start_column = cmp::min(tail.column(), goal_column);
3849 let end_column = cmp::max(tail.column(), goal_column);
3850 let reversed = start_column < tail.column();
3851
3852 let selection_ranges = (start_row.0..=end_row.0)
3853 .map(DisplayRow)
3854 .filter_map(|row| {
3855 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3856 || start_column <= display_map.line_len(row))
3857 && !display_map.is_block_line(row)
3858 {
3859 let start = display_map
3860 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3861 .to_point(display_map);
3862 let end = display_map
3863 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3864 .to_point(display_map);
3865 if reversed {
3866 Some(end..start)
3867 } else {
3868 Some(start..end)
3869 }
3870 } else {
3871 None
3872 }
3873 })
3874 .collect::<Vec<_>>();
3875
3876 let ranges = match columnar_state {
3877 ColumnarSelectionState::FromMouse { .. } => {
3878 let mut non_empty_ranges = selection_ranges
3879 .iter()
3880 .filter(|selection_range| selection_range.start != selection_range.end)
3881 .peekable();
3882 if non_empty_ranges.peek().is_some() {
3883 non_empty_ranges.cloned().collect()
3884 } else {
3885 selection_ranges
3886 }
3887 }
3888 _ => selection_ranges,
3889 };
3890
3891 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3892 s.select_ranges(ranges);
3893 });
3894 cx.notify();
3895 }
3896
3897 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3898 self.selections
3899 .all_adjusted(cx)
3900 .iter()
3901 .any(|selection| !selection.is_empty())
3902 }
3903
3904 pub fn has_pending_nonempty_selection(&self) -> bool {
3905 let pending_nonempty_selection = match self.selections.pending_anchor() {
3906 Some(Selection { start, end, .. }) => start != end,
3907 None => false,
3908 };
3909
3910 pending_nonempty_selection
3911 || (self.columnar_selection_state.is_some()
3912 && self.selections.disjoint_anchors().len() > 1)
3913 }
3914
3915 pub fn has_pending_selection(&self) -> bool {
3916 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3917 }
3918
3919 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3920 self.selection_mark_mode = false;
3921 self.selection_drag_state = SelectionDragState::None;
3922
3923 if self.clear_expanded_diff_hunks(cx) {
3924 cx.notify();
3925 return;
3926 }
3927 if self.dismiss_menus_and_popups(true, window, cx) {
3928 return;
3929 }
3930
3931 if self.mode.is_full()
3932 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3933 {
3934 return;
3935 }
3936
3937 cx.propagate();
3938 }
3939
3940 pub fn dismiss_menus_and_popups(
3941 &mut self,
3942 is_user_requested: bool,
3943 window: &mut Window,
3944 cx: &mut Context<Self>,
3945 ) -> bool {
3946 if self.take_rename(false, window, cx).is_some() {
3947 return true;
3948 }
3949
3950 if hide_hover(self, cx) {
3951 return true;
3952 }
3953
3954 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3955 return true;
3956 }
3957
3958 if self.hide_context_menu(window, cx).is_some() {
3959 return true;
3960 }
3961
3962 if self.mouse_context_menu.take().is_some() {
3963 return true;
3964 }
3965
3966 if is_user_requested && self.discard_edit_prediction(true, cx) {
3967 return true;
3968 }
3969
3970 if self.snippet_stack.pop().is_some() {
3971 return true;
3972 }
3973
3974 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3975 self.dismiss_diagnostics(cx);
3976 return true;
3977 }
3978
3979 false
3980 }
3981
3982 fn linked_editing_ranges_for(
3983 &self,
3984 selection: Range<text::Anchor>,
3985 cx: &App,
3986 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3987 if self.linked_edit_ranges.is_empty() {
3988 return None;
3989 }
3990 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3991 selection.end.buffer_id.and_then(|end_buffer_id| {
3992 if selection.start.buffer_id != Some(end_buffer_id) {
3993 return None;
3994 }
3995 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3996 let snapshot = buffer.read(cx).snapshot();
3997 self.linked_edit_ranges
3998 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3999 .map(|ranges| (ranges, snapshot, buffer))
4000 })?;
4001 use text::ToOffset as TO;
4002 // find offset from the start of current range to current cursor position
4003 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4004
4005 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4006 let start_difference = start_offset - start_byte_offset;
4007 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4008 let end_difference = end_offset - start_byte_offset;
4009 // Current range has associated linked ranges.
4010 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4011 for range in linked_ranges.iter() {
4012 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4013 let end_offset = start_offset + end_difference;
4014 let start_offset = start_offset + start_difference;
4015 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4016 continue;
4017 }
4018 if self.selections.disjoint_anchor_ranges().any(|s| {
4019 if s.start.buffer_id != selection.start.buffer_id
4020 || s.end.buffer_id != selection.end.buffer_id
4021 {
4022 return false;
4023 }
4024 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4025 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4026 }) {
4027 continue;
4028 }
4029 let start = buffer_snapshot.anchor_after(start_offset);
4030 let end = buffer_snapshot.anchor_after(end_offset);
4031 linked_edits
4032 .entry(buffer.clone())
4033 .or_default()
4034 .push(start..end);
4035 }
4036 Some(linked_edits)
4037 }
4038
4039 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4040 let text: Arc<str> = text.into();
4041
4042 if self.read_only(cx) {
4043 return;
4044 }
4045
4046 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4047
4048 let selections = self.selections.all_adjusted(cx);
4049 let mut bracket_inserted = false;
4050 let mut edits = Vec::new();
4051 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4052 let mut new_selections = Vec::with_capacity(selections.len());
4053 let mut new_autoclose_regions = Vec::new();
4054 let snapshot = self.buffer.read(cx).read(cx);
4055 let mut clear_linked_edit_ranges = false;
4056
4057 for (selection, autoclose_region) in
4058 self.selections_with_autoclose_regions(selections, &snapshot)
4059 {
4060 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4061 // Determine if the inserted text matches the opening or closing
4062 // bracket of any of this language's bracket pairs.
4063 let mut bracket_pair = None;
4064 let mut is_bracket_pair_start = false;
4065 let mut is_bracket_pair_end = false;
4066 if !text.is_empty() {
4067 let mut bracket_pair_matching_end = None;
4068 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4069 // and they are removing the character that triggered IME popup.
4070 for (pair, enabled) in scope.brackets() {
4071 if !pair.close && !pair.surround {
4072 continue;
4073 }
4074
4075 if enabled && pair.start.ends_with(text.as_ref()) {
4076 let prefix_len = pair.start.len() - text.len();
4077 let preceding_text_matches_prefix = prefix_len == 0
4078 || (selection.start.column >= (prefix_len as u32)
4079 && snapshot.contains_str_at(
4080 Point::new(
4081 selection.start.row,
4082 selection.start.column - (prefix_len as u32),
4083 ),
4084 &pair.start[..prefix_len],
4085 ));
4086 if preceding_text_matches_prefix {
4087 bracket_pair = Some(pair.clone());
4088 is_bracket_pair_start = true;
4089 break;
4090 }
4091 }
4092 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4093 {
4094 // take first bracket pair matching end, but don't break in case a later bracket
4095 // pair matches start
4096 bracket_pair_matching_end = Some(pair.clone());
4097 }
4098 }
4099 if let Some(end) = bracket_pair_matching_end
4100 && bracket_pair.is_none()
4101 {
4102 bracket_pair = Some(end);
4103 is_bracket_pair_end = true;
4104 }
4105 }
4106
4107 if let Some(bracket_pair) = bracket_pair {
4108 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4109 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4110 let auto_surround =
4111 self.use_auto_surround && snapshot_settings.use_auto_surround;
4112 if selection.is_empty() {
4113 if is_bracket_pair_start {
4114 // If the inserted text is a suffix of an opening bracket and the
4115 // selection is preceded by the rest of the opening bracket, then
4116 // insert the closing bracket.
4117 let following_text_allows_autoclose = snapshot
4118 .chars_at(selection.start)
4119 .next()
4120 .is_none_or(|c| scope.should_autoclose_before(c));
4121
4122 let preceding_text_allows_autoclose = selection.start.column == 0
4123 || snapshot
4124 .reversed_chars_at(selection.start)
4125 .next()
4126 .is_none_or(|c| {
4127 bracket_pair.start != bracket_pair.end
4128 || !snapshot
4129 .char_classifier_at(selection.start)
4130 .is_word(c)
4131 });
4132
4133 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4134 && bracket_pair.start.len() == 1
4135 {
4136 let target = bracket_pair.start.chars().next().unwrap();
4137 let current_line_count = snapshot
4138 .reversed_chars_at(selection.start)
4139 .take_while(|&c| c != '\n')
4140 .filter(|&c| c == target)
4141 .count();
4142 current_line_count % 2 == 1
4143 } else {
4144 false
4145 };
4146
4147 if autoclose
4148 && bracket_pair.close
4149 && following_text_allows_autoclose
4150 && preceding_text_allows_autoclose
4151 && !is_closing_quote
4152 {
4153 let anchor = snapshot.anchor_before(selection.end);
4154 new_selections.push((selection.map(|_| anchor), text.len()));
4155 new_autoclose_regions.push((
4156 anchor,
4157 text.len(),
4158 selection.id,
4159 bracket_pair.clone(),
4160 ));
4161 edits.push((
4162 selection.range(),
4163 format!("{}{}", text, bracket_pair.end).into(),
4164 ));
4165 bracket_inserted = true;
4166 continue;
4167 }
4168 }
4169
4170 if let Some(region) = autoclose_region {
4171 // If the selection is followed by an auto-inserted closing bracket,
4172 // then don't insert that closing bracket again; just move the selection
4173 // past the closing bracket.
4174 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4175 && text.as_ref() == region.pair.end.as_str()
4176 && snapshot.contains_str_at(region.range.end, text.as_ref());
4177 if should_skip {
4178 let anchor = snapshot.anchor_after(selection.end);
4179 new_selections
4180 .push((selection.map(|_| anchor), region.pair.end.len()));
4181 continue;
4182 }
4183 }
4184
4185 let always_treat_brackets_as_autoclosed = snapshot
4186 .language_settings_at(selection.start, cx)
4187 .always_treat_brackets_as_autoclosed;
4188 if always_treat_brackets_as_autoclosed
4189 && is_bracket_pair_end
4190 && snapshot.contains_str_at(selection.end, text.as_ref())
4191 {
4192 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4193 // and the inserted text is a closing bracket and the selection is followed
4194 // by the closing bracket then move the selection past the closing bracket.
4195 let anchor = snapshot.anchor_after(selection.end);
4196 new_selections.push((selection.map(|_| anchor), text.len()));
4197 continue;
4198 }
4199 }
4200 // If an opening bracket is 1 character long and is typed while
4201 // text is selected, then surround that text with the bracket pair.
4202 else if auto_surround
4203 && bracket_pair.surround
4204 && is_bracket_pair_start
4205 && bracket_pair.start.chars().count() == 1
4206 {
4207 edits.push((selection.start..selection.start, text.clone()));
4208 edits.push((
4209 selection.end..selection.end,
4210 bracket_pair.end.as_str().into(),
4211 ));
4212 bracket_inserted = true;
4213 new_selections.push((
4214 Selection {
4215 id: selection.id,
4216 start: snapshot.anchor_after(selection.start),
4217 end: snapshot.anchor_before(selection.end),
4218 reversed: selection.reversed,
4219 goal: selection.goal,
4220 },
4221 0,
4222 ));
4223 continue;
4224 }
4225 }
4226 }
4227
4228 if self.auto_replace_emoji_shortcode
4229 && selection.is_empty()
4230 && text.as_ref().ends_with(':')
4231 && let Some(possible_emoji_short_code) =
4232 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4233 && !possible_emoji_short_code.is_empty()
4234 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4235 {
4236 let emoji_shortcode_start = Point::new(
4237 selection.start.row,
4238 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4239 );
4240
4241 // Remove shortcode from buffer
4242 edits.push((
4243 emoji_shortcode_start..selection.start,
4244 "".to_string().into(),
4245 ));
4246 new_selections.push((
4247 Selection {
4248 id: selection.id,
4249 start: snapshot.anchor_after(emoji_shortcode_start),
4250 end: snapshot.anchor_before(selection.start),
4251 reversed: selection.reversed,
4252 goal: selection.goal,
4253 },
4254 0,
4255 ));
4256
4257 // Insert emoji
4258 let selection_start_anchor = snapshot.anchor_after(selection.start);
4259 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4260 edits.push((selection.start..selection.end, emoji.to_string().into()));
4261
4262 continue;
4263 }
4264
4265 // If not handling any auto-close operation, then just replace the selected
4266 // text with the given input and move the selection to the end of the
4267 // newly inserted text.
4268 let anchor = snapshot.anchor_after(selection.end);
4269 if !self.linked_edit_ranges.is_empty() {
4270 let start_anchor = snapshot.anchor_before(selection.start);
4271
4272 let is_word_char = text.chars().next().is_none_or(|char| {
4273 let classifier = snapshot
4274 .char_classifier_at(start_anchor.to_offset(&snapshot))
4275 .scope_context(Some(CharScopeContext::LinkedEdit));
4276 classifier.is_word(char)
4277 });
4278
4279 if is_word_char {
4280 if let Some(ranges) = self
4281 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4282 {
4283 for (buffer, edits) in ranges {
4284 linked_edits
4285 .entry(buffer.clone())
4286 .or_default()
4287 .extend(edits.into_iter().map(|range| (range, text.clone())));
4288 }
4289 }
4290 } else {
4291 clear_linked_edit_ranges = true;
4292 }
4293 }
4294
4295 new_selections.push((selection.map(|_| anchor), 0));
4296 edits.push((selection.start..selection.end, text.clone()));
4297 }
4298
4299 drop(snapshot);
4300
4301 self.transact(window, cx, |this, window, cx| {
4302 if clear_linked_edit_ranges {
4303 this.linked_edit_ranges.clear();
4304 }
4305 let initial_buffer_versions =
4306 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4307
4308 this.buffer.update(cx, |buffer, cx| {
4309 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4310 });
4311 for (buffer, edits) in linked_edits {
4312 buffer.update(cx, |buffer, cx| {
4313 let snapshot = buffer.snapshot();
4314 let edits = edits
4315 .into_iter()
4316 .map(|(range, text)| {
4317 use text::ToPoint as TP;
4318 let end_point = TP::to_point(&range.end, &snapshot);
4319 let start_point = TP::to_point(&range.start, &snapshot);
4320 (start_point..end_point, text)
4321 })
4322 .sorted_by_key(|(range, _)| range.start);
4323 buffer.edit(edits, None, cx);
4324 })
4325 }
4326 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4327 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4328 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4329 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4330 .zip(new_selection_deltas)
4331 .map(|(selection, delta)| Selection {
4332 id: selection.id,
4333 start: selection.start + delta,
4334 end: selection.end + delta,
4335 reversed: selection.reversed,
4336 goal: SelectionGoal::None,
4337 })
4338 .collect::<Vec<_>>();
4339
4340 let mut i = 0;
4341 for (position, delta, selection_id, pair) in new_autoclose_regions {
4342 let position = position.to_offset(&map.buffer_snapshot) + delta;
4343 let start = map.buffer_snapshot.anchor_before(position);
4344 let end = map.buffer_snapshot.anchor_after(position);
4345 while let Some(existing_state) = this.autoclose_regions.get(i) {
4346 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4347 Ordering::Less => i += 1,
4348 Ordering::Greater => break,
4349 Ordering::Equal => {
4350 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4351 Ordering::Less => i += 1,
4352 Ordering::Equal => break,
4353 Ordering::Greater => break,
4354 }
4355 }
4356 }
4357 }
4358 this.autoclose_regions.insert(
4359 i,
4360 AutocloseRegion {
4361 selection_id,
4362 range: start..end,
4363 pair,
4364 },
4365 );
4366 }
4367
4368 let had_active_edit_prediction = this.has_active_edit_prediction();
4369 this.change_selections(
4370 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4371 window,
4372 cx,
4373 |s| s.select(new_selections),
4374 );
4375
4376 if !bracket_inserted
4377 && let Some(on_type_format_task) =
4378 this.trigger_on_type_formatting(text.to_string(), window, cx)
4379 {
4380 on_type_format_task.detach_and_log_err(cx);
4381 }
4382
4383 let editor_settings = EditorSettings::get_global(cx);
4384 if bracket_inserted
4385 && (editor_settings.auto_signature_help
4386 || editor_settings.show_signature_help_after_edits)
4387 {
4388 this.show_signature_help(&ShowSignatureHelp, window, cx);
4389 }
4390
4391 let trigger_in_words =
4392 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4393 if this.hard_wrap.is_some() {
4394 let latest: Range<Point> = this.selections.newest(cx).range();
4395 if latest.is_empty()
4396 && this
4397 .buffer()
4398 .read(cx)
4399 .snapshot(cx)
4400 .line_len(MultiBufferRow(latest.start.row))
4401 == latest.start.column
4402 {
4403 this.rewrap_impl(
4404 RewrapOptions {
4405 override_language_settings: true,
4406 preserve_existing_whitespace: true,
4407 },
4408 cx,
4409 )
4410 }
4411 }
4412 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4413 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4414 this.refresh_edit_prediction(true, false, window, cx);
4415 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4416 });
4417 }
4418
4419 fn find_possible_emoji_shortcode_at_position(
4420 snapshot: &MultiBufferSnapshot,
4421 position: Point,
4422 ) -> Option<String> {
4423 let mut chars = Vec::new();
4424 let mut found_colon = false;
4425 for char in snapshot.reversed_chars_at(position).take(100) {
4426 // Found a possible emoji shortcode in the middle of the buffer
4427 if found_colon {
4428 if char.is_whitespace() {
4429 chars.reverse();
4430 return Some(chars.iter().collect());
4431 }
4432 // If the previous character is not a whitespace, we are in the middle of a word
4433 // and we only want to complete the shortcode if the word is made up of other emojis
4434 let mut containing_word = String::new();
4435 for ch in snapshot
4436 .reversed_chars_at(position)
4437 .skip(chars.len() + 1)
4438 .take(100)
4439 {
4440 if ch.is_whitespace() {
4441 break;
4442 }
4443 containing_word.push(ch);
4444 }
4445 let containing_word = containing_word.chars().rev().collect::<String>();
4446 if util::word_consists_of_emojis(containing_word.as_str()) {
4447 chars.reverse();
4448 return Some(chars.iter().collect());
4449 }
4450 }
4451
4452 if char.is_whitespace() || !char.is_ascii() {
4453 return None;
4454 }
4455 if char == ':' {
4456 found_colon = true;
4457 } else {
4458 chars.push(char);
4459 }
4460 }
4461 // Found a possible emoji shortcode at the beginning of the buffer
4462 chars.reverse();
4463 Some(chars.iter().collect())
4464 }
4465
4466 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4467 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4468 self.transact(window, cx, |this, window, cx| {
4469 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4470 let selections = this.selections.all::<usize>(cx);
4471 let multi_buffer = this.buffer.read(cx);
4472 let buffer = multi_buffer.snapshot(cx);
4473 selections
4474 .iter()
4475 .map(|selection| {
4476 let start_point = selection.start.to_point(&buffer);
4477 let mut existing_indent =
4478 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4479 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4480 let start = selection.start;
4481 let end = selection.end;
4482 let selection_is_empty = start == end;
4483 let language_scope = buffer.language_scope_at(start);
4484 let (
4485 comment_delimiter,
4486 doc_delimiter,
4487 insert_extra_newline,
4488 indent_on_newline,
4489 indent_on_extra_newline,
4490 ) = if let Some(language) = &language_scope {
4491 let mut insert_extra_newline =
4492 insert_extra_newline_brackets(&buffer, start..end, language)
4493 || insert_extra_newline_tree_sitter(&buffer, start..end);
4494
4495 // Comment extension on newline is allowed only for cursor selections
4496 let comment_delimiter = maybe!({
4497 if !selection_is_empty {
4498 return None;
4499 }
4500
4501 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4502 return None;
4503 }
4504
4505 let delimiters = language.line_comment_prefixes();
4506 let max_len_of_delimiter =
4507 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4508 let (snapshot, range) =
4509 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4510
4511 let num_of_whitespaces = snapshot
4512 .chars_for_range(range.clone())
4513 .take_while(|c| c.is_whitespace())
4514 .count();
4515 let comment_candidate = snapshot
4516 .chars_for_range(range.clone())
4517 .skip(num_of_whitespaces)
4518 .take(max_len_of_delimiter)
4519 .collect::<String>();
4520 let (delimiter, trimmed_len) = delimiters
4521 .iter()
4522 .filter_map(|delimiter| {
4523 let prefix = delimiter.trim_end();
4524 if comment_candidate.starts_with(prefix) {
4525 Some((delimiter, prefix.len()))
4526 } else {
4527 None
4528 }
4529 })
4530 .max_by_key(|(_, len)| *len)?;
4531
4532 if let Some(BlockCommentConfig {
4533 start: block_start, ..
4534 }) = language.block_comment()
4535 {
4536 let block_start_trimmed = block_start.trim_end();
4537 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4538 let line_content = snapshot
4539 .chars_for_range(range)
4540 .skip(num_of_whitespaces)
4541 .take(block_start_trimmed.len())
4542 .collect::<String>();
4543
4544 if line_content.starts_with(block_start_trimmed) {
4545 return None;
4546 }
4547 }
4548 }
4549
4550 let cursor_is_placed_after_comment_marker =
4551 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4552 if cursor_is_placed_after_comment_marker {
4553 Some(delimiter.clone())
4554 } else {
4555 None
4556 }
4557 });
4558
4559 let mut indent_on_newline = IndentSize::spaces(0);
4560 let mut indent_on_extra_newline = IndentSize::spaces(0);
4561
4562 let doc_delimiter = maybe!({
4563 if !selection_is_empty {
4564 return None;
4565 }
4566
4567 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4568 return None;
4569 }
4570
4571 let BlockCommentConfig {
4572 start: start_tag,
4573 end: end_tag,
4574 prefix: delimiter,
4575 tab_size: len,
4576 } = language.documentation_comment()?;
4577 let is_within_block_comment = buffer
4578 .language_scope_at(start_point)
4579 .is_some_and(|scope| scope.override_name() == Some("comment"));
4580 if !is_within_block_comment {
4581 return None;
4582 }
4583
4584 let (snapshot, range) =
4585 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4586
4587 let num_of_whitespaces = snapshot
4588 .chars_for_range(range.clone())
4589 .take_while(|c| c.is_whitespace())
4590 .count();
4591
4592 // 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.
4593 let column = start_point.column;
4594 let cursor_is_after_start_tag = {
4595 let start_tag_len = start_tag.len();
4596 let start_tag_line = snapshot
4597 .chars_for_range(range.clone())
4598 .skip(num_of_whitespaces)
4599 .take(start_tag_len)
4600 .collect::<String>();
4601 if start_tag_line.starts_with(start_tag.as_ref()) {
4602 num_of_whitespaces + start_tag_len <= column as usize
4603 } else {
4604 false
4605 }
4606 };
4607
4608 let cursor_is_after_delimiter = {
4609 let delimiter_trim = delimiter.trim_end();
4610 let delimiter_line = snapshot
4611 .chars_for_range(range.clone())
4612 .skip(num_of_whitespaces)
4613 .take(delimiter_trim.len())
4614 .collect::<String>();
4615 if delimiter_line.starts_with(delimiter_trim) {
4616 num_of_whitespaces + delimiter_trim.len() <= column as usize
4617 } else {
4618 false
4619 }
4620 };
4621
4622 let cursor_is_before_end_tag_if_exists = {
4623 let mut char_position = 0u32;
4624 let mut end_tag_offset = None;
4625
4626 'outer: for chunk in snapshot.text_for_range(range) {
4627 if let Some(byte_pos) = chunk.find(&**end_tag) {
4628 let chars_before_match =
4629 chunk[..byte_pos].chars().count() as u32;
4630 end_tag_offset =
4631 Some(char_position + chars_before_match);
4632 break 'outer;
4633 }
4634 char_position += chunk.chars().count() as u32;
4635 }
4636
4637 if let Some(end_tag_offset) = end_tag_offset {
4638 let cursor_is_before_end_tag = column <= end_tag_offset;
4639 if cursor_is_after_start_tag {
4640 if cursor_is_before_end_tag {
4641 insert_extra_newline = true;
4642 }
4643 let cursor_is_at_start_of_end_tag =
4644 column == end_tag_offset;
4645 if cursor_is_at_start_of_end_tag {
4646 indent_on_extra_newline.len = *len;
4647 }
4648 }
4649 cursor_is_before_end_tag
4650 } else {
4651 true
4652 }
4653 };
4654
4655 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4656 && cursor_is_before_end_tag_if_exists
4657 {
4658 if cursor_is_after_start_tag {
4659 indent_on_newline.len = *len;
4660 }
4661 Some(delimiter.clone())
4662 } else {
4663 None
4664 }
4665 });
4666
4667 (
4668 comment_delimiter,
4669 doc_delimiter,
4670 insert_extra_newline,
4671 indent_on_newline,
4672 indent_on_extra_newline,
4673 )
4674 } else {
4675 (
4676 None,
4677 None,
4678 false,
4679 IndentSize::default(),
4680 IndentSize::default(),
4681 )
4682 };
4683
4684 let prevent_auto_indent = doc_delimiter.is_some();
4685 let delimiter = comment_delimiter.or(doc_delimiter);
4686
4687 let capacity_for_delimiter =
4688 delimiter.as_deref().map(str::len).unwrap_or_default();
4689 let mut new_text = String::with_capacity(
4690 1 + capacity_for_delimiter
4691 + existing_indent.len as usize
4692 + indent_on_newline.len as usize
4693 + indent_on_extra_newline.len as usize,
4694 );
4695 new_text.push('\n');
4696 new_text.extend(existing_indent.chars());
4697 new_text.extend(indent_on_newline.chars());
4698
4699 if let Some(delimiter) = &delimiter {
4700 new_text.push_str(delimiter);
4701 }
4702
4703 if insert_extra_newline {
4704 new_text.push('\n');
4705 new_text.extend(existing_indent.chars());
4706 new_text.extend(indent_on_extra_newline.chars());
4707 }
4708
4709 let anchor = buffer.anchor_after(end);
4710 let new_selection = selection.map(|_| anchor);
4711 (
4712 ((start..end, new_text), prevent_auto_indent),
4713 (insert_extra_newline, new_selection),
4714 )
4715 })
4716 .unzip()
4717 };
4718
4719 let mut auto_indent_edits = Vec::new();
4720 let mut edits = Vec::new();
4721 for (edit, prevent_auto_indent) in edits_with_flags {
4722 if prevent_auto_indent {
4723 edits.push(edit);
4724 } else {
4725 auto_indent_edits.push(edit);
4726 }
4727 }
4728 if !edits.is_empty() {
4729 this.edit(edits, cx);
4730 }
4731 if !auto_indent_edits.is_empty() {
4732 this.edit_with_autoindent(auto_indent_edits, cx);
4733 }
4734
4735 let buffer = this.buffer.read(cx).snapshot(cx);
4736 let new_selections = selection_info
4737 .into_iter()
4738 .map(|(extra_newline_inserted, new_selection)| {
4739 let mut cursor = new_selection.end.to_point(&buffer);
4740 if extra_newline_inserted {
4741 cursor.row -= 1;
4742 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4743 }
4744 new_selection.map(|_| cursor)
4745 })
4746 .collect();
4747
4748 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4749 this.refresh_edit_prediction(true, false, window, cx);
4750 });
4751 }
4752
4753 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4754 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4755
4756 let buffer = self.buffer.read(cx);
4757 let snapshot = buffer.snapshot(cx);
4758
4759 let mut edits = Vec::new();
4760 let mut rows = Vec::new();
4761
4762 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4763 let cursor = selection.head();
4764 let row = cursor.row;
4765
4766 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4767
4768 let newline = "\n".to_string();
4769 edits.push((start_of_line..start_of_line, newline));
4770
4771 rows.push(row + rows_inserted as u32);
4772 }
4773
4774 self.transact(window, cx, |editor, window, cx| {
4775 editor.edit(edits, cx);
4776
4777 editor.change_selections(Default::default(), window, cx, |s| {
4778 let mut index = 0;
4779 s.move_cursors_with(|map, _, _| {
4780 let row = rows[index];
4781 index += 1;
4782
4783 let point = Point::new(row, 0);
4784 let boundary = map.next_line_boundary(point).1;
4785 let clipped = map.clip_point(boundary, Bias::Left);
4786
4787 (clipped, SelectionGoal::None)
4788 });
4789 });
4790
4791 let mut indent_edits = Vec::new();
4792 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4793 for row in rows {
4794 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4795 for (row, indent) in indents {
4796 if indent.len == 0 {
4797 continue;
4798 }
4799
4800 let text = match indent.kind {
4801 IndentKind::Space => " ".repeat(indent.len as usize),
4802 IndentKind::Tab => "\t".repeat(indent.len as usize),
4803 };
4804 let point = Point::new(row.0, 0);
4805 indent_edits.push((point..point, text));
4806 }
4807 }
4808 editor.edit(indent_edits, cx);
4809 });
4810 }
4811
4812 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4813 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4814
4815 let buffer = self.buffer.read(cx);
4816 let snapshot = buffer.snapshot(cx);
4817
4818 let mut edits = Vec::new();
4819 let mut rows = Vec::new();
4820 let mut rows_inserted = 0;
4821
4822 for selection in self.selections.all_adjusted(cx) {
4823 let cursor = selection.head();
4824 let row = cursor.row;
4825
4826 let point = Point::new(row + 1, 0);
4827 let start_of_line = snapshot.clip_point(point, Bias::Left);
4828
4829 let newline = "\n".to_string();
4830 edits.push((start_of_line..start_of_line, newline));
4831
4832 rows_inserted += 1;
4833 rows.push(row + rows_inserted);
4834 }
4835
4836 self.transact(window, cx, |editor, window, cx| {
4837 editor.edit(edits, cx);
4838
4839 editor.change_selections(Default::default(), window, cx, |s| {
4840 let mut index = 0;
4841 s.move_cursors_with(|map, _, _| {
4842 let row = rows[index];
4843 index += 1;
4844
4845 let point = Point::new(row, 0);
4846 let boundary = map.next_line_boundary(point).1;
4847 let clipped = map.clip_point(boundary, Bias::Left);
4848
4849 (clipped, SelectionGoal::None)
4850 });
4851 });
4852
4853 let mut indent_edits = Vec::new();
4854 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4855 for row in rows {
4856 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4857 for (row, indent) in indents {
4858 if indent.len == 0 {
4859 continue;
4860 }
4861
4862 let text = match indent.kind {
4863 IndentKind::Space => " ".repeat(indent.len as usize),
4864 IndentKind::Tab => "\t".repeat(indent.len as usize),
4865 };
4866 let point = Point::new(row.0, 0);
4867 indent_edits.push((point..point, text));
4868 }
4869 }
4870 editor.edit(indent_edits, cx);
4871 });
4872 }
4873
4874 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4875 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4876 original_indent_columns: Vec::new(),
4877 });
4878 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4879 }
4880
4881 fn insert_with_autoindent_mode(
4882 &mut self,
4883 text: &str,
4884 autoindent_mode: Option<AutoindentMode>,
4885 window: &mut Window,
4886 cx: &mut Context<Self>,
4887 ) {
4888 if self.read_only(cx) {
4889 return;
4890 }
4891
4892 let text: Arc<str> = text.into();
4893 self.transact(window, cx, |this, window, cx| {
4894 let old_selections = this.selections.all_adjusted(cx);
4895 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4896 let anchors = {
4897 let snapshot = buffer.read(cx);
4898 old_selections
4899 .iter()
4900 .map(|s| {
4901 let anchor = snapshot.anchor_after(s.head());
4902 s.map(|_| anchor)
4903 })
4904 .collect::<Vec<_>>()
4905 };
4906 buffer.edit(
4907 old_selections
4908 .iter()
4909 .map(|s| (s.start..s.end, text.clone())),
4910 autoindent_mode,
4911 cx,
4912 );
4913 anchors
4914 });
4915
4916 this.change_selections(Default::default(), window, cx, |s| {
4917 s.select_anchors(selection_anchors);
4918 });
4919
4920 cx.notify();
4921 });
4922 }
4923
4924 fn trigger_completion_on_input(
4925 &mut self,
4926 text: &str,
4927 trigger_in_words: bool,
4928 window: &mut Window,
4929 cx: &mut Context<Self>,
4930 ) {
4931 let completions_source = self
4932 .context_menu
4933 .borrow()
4934 .as_ref()
4935 .and_then(|menu| match menu {
4936 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4937 CodeContextMenu::CodeActions(_) => None,
4938 });
4939
4940 match completions_source {
4941 Some(CompletionsMenuSource::Words { .. }) => {
4942 self.open_or_update_completions_menu(
4943 Some(CompletionsMenuSource::Words {
4944 ignore_threshold: false,
4945 }),
4946 None,
4947 window,
4948 cx,
4949 );
4950 }
4951 Some(CompletionsMenuSource::Normal)
4952 | Some(CompletionsMenuSource::SnippetChoices)
4953 | None
4954 if self.is_completion_trigger(
4955 text,
4956 trigger_in_words,
4957 completions_source.is_some(),
4958 cx,
4959 ) =>
4960 {
4961 self.show_completions(
4962 &ShowCompletions {
4963 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4964 },
4965 window,
4966 cx,
4967 )
4968 }
4969 _ => {
4970 self.hide_context_menu(window, cx);
4971 }
4972 }
4973 }
4974
4975 fn is_completion_trigger(
4976 &self,
4977 text: &str,
4978 trigger_in_words: bool,
4979 menu_is_open: bool,
4980 cx: &mut Context<Self>,
4981 ) -> bool {
4982 let position = self.selections.newest_anchor().head();
4983 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4984 return false;
4985 };
4986
4987 if let Some(completion_provider) = &self.completion_provider {
4988 completion_provider.is_completion_trigger(
4989 &buffer,
4990 position.text_anchor,
4991 text,
4992 trigger_in_words,
4993 menu_is_open,
4994 cx,
4995 )
4996 } else {
4997 false
4998 }
4999 }
5000
5001 /// If any empty selections is touching the start of its innermost containing autoclose
5002 /// region, expand it to select the brackets.
5003 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5004 let selections = self.selections.all::<usize>(cx);
5005 let buffer = self.buffer.read(cx).read(cx);
5006 let new_selections = self
5007 .selections_with_autoclose_regions(selections, &buffer)
5008 .map(|(mut selection, region)| {
5009 if !selection.is_empty() {
5010 return selection;
5011 }
5012
5013 if let Some(region) = region {
5014 let mut range = region.range.to_offset(&buffer);
5015 if selection.start == range.start && range.start >= region.pair.start.len() {
5016 range.start -= region.pair.start.len();
5017 if buffer.contains_str_at(range.start, ®ion.pair.start)
5018 && buffer.contains_str_at(range.end, ®ion.pair.end)
5019 {
5020 range.end += region.pair.end.len();
5021 selection.start = range.start;
5022 selection.end = range.end;
5023
5024 return selection;
5025 }
5026 }
5027 }
5028
5029 let always_treat_brackets_as_autoclosed = buffer
5030 .language_settings_at(selection.start, cx)
5031 .always_treat_brackets_as_autoclosed;
5032
5033 if !always_treat_brackets_as_autoclosed {
5034 return selection;
5035 }
5036
5037 if let Some(scope) = buffer.language_scope_at(selection.start) {
5038 for (pair, enabled) in scope.brackets() {
5039 if !enabled || !pair.close {
5040 continue;
5041 }
5042
5043 if buffer.contains_str_at(selection.start, &pair.end) {
5044 let pair_start_len = pair.start.len();
5045 if buffer.contains_str_at(
5046 selection.start.saturating_sub(pair_start_len),
5047 &pair.start,
5048 ) {
5049 selection.start -= pair_start_len;
5050 selection.end += pair.end.len();
5051
5052 return selection;
5053 }
5054 }
5055 }
5056 }
5057
5058 selection
5059 })
5060 .collect();
5061
5062 drop(buffer);
5063 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5064 selections.select(new_selections)
5065 });
5066 }
5067
5068 /// Iterate the given selections, and for each one, find the smallest surrounding
5069 /// autoclose region. This uses the ordering of the selections and the autoclose
5070 /// regions to avoid repeated comparisons.
5071 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5072 &'a self,
5073 selections: impl IntoIterator<Item = Selection<D>>,
5074 buffer: &'a MultiBufferSnapshot,
5075 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5076 let mut i = 0;
5077 let mut regions = self.autoclose_regions.as_slice();
5078 selections.into_iter().map(move |selection| {
5079 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5080
5081 let mut enclosing = None;
5082 while let Some(pair_state) = regions.get(i) {
5083 if pair_state.range.end.to_offset(buffer) < range.start {
5084 regions = ®ions[i + 1..];
5085 i = 0;
5086 } else if pair_state.range.start.to_offset(buffer) > range.end {
5087 break;
5088 } else {
5089 if pair_state.selection_id == selection.id {
5090 enclosing = Some(pair_state);
5091 }
5092 i += 1;
5093 }
5094 }
5095
5096 (selection, enclosing)
5097 })
5098 }
5099
5100 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5101 fn invalidate_autoclose_regions(
5102 &mut self,
5103 mut selections: &[Selection<Anchor>],
5104 buffer: &MultiBufferSnapshot,
5105 ) {
5106 self.autoclose_regions.retain(|state| {
5107 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5108 return false;
5109 }
5110
5111 let mut i = 0;
5112 while let Some(selection) = selections.get(i) {
5113 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5114 selections = &selections[1..];
5115 continue;
5116 }
5117 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5118 break;
5119 }
5120 if selection.id == state.selection_id {
5121 return true;
5122 } else {
5123 i += 1;
5124 }
5125 }
5126 false
5127 });
5128 }
5129
5130 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5131 let offset = position.to_offset(buffer);
5132 let (word_range, kind) =
5133 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5134 if offset > word_range.start && kind == Some(CharKind::Word) {
5135 Some(
5136 buffer
5137 .text_for_range(word_range.start..offset)
5138 .collect::<String>(),
5139 )
5140 } else {
5141 None
5142 }
5143 }
5144
5145 pub fn toggle_inline_values(
5146 &mut self,
5147 _: &ToggleInlineValues,
5148 _: &mut Window,
5149 cx: &mut Context<Self>,
5150 ) {
5151 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5152
5153 self.refresh_inline_values(cx);
5154 }
5155
5156 pub fn toggle_inlay_hints(
5157 &mut self,
5158 _: &ToggleInlayHints,
5159 _: &mut Window,
5160 cx: &mut Context<Self>,
5161 ) {
5162 self.refresh_inlay_hints(
5163 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5164 cx,
5165 );
5166 }
5167
5168 pub fn inlay_hints_enabled(&self) -> bool {
5169 self.inlay_hint_cache.enabled
5170 }
5171
5172 pub fn inline_values_enabled(&self) -> bool {
5173 self.inline_value_cache.enabled
5174 }
5175
5176 #[cfg(any(test, feature = "test-support"))]
5177 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5178 self.display_map
5179 .read(cx)
5180 .current_inlays()
5181 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5182 .cloned()
5183 .collect()
5184 }
5185
5186 #[cfg(any(test, feature = "test-support"))]
5187 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5188 self.display_map
5189 .read(cx)
5190 .current_inlays()
5191 .cloned()
5192 .collect()
5193 }
5194
5195 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5196 if self.semantics_provider.is_none() || !self.mode.is_full() {
5197 return;
5198 }
5199
5200 let reason_description = reason.description();
5201 let ignore_debounce = matches!(
5202 reason,
5203 InlayHintRefreshReason::SettingsChange(_)
5204 | InlayHintRefreshReason::Toggle(_)
5205 | InlayHintRefreshReason::ExcerptsRemoved(_)
5206 | InlayHintRefreshReason::ModifiersChanged(_)
5207 );
5208 let (invalidate_cache, required_languages) = match reason {
5209 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5210 match self.inlay_hint_cache.modifiers_override(enabled) {
5211 Some(enabled) => {
5212 if enabled {
5213 (InvalidationStrategy::RefreshRequested, None)
5214 } else {
5215 self.splice_inlays(
5216 &self
5217 .visible_inlay_hints(cx)
5218 .iter()
5219 .map(|inlay| inlay.id)
5220 .collect::<Vec<InlayId>>(),
5221 Vec::new(),
5222 cx,
5223 );
5224 return;
5225 }
5226 }
5227 None => return,
5228 }
5229 }
5230 InlayHintRefreshReason::Toggle(enabled) => {
5231 if self.inlay_hint_cache.toggle(enabled) {
5232 if enabled {
5233 (InvalidationStrategy::RefreshRequested, None)
5234 } else {
5235 self.splice_inlays(
5236 &self
5237 .visible_inlay_hints(cx)
5238 .iter()
5239 .map(|inlay| inlay.id)
5240 .collect::<Vec<InlayId>>(),
5241 Vec::new(),
5242 cx,
5243 );
5244 return;
5245 }
5246 } else {
5247 return;
5248 }
5249 }
5250 InlayHintRefreshReason::SettingsChange(new_settings) => {
5251 match self.inlay_hint_cache.update_settings(
5252 &self.buffer,
5253 new_settings,
5254 self.visible_inlay_hints(cx),
5255 cx,
5256 ) {
5257 ControlFlow::Break(Some(InlaySplice {
5258 to_remove,
5259 to_insert,
5260 })) => {
5261 self.splice_inlays(&to_remove, to_insert, cx);
5262 return;
5263 }
5264 ControlFlow::Break(None) => return,
5265 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5266 }
5267 }
5268 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5269 if let Some(InlaySplice {
5270 to_remove,
5271 to_insert,
5272 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5273 {
5274 self.splice_inlays(&to_remove, to_insert, cx);
5275 }
5276 self.display_map.update(cx, |display_map, _| {
5277 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5278 });
5279 return;
5280 }
5281 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5282 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5283 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5284 }
5285 InlayHintRefreshReason::RefreshRequested => {
5286 (InvalidationStrategy::RefreshRequested, None)
5287 }
5288 };
5289
5290 if let Some(InlaySplice {
5291 to_remove,
5292 to_insert,
5293 }) = self.inlay_hint_cache.spawn_hint_refresh(
5294 reason_description,
5295 self.visible_excerpts(required_languages.as_ref(), cx),
5296 invalidate_cache,
5297 ignore_debounce,
5298 cx,
5299 ) {
5300 self.splice_inlays(&to_remove, to_insert, cx);
5301 }
5302 }
5303
5304 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5305 self.display_map
5306 .read(cx)
5307 .current_inlays()
5308 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5309 .cloned()
5310 .collect()
5311 }
5312
5313 pub fn visible_excerpts(
5314 &self,
5315 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5316 cx: &mut Context<Editor>,
5317 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5318 let Some(project) = self.project() else {
5319 return HashMap::default();
5320 };
5321 let project = project.read(cx);
5322 let multi_buffer = self.buffer().read(cx);
5323 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5324 let multi_buffer_visible_start = self
5325 .scroll_manager
5326 .anchor()
5327 .anchor
5328 .to_point(&multi_buffer_snapshot);
5329 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5330 multi_buffer_visible_start
5331 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5332 Bias::Left,
5333 );
5334 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5335 multi_buffer_snapshot
5336 .range_to_buffer_ranges(multi_buffer_visible_range)
5337 .into_iter()
5338 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5339 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5340 let buffer_file = project::File::from_dyn(buffer.file())?;
5341 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5342 let worktree_entry = buffer_worktree
5343 .read(cx)
5344 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5345 if worktree_entry.is_ignored {
5346 return None;
5347 }
5348
5349 let language = buffer.language()?;
5350 if let Some(restrict_to_languages) = restrict_to_languages
5351 && !restrict_to_languages.contains(language)
5352 {
5353 return None;
5354 }
5355 Some((
5356 excerpt_id,
5357 (
5358 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5359 buffer.version().clone(),
5360 excerpt_visible_range,
5361 ),
5362 ))
5363 })
5364 .collect()
5365 }
5366
5367 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5368 TextLayoutDetails {
5369 text_system: window.text_system().clone(),
5370 editor_style: self.style.clone().unwrap(),
5371 rem_size: window.rem_size(),
5372 scroll_anchor: self.scroll_manager.anchor(),
5373 visible_rows: self.visible_line_count(),
5374 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5375 }
5376 }
5377
5378 pub fn splice_inlays(
5379 &self,
5380 to_remove: &[InlayId],
5381 to_insert: Vec<Inlay>,
5382 cx: &mut Context<Self>,
5383 ) {
5384 self.display_map.update(cx, |display_map, cx| {
5385 display_map.splice_inlays(to_remove, to_insert, cx)
5386 });
5387 cx.notify();
5388 }
5389
5390 fn trigger_on_type_formatting(
5391 &self,
5392 input: String,
5393 window: &mut Window,
5394 cx: &mut Context<Self>,
5395 ) -> Option<Task<Result<()>>> {
5396 if input.len() != 1 {
5397 return None;
5398 }
5399
5400 let project = self.project()?;
5401 let position = self.selections.newest_anchor().head();
5402 let (buffer, buffer_position) = self
5403 .buffer
5404 .read(cx)
5405 .text_anchor_for_position(position, cx)?;
5406
5407 let settings = language_settings::language_settings(
5408 buffer
5409 .read(cx)
5410 .language_at(buffer_position)
5411 .map(|l| l.name()),
5412 buffer.read(cx).file(),
5413 cx,
5414 );
5415 if !settings.use_on_type_format {
5416 return None;
5417 }
5418
5419 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5420 // hence we do LSP request & edit on host side only — add formats to host's history.
5421 let push_to_lsp_host_history = true;
5422 // If this is not the host, append its history with new edits.
5423 let push_to_client_history = project.read(cx).is_via_collab();
5424
5425 let on_type_formatting = project.update(cx, |project, cx| {
5426 project.on_type_format(
5427 buffer.clone(),
5428 buffer_position,
5429 input,
5430 push_to_lsp_host_history,
5431 cx,
5432 )
5433 });
5434 Some(cx.spawn_in(window, async move |editor, cx| {
5435 if let Some(transaction) = on_type_formatting.await? {
5436 if push_to_client_history {
5437 buffer
5438 .update(cx, |buffer, _| {
5439 buffer.push_transaction(transaction, Instant::now());
5440 buffer.finalize_last_transaction();
5441 })
5442 .ok();
5443 }
5444 editor.update(cx, |editor, cx| {
5445 editor.refresh_document_highlights(cx);
5446 })?;
5447 }
5448 Ok(())
5449 }))
5450 }
5451
5452 pub fn show_word_completions(
5453 &mut self,
5454 _: &ShowWordCompletions,
5455 window: &mut Window,
5456 cx: &mut Context<Self>,
5457 ) {
5458 self.open_or_update_completions_menu(
5459 Some(CompletionsMenuSource::Words {
5460 ignore_threshold: true,
5461 }),
5462 None,
5463 window,
5464 cx,
5465 );
5466 }
5467
5468 pub fn show_completions(
5469 &mut self,
5470 options: &ShowCompletions,
5471 window: &mut Window,
5472 cx: &mut Context<Self>,
5473 ) {
5474 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5475 }
5476
5477 fn open_or_update_completions_menu(
5478 &mut self,
5479 requested_source: Option<CompletionsMenuSource>,
5480 trigger: Option<&str>,
5481 window: &mut Window,
5482 cx: &mut Context<Self>,
5483 ) {
5484 if self.pending_rename.is_some() {
5485 return;
5486 }
5487
5488 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5489
5490 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5491 // inserted and selected. To handle that case, the start of the selection is used so that
5492 // the menu starts with all choices.
5493 let position = self
5494 .selections
5495 .newest_anchor()
5496 .start
5497 .bias_right(&multibuffer_snapshot);
5498 if position.diff_base_anchor.is_some() {
5499 return;
5500 }
5501 let buffer_position = multibuffer_snapshot.anchor_before(position);
5502 let Some(buffer) = buffer_position
5503 .buffer_id
5504 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5505 else {
5506 return;
5507 };
5508 let buffer_snapshot = buffer.read(cx).snapshot();
5509
5510 let query: Option<Arc<String>> =
5511 Self::completion_query(&multibuffer_snapshot, buffer_position)
5512 .map(|query| query.into());
5513
5514 drop(multibuffer_snapshot);
5515
5516 // Hide the current completions menu when query is empty. Without this, cached
5517 // completions from before the trigger char may be reused (#32774).
5518 if query.is_none() {
5519 let menu_is_open = matches!(
5520 self.context_menu.borrow().as_ref(),
5521 Some(CodeContextMenu::Completions(_))
5522 );
5523 if menu_is_open {
5524 self.hide_context_menu(window, cx);
5525 }
5526 }
5527
5528 let mut ignore_word_threshold = false;
5529 let provider = match requested_source {
5530 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5531 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5532 ignore_word_threshold = ignore_threshold;
5533 None
5534 }
5535 Some(CompletionsMenuSource::SnippetChoices) => {
5536 log::error!("bug: SnippetChoices requested_source is not handled");
5537 None
5538 }
5539 };
5540
5541 let sort_completions = provider
5542 .as_ref()
5543 .is_some_and(|provider| provider.sort_completions());
5544
5545 let filter_completions = provider
5546 .as_ref()
5547 .is_none_or(|provider| provider.filter_completions());
5548
5549 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5550 if filter_completions {
5551 menu.filter(query.clone(), provider.clone(), window, cx);
5552 }
5553 // When `is_incomplete` is false, no need to re-query completions when the current query
5554 // is a suffix of the initial query.
5555 if !menu.is_incomplete {
5556 // If the new query is a suffix of the old query (typing more characters) and
5557 // the previous result was complete, the existing completions can be filtered.
5558 //
5559 // Note that this is always true for snippet completions.
5560 let query_matches = match (&menu.initial_query, &query) {
5561 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5562 (None, _) => true,
5563 _ => false,
5564 };
5565 if query_matches {
5566 let position_matches = if menu.initial_position == position {
5567 true
5568 } else {
5569 let snapshot = self.buffer.read(cx).read(cx);
5570 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5571 };
5572 if position_matches {
5573 return;
5574 }
5575 }
5576 }
5577 };
5578
5579 let trigger_kind = match trigger {
5580 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5581 CompletionTriggerKind::TRIGGER_CHARACTER
5582 }
5583 _ => CompletionTriggerKind::INVOKED,
5584 };
5585 let completion_context = CompletionContext {
5586 trigger_character: trigger.and_then(|trigger| {
5587 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5588 Some(String::from(trigger))
5589 } else {
5590 None
5591 }
5592 }),
5593 trigger_kind,
5594 };
5595
5596 let Anchor {
5597 excerpt_id: buffer_excerpt_id,
5598 text_anchor: buffer_position,
5599 ..
5600 } = buffer_position;
5601
5602 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5603 buffer_snapshot.surrounding_word(buffer_position, None)
5604 {
5605 let word_to_exclude = buffer_snapshot
5606 .text_for_range(word_range.clone())
5607 .collect::<String>();
5608 (
5609 buffer_snapshot.anchor_before(word_range.start)
5610 ..buffer_snapshot.anchor_after(buffer_position),
5611 Some(word_to_exclude),
5612 )
5613 } else {
5614 (buffer_position..buffer_position, None)
5615 };
5616
5617 let language = buffer_snapshot
5618 .language_at(buffer_position)
5619 .map(|language| language.name());
5620
5621 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5622 .completions
5623 .clone();
5624
5625 let show_completion_documentation = buffer_snapshot
5626 .settings_at(buffer_position, cx)
5627 .show_completion_documentation;
5628
5629 // The document can be large, so stay in reasonable bounds when searching for words,
5630 // otherwise completion pop-up might be slow to appear.
5631 const WORD_LOOKUP_ROWS: u32 = 5_000;
5632 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5633 let min_word_search = buffer_snapshot.clip_point(
5634 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5635 Bias::Left,
5636 );
5637 let max_word_search = buffer_snapshot.clip_point(
5638 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5639 Bias::Right,
5640 );
5641 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5642 ..buffer_snapshot.point_to_offset(max_word_search);
5643
5644 let skip_digits = query
5645 .as_ref()
5646 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5647
5648 let omit_word_completions = !self.word_completions_enabled
5649 || (!ignore_word_threshold
5650 && match &query {
5651 Some(query) => query.chars().count() < completion_settings.words_min_length,
5652 None => completion_settings.words_min_length != 0,
5653 });
5654
5655 let (mut words, provider_responses) = match &provider {
5656 Some(provider) => {
5657 let provider_responses = provider.completions(
5658 buffer_excerpt_id,
5659 &buffer,
5660 buffer_position,
5661 completion_context,
5662 window,
5663 cx,
5664 );
5665
5666 let words = match (omit_word_completions, completion_settings.words) {
5667 (true, _) | (_, WordsCompletionMode::Disabled) => {
5668 Task::ready(BTreeMap::default())
5669 }
5670 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5671 .background_spawn(async move {
5672 buffer_snapshot.words_in_range(WordsQuery {
5673 fuzzy_contents: None,
5674 range: word_search_range,
5675 skip_digits,
5676 })
5677 }),
5678 };
5679
5680 (words, provider_responses)
5681 }
5682 None => {
5683 let words = if omit_word_completions {
5684 Task::ready(BTreeMap::default())
5685 } else {
5686 cx.background_spawn(async move {
5687 buffer_snapshot.words_in_range(WordsQuery {
5688 fuzzy_contents: None,
5689 range: word_search_range,
5690 skip_digits,
5691 })
5692 })
5693 };
5694 (words, Task::ready(Ok(Vec::new())))
5695 }
5696 };
5697
5698 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5699
5700 let id = post_inc(&mut self.next_completion_id);
5701 let task = cx.spawn_in(window, async move |editor, cx| {
5702 let Ok(()) = editor.update(cx, |this, _| {
5703 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5704 }) else {
5705 return;
5706 };
5707
5708 // TODO: Ideally completions from different sources would be selectively re-queried, so
5709 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5710 let mut completions = Vec::new();
5711 let mut is_incomplete = false;
5712 let mut display_options: Option<CompletionDisplayOptions> = None;
5713 if let Some(provider_responses) = provider_responses.await.log_err()
5714 && !provider_responses.is_empty()
5715 {
5716 for response in provider_responses {
5717 completions.extend(response.completions);
5718 is_incomplete = is_incomplete || response.is_incomplete;
5719 match display_options.as_mut() {
5720 None => {
5721 display_options = Some(response.display_options);
5722 }
5723 Some(options) => options.merge(&response.display_options),
5724 }
5725 }
5726 if completion_settings.words == WordsCompletionMode::Fallback {
5727 words = Task::ready(BTreeMap::default());
5728 }
5729 }
5730 let display_options = display_options.unwrap_or_default();
5731
5732 let mut words = words.await;
5733 if let Some(word_to_exclude) = &word_to_exclude {
5734 words.remove(word_to_exclude);
5735 }
5736 for lsp_completion in &completions {
5737 words.remove(&lsp_completion.new_text);
5738 }
5739 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5740 replace_range: word_replace_range.clone(),
5741 new_text: word.clone(),
5742 label: CodeLabel::plain(word, None),
5743 icon_path: None,
5744 documentation: None,
5745 source: CompletionSource::BufferWord {
5746 word_range,
5747 resolved: false,
5748 },
5749 insert_text_mode: Some(InsertTextMode::AS_IS),
5750 confirm: None,
5751 }));
5752
5753 let menu = if completions.is_empty() {
5754 None
5755 } else {
5756 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5757 let languages = editor
5758 .workspace
5759 .as_ref()
5760 .and_then(|(workspace, _)| workspace.upgrade())
5761 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5762 let menu = CompletionsMenu::new(
5763 id,
5764 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5765 sort_completions,
5766 show_completion_documentation,
5767 position,
5768 query.clone(),
5769 is_incomplete,
5770 buffer.clone(),
5771 completions.into(),
5772 display_options,
5773 snippet_sort_order,
5774 languages,
5775 language,
5776 cx,
5777 );
5778
5779 let query = if filter_completions { query } else { None };
5780 let matches_task = if let Some(query) = query {
5781 menu.do_async_filtering(query, cx)
5782 } else {
5783 Task::ready(menu.unfiltered_matches())
5784 };
5785 (menu, matches_task)
5786 }) else {
5787 return;
5788 };
5789
5790 let matches = matches_task.await;
5791
5792 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5793 // Newer menu already set, so exit.
5794 if let Some(CodeContextMenu::Completions(prev_menu)) =
5795 editor.context_menu.borrow().as_ref()
5796 && prev_menu.id > id
5797 {
5798 return;
5799 };
5800
5801 // Only valid to take prev_menu because it the new menu is immediately set
5802 // below, or the menu is hidden.
5803 if let Some(CodeContextMenu::Completions(prev_menu)) =
5804 editor.context_menu.borrow_mut().take()
5805 {
5806 let position_matches =
5807 if prev_menu.initial_position == menu.initial_position {
5808 true
5809 } else {
5810 let snapshot = editor.buffer.read(cx).read(cx);
5811 prev_menu.initial_position.to_offset(&snapshot)
5812 == menu.initial_position.to_offset(&snapshot)
5813 };
5814 if position_matches {
5815 // Preserve markdown cache before `set_filter_results` because it will
5816 // try to populate the documentation cache.
5817 menu.preserve_markdown_cache(prev_menu);
5818 }
5819 };
5820
5821 menu.set_filter_results(matches, provider, window, cx);
5822 }) else {
5823 return;
5824 };
5825
5826 menu.visible().then_some(menu)
5827 };
5828
5829 editor
5830 .update_in(cx, |editor, window, cx| {
5831 if editor.focus_handle.is_focused(window)
5832 && let Some(menu) = menu
5833 {
5834 *editor.context_menu.borrow_mut() =
5835 Some(CodeContextMenu::Completions(menu));
5836
5837 crate::hover_popover::hide_hover(editor, cx);
5838 if editor.show_edit_predictions_in_menu() {
5839 editor.update_visible_edit_prediction(window, cx);
5840 } else {
5841 editor.discard_edit_prediction(false, cx);
5842 }
5843
5844 cx.notify();
5845 return;
5846 }
5847
5848 if editor.completion_tasks.len() <= 1 {
5849 // If there are no more completion tasks and the last menu was empty, we should hide it.
5850 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5851 // If it was already hidden and we don't show edit predictions in the menu,
5852 // we should also show the edit prediction when available.
5853 if was_hidden && editor.show_edit_predictions_in_menu() {
5854 editor.update_visible_edit_prediction(window, cx);
5855 }
5856 }
5857 })
5858 .ok();
5859 });
5860
5861 self.completion_tasks.push((id, task));
5862 }
5863
5864 #[cfg(feature = "test-support")]
5865 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5866 let menu = self.context_menu.borrow();
5867 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5868 let completions = menu.completions.borrow();
5869 Some(completions.to_vec())
5870 } else {
5871 None
5872 }
5873 }
5874
5875 pub fn with_completions_menu_matching_id<R>(
5876 &self,
5877 id: CompletionId,
5878 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5879 ) -> R {
5880 let mut context_menu = self.context_menu.borrow_mut();
5881 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5882 return f(None);
5883 };
5884 if completions_menu.id != id {
5885 return f(None);
5886 }
5887 f(Some(completions_menu))
5888 }
5889
5890 pub fn confirm_completion(
5891 &mut self,
5892 action: &ConfirmCompletion,
5893 window: &mut Window,
5894 cx: &mut Context<Self>,
5895 ) -> Option<Task<Result<()>>> {
5896 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5897 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5898 }
5899
5900 pub fn confirm_completion_insert(
5901 &mut self,
5902 _: &ConfirmCompletionInsert,
5903 window: &mut Window,
5904 cx: &mut Context<Self>,
5905 ) -> Option<Task<Result<()>>> {
5906 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5907 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5908 }
5909
5910 pub fn confirm_completion_replace(
5911 &mut self,
5912 _: &ConfirmCompletionReplace,
5913 window: &mut Window,
5914 cx: &mut Context<Self>,
5915 ) -> Option<Task<Result<()>>> {
5916 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5917 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5918 }
5919
5920 pub fn compose_completion(
5921 &mut self,
5922 action: &ComposeCompletion,
5923 window: &mut Window,
5924 cx: &mut Context<Self>,
5925 ) -> Option<Task<Result<()>>> {
5926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5927 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5928 }
5929
5930 fn do_completion(
5931 &mut self,
5932 item_ix: Option<usize>,
5933 intent: CompletionIntent,
5934 window: &mut Window,
5935 cx: &mut Context<Editor>,
5936 ) -> Option<Task<Result<()>>> {
5937 use language::ToOffset as _;
5938
5939 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5940 else {
5941 return None;
5942 };
5943
5944 let candidate_id = {
5945 let entries = completions_menu.entries.borrow();
5946 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5947 if self.show_edit_predictions_in_menu() {
5948 self.discard_edit_prediction(true, cx);
5949 }
5950 mat.candidate_id
5951 };
5952
5953 let completion = completions_menu
5954 .completions
5955 .borrow()
5956 .get(candidate_id)?
5957 .clone();
5958 cx.stop_propagation();
5959
5960 let buffer_handle = completions_menu.buffer.clone();
5961
5962 let CompletionEdit {
5963 new_text,
5964 snippet,
5965 replace_range,
5966 } = process_completion_for_edit(
5967 &completion,
5968 intent,
5969 &buffer_handle,
5970 &completions_menu.initial_position.text_anchor,
5971 cx,
5972 );
5973
5974 let buffer = buffer_handle.read(cx);
5975 let snapshot = self.buffer.read(cx).snapshot(cx);
5976 let newest_anchor = self.selections.newest_anchor();
5977 let replace_range_multibuffer = {
5978 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5979 let multibuffer_anchor = snapshot
5980 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5981 .unwrap()
5982 ..snapshot
5983 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5984 .unwrap();
5985 multibuffer_anchor.start.to_offset(&snapshot)
5986 ..multibuffer_anchor.end.to_offset(&snapshot)
5987 };
5988 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5989 return None;
5990 }
5991
5992 let old_text = buffer
5993 .text_for_range(replace_range.clone())
5994 .collect::<String>();
5995 let lookbehind = newest_anchor
5996 .start
5997 .text_anchor
5998 .to_offset(buffer)
5999 .saturating_sub(replace_range.start);
6000 let lookahead = replace_range
6001 .end
6002 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6003 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6004 let suffix = &old_text[lookbehind.min(old_text.len())..];
6005
6006 let selections = self.selections.all::<usize>(cx);
6007 let mut ranges = Vec::new();
6008 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6009
6010 for selection in &selections {
6011 let range = if selection.id == newest_anchor.id {
6012 replace_range_multibuffer.clone()
6013 } else {
6014 let mut range = selection.range();
6015
6016 // if prefix is present, don't duplicate it
6017 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6018 range.start = range.start.saturating_sub(lookbehind);
6019
6020 // if suffix is also present, mimic the newest cursor and replace it
6021 if selection.id != newest_anchor.id
6022 && snapshot.contains_str_at(range.end, suffix)
6023 {
6024 range.end += lookahead;
6025 }
6026 }
6027 range
6028 };
6029
6030 ranges.push(range.clone());
6031
6032 if !self.linked_edit_ranges.is_empty() {
6033 let start_anchor = snapshot.anchor_before(range.start);
6034 let end_anchor = snapshot.anchor_after(range.end);
6035 if let Some(ranges) = self
6036 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6037 {
6038 for (buffer, edits) in ranges {
6039 linked_edits
6040 .entry(buffer.clone())
6041 .or_default()
6042 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6043 }
6044 }
6045 }
6046 }
6047
6048 let common_prefix_len = old_text
6049 .chars()
6050 .zip(new_text.chars())
6051 .take_while(|(a, b)| a == b)
6052 .map(|(a, _)| a.len_utf8())
6053 .sum::<usize>();
6054
6055 cx.emit(EditorEvent::InputHandled {
6056 utf16_range_to_replace: None,
6057 text: new_text[common_prefix_len..].into(),
6058 });
6059
6060 self.transact(window, cx, |editor, window, cx| {
6061 if let Some(mut snippet) = snippet {
6062 snippet.text = new_text.to_string();
6063 editor
6064 .insert_snippet(&ranges, snippet, window, cx)
6065 .log_err();
6066 } else {
6067 editor.buffer.update(cx, |multi_buffer, cx| {
6068 let auto_indent = match completion.insert_text_mode {
6069 Some(InsertTextMode::AS_IS) => None,
6070 _ => editor.autoindent_mode.clone(),
6071 };
6072 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6073 multi_buffer.edit(edits, auto_indent, cx);
6074 });
6075 }
6076 for (buffer, edits) in linked_edits {
6077 buffer.update(cx, |buffer, cx| {
6078 let snapshot = buffer.snapshot();
6079 let edits = edits
6080 .into_iter()
6081 .map(|(range, text)| {
6082 use text::ToPoint as TP;
6083 let end_point = TP::to_point(&range.end, &snapshot);
6084 let start_point = TP::to_point(&range.start, &snapshot);
6085 (start_point..end_point, text)
6086 })
6087 .sorted_by_key(|(range, _)| range.start);
6088 buffer.edit(edits, None, cx);
6089 })
6090 }
6091
6092 editor.refresh_edit_prediction(true, false, window, cx);
6093 });
6094 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6095
6096 let show_new_completions_on_confirm = completion
6097 .confirm
6098 .as_ref()
6099 .is_some_and(|confirm| confirm(intent, window, cx));
6100 if show_new_completions_on_confirm {
6101 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6102 }
6103
6104 let provider = self.completion_provider.as_ref()?;
6105 drop(completion);
6106 let apply_edits = provider.apply_additional_edits_for_completion(
6107 buffer_handle,
6108 completions_menu.completions.clone(),
6109 candidate_id,
6110 true,
6111 cx,
6112 );
6113
6114 let editor_settings = EditorSettings::get_global(cx);
6115 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6116 // After the code completion is finished, users often want to know what signatures are needed.
6117 // so we should automatically call signature_help
6118 self.show_signature_help(&ShowSignatureHelp, window, cx);
6119 }
6120
6121 Some(cx.foreground_executor().spawn(async move {
6122 apply_edits.await?;
6123 Ok(())
6124 }))
6125 }
6126
6127 pub fn toggle_code_actions(
6128 &mut self,
6129 action: &ToggleCodeActions,
6130 window: &mut Window,
6131 cx: &mut Context<Self>,
6132 ) {
6133 let quick_launch = action.quick_launch;
6134 let mut context_menu = self.context_menu.borrow_mut();
6135 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6136 if code_actions.deployed_from == action.deployed_from {
6137 // Toggle if we're selecting the same one
6138 *context_menu = None;
6139 cx.notify();
6140 return;
6141 } else {
6142 // Otherwise, clear it and start a new one
6143 *context_menu = None;
6144 cx.notify();
6145 }
6146 }
6147 drop(context_menu);
6148 let snapshot = self.snapshot(window, cx);
6149 let deployed_from = action.deployed_from.clone();
6150 let action = action.clone();
6151 self.completion_tasks.clear();
6152 self.discard_edit_prediction(false, cx);
6153
6154 let multibuffer_point = match &action.deployed_from {
6155 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6156 DisplayPoint::new(*row, 0).to_point(&snapshot)
6157 }
6158 _ => self.selections.newest::<Point>(cx).head(),
6159 };
6160 let Some((buffer, buffer_row)) = snapshot
6161 .buffer_snapshot
6162 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6163 .and_then(|(buffer_snapshot, range)| {
6164 self.buffer()
6165 .read(cx)
6166 .buffer(buffer_snapshot.remote_id())
6167 .map(|buffer| (buffer, range.start.row))
6168 })
6169 else {
6170 return;
6171 };
6172 let buffer_id = buffer.read(cx).remote_id();
6173 let tasks = self
6174 .tasks
6175 .get(&(buffer_id, buffer_row))
6176 .map(|t| Arc::new(t.to_owned()));
6177
6178 if !self.focus_handle.is_focused(window) {
6179 return;
6180 }
6181 let project = self.project.clone();
6182
6183 let code_actions_task = match deployed_from {
6184 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6185 _ => self.code_actions(buffer_row, window, cx),
6186 };
6187
6188 let runnable_task = match deployed_from {
6189 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6190 _ => {
6191 let mut task_context_task = Task::ready(None);
6192 if let Some(tasks) = &tasks
6193 && let Some(project) = project
6194 {
6195 task_context_task =
6196 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6197 }
6198
6199 cx.spawn_in(window, {
6200 let buffer = buffer.clone();
6201 async move |editor, cx| {
6202 let task_context = task_context_task.await;
6203
6204 let resolved_tasks =
6205 tasks
6206 .zip(task_context.clone())
6207 .map(|(tasks, task_context)| ResolvedTasks {
6208 templates: tasks.resolve(&task_context).collect(),
6209 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6210 multibuffer_point.row,
6211 tasks.column,
6212 )),
6213 });
6214 let debug_scenarios = editor
6215 .update(cx, |editor, cx| {
6216 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6217 })?
6218 .await;
6219 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6220 }
6221 })
6222 }
6223 };
6224
6225 cx.spawn_in(window, async move |editor, cx| {
6226 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6227 let code_actions = code_actions_task.await;
6228 let spawn_straight_away = quick_launch
6229 && resolved_tasks
6230 .as_ref()
6231 .is_some_and(|tasks| tasks.templates.len() == 1)
6232 && code_actions
6233 .as_ref()
6234 .is_none_or(|actions| actions.is_empty())
6235 && debug_scenarios.is_empty();
6236
6237 editor.update_in(cx, |editor, window, cx| {
6238 crate::hover_popover::hide_hover(editor, cx);
6239 let actions = CodeActionContents::new(
6240 resolved_tasks,
6241 code_actions,
6242 debug_scenarios,
6243 task_context.unwrap_or_default(),
6244 );
6245
6246 // Don't show the menu if there are no actions available
6247 if actions.is_empty() {
6248 cx.notify();
6249 return Task::ready(Ok(()));
6250 }
6251
6252 *editor.context_menu.borrow_mut() =
6253 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6254 buffer,
6255 actions,
6256 selected_item: Default::default(),
6257 scroll_handle: UniformListScrollHandle::default(),
6258 deployed_from,
6259 }));
6260 cx.notify();
6261 if spawn_straight_away
6262 && let Some(task) = editor.confirm_code_action(
6263 &ConfirmCodeAction { item_ix: Some(0) },
6264 window,
6265 cx,
6266 )
6267 {
6268 return task;
6269 }
6270
6271 Task::ready(Ok(()))
6272 })
6273 })
6274 .detach_and_log_err(cx);
6275 }
6276
6277 fn debug_scenarios(
6278 &mut self,
6279 resolved_tasks: &Option<ResolvedTasks>,
6280 buffer: &Entity<Buffer>,
6281 cx: &mut App,
6282 ) -> Task<Vec<task::DebugScenario>> {
6283 maybe!({
6284 let project = self.project()?;
6285 let dap_store = project.read(cx).dap_store();
6286 let mut scenarios = vec![];
6287 let resolved_tasks = resolved_tasks.as_ref()?;
6288 let buffer = buffer.read(cx);
6289 let language = buffer.language()?;
6290 let file = buffer.file();
6291 let debug_adapter = language_settings(language.name().into(), file, cx)
6292 .debuggers
6293 .first()
6294 .map(SharedString::from)
6295 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6296
6297 dap_store.update(cx, |dap_store, cx| {
6298 for (_, task) in &resolved_tasks.templates {
6299 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6300 task.original_task().clone(),
6301 debug_adapter.clone().into(),
6302 task.display_label().to_owned().into(),
6303 cx,
6304 );
6305 scenarios.push(maybe_scenario);
6306 }
6307 });
6308 Some(cx.background_spawn(async move {
6309 futures::future::join_all(scenarios)
6310 .await
6311 .into_iter()
6312 .flatten()
6313 .collect::<Vec<_>>()
6314 }))
6315 })
6316 .unwrap_or_else(|| Task::ready(vec![]))
6317 }
6318
6319 fn code_actions(
6320 &mut self,
6321 buffer_row: u32,
6322 window: &mut Window,
6323 cx: &mut Context<Self>,
6324 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6325 let mut task = self.code_actions_task.take();
6326 cx.spawn_in(window, async move |editor, cx| {
6327 while let Some(prev_task) = task {
6328 prev_task.await.log_err();
6329 task = editor
6330 .update(cx, |this, _| this.code_actions_task.take())
6331 .ok()?;
6332 }
6333
6334 editor
6335 .update(cx, |editor, cx| {
6336 editor
6337 .available_code_actions
6338 .clone()
6339 .and_then(|(location, code_actions)| {
6340 let snapshot = location.buffer.read(cx).snapshot();
6341 let point_range = location.range.to_point(&snapshot);
6342 let point_range = point_range.start.row..=point_range.end.row;
6343 if point_range.contains(&buffer_row) {
6344 Some(code_actions)
6345 } else {
6346 None
6347 }
6348 })
6349 })
6350 .ok()
6351 .flatten()
6352 })
6353 }
6354
6355 pub fn confirm_code_action(
6356 &mut self,
6357 action: &ConfirmCodeAction,
6358 window: &mut Window,
6359 cx: &mut Context<Self>,
6360 ) -> Option<Task<Result<()>>> {
6361 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6362
6363 let actions_menu =
6364 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6365 menu
6366 } else {
6367 return None;
6368 };
6369
6370 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6371 let action = actions_menu.actions.get(action_ix)?;
6372 let title = action.label();
6373 let buffer = actions_menu.buffer;
6374 let workspace = self.workspace()?;
6375
6376 match action {
6377 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6378 workspace.update(cx, |workspace, cx| {
6379 workspace.schedule_resolved_task(
6380 task_source_kind,
6381 resolved_task,
6382 false,
6383 window,
6384 cx,
6385 );
6386
6387 Some(Task::ready(Ok(())))
6388 })
6389 }
6390 CodeActionsItem::CodeAction {
6391 excerpt_id,
6392 action,
6393 provider,
6394 } => {
6395 let apply_code_action =
6396 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6397 let workspace = workspace.downgrade();
6398 Some(cx.spawn_in(window, async move |editor, cx| {
6399 let project_transaction = apply_code_action.await?;
6400 Self::open_project_transaction(
6401 &editor,
6402 workspace,
6403 project_transaction,
6404 title,
6405 cx,
6406 )
6407 .await
6408 }))
6409 }
6410 CodeActionsItem::DebugScenario(scenario) => {
6411 let context = actions_menu.actions.context;
6412
6413 workspace.update(cx, |workspace, cx| {
6414 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6415 workspace.start_debug_session(
6416 scenario,
6417 context,
6418 Some(buffer),
6419 None,
6420 window,
6421 cx,
6422 );
6423 });
6424 Some(Task::ready(Ok(())))
6425 }
6426 }
6427 }
6428
6429 pub async fn open_project_transaction(
6430 editor: &WeakEntity<Editor>,
6431 workspace: WeakEntity<Workspace>,
6432 transaction: ProjectTransaction,
6433 title: String,
6434 cx: &mut AsyncWindowContext,
6435 ) -> Result<()> {
6436 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6437 cx.update(|_, cx| {
6438 entries.sort_unstable_by_key(|(buffer, _)| {
6439 buffer.read(cx).file().map(|f| f.path().clone())
6440 });
6441 })?;
6442 if entries.is_empty() {
6443 return Ok(());
6444 }
6445
6446 // If the project transaction's edits are all contained within this editor, then
6447 // avoid opening a new editor to display them.
6448
6449 if let [(buffer, transaction)] = &*entries {
6450 let excerpt = editor.update(cx, |editor, cx| {
6451 editor
6452 .buffer()
6453 .read(cx)
6454 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6455 })?;
6456 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6457 && excerpted_buffer == *buffer
6458 {
6459 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6460 let excerpt_range = excerpt_range.to_offset(buffer);
6461 buffer
6462 .edited_ranges_for_transaction::<usize>(transaction)
6463 .all(|range| {
6464 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6465 })
6466 })?;
6467
6468 if all_edits_within_excerpt {
6469 return Ok(());
6470 }
6471 }
6472 }
6473
6474 let mut ranges_to_highlight = Vec::new();
6475 let excerpt_buffer = cx.new(|cx| {
6476 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6477 for (buffer_handle, transaction) in &entries {
6478 let edited_ranges = buffer_handle
6479 .read(cx)
6480 .edited_ranges_for_transaction::<Point>(transaction)
6481 .collect::<Vec<_>>();
6482 let (ranges, _) = multibuffer.set_excerpts_for_path(
6483 PathKey::for_buffer(buffer_handle, cx),
6484 buffer_handle.clone(),
6485 edited_ranges,
6486 multibuffer_context_lines(cx),
6487 cx,
6488 );
6489
6490 ranges_to_highlight.extend(ranges);
6491 }
6492 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6493 multibuffer
6494 })?;
6495
6496 workspace.update_in(cx, |workspace, window, cx| {
6497 let project = workspace.project().clone();
6498 let editor =
6499 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6500 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6501 editor.update(cx, |editor, cx| {
6502 editor.highlight_background::<Self>(
6503 &ranges_to_highlight,
6504 |theme| theme.colors().editor_highlighted_line_background,
6505 cx,
6506 );
6507 });
6508 })?;
6509
6510 Ok(())
6511 }
6512
6513 pub fn clear_code_action_providers(&mut self) {
6514 self.code_action_providers.clear();
6515 self.available_code_actions.take();
6516 }
6517
6518 pub fn add_code_action_provider(
6519 &mut self,
6520 provider: Rc<dyn CodeActionProvider>,
6521 window: &mut Window,
6522 cx: &mut Context<Self>,
6523 ) {
6524 if self
6525 .code_action_providers
6526 .iter()
6527 .any(|existing_provider| existing_provider.id() == provider.id())
6528 {
6529 return;
6530 }
6531
6532 self.code_action_providers.push(provider);
6533 self.refresh_code_actions(window, cx);
6534 }
6535
6536 pub fn remove_code_action_provider(
6537 &mut self,
6538 id: Arc<str>,
6539 window: &mut Window,
6540 cx: &mut Context<Self>,
6541 ) {
6542 self.code_action_providers
6543 .retain(|provider| provider.id() != id);
6544 self.refresh_code_actions(window, cx);
6545 }
6546
6547 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6548 !self.code_action_providers.is_empty()
6549 && EditorSettings::get_global(cx).toolbar.code_actions
6550 }
6551
6552 pub fn has_available_code_actions(&self) -> bool {
6553 self.available_code_actions
6554 .as_ref()
6555 .is_some_and(|(_, actions)| !actions.is_empty())
6556 }
6557
6558 fn render_inline_code_actions(
6559 &self,
6560 icon_size: ui::IconSize,
6561 display_row: DisplayRow,
6562 is_active: bool,
6563 cx: &mut Context<Self>,
6564 ) -> AnyElement {
6565 let show_tooltip = !self.context_menu_visible();
6566 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6567 .icon_size(icon_size)
6568 .shape(ui::IconButtonShape::Square)
6569 .icon_color(ui::Color::Hidden)
6570 .toggle_state(is_active)
6571 .when(show_tooltip, |this| {
6572 this.tooltip({
6573 let focus_handle = self.focus_handle.clone();
6574 move |window, cx| {
6575 Tooltip::for_action_in(
6576 "Toggle Code Actions",
6577 &ToggleCodeActions {
6578 deployed_from: None,
6579 quick_launch: false,
6580 },
6581 &focus_handle,
6582 window,
6583 cx,
6584 )
6585 }
6586 })
6587 })
6588 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6589 window.focus(&editor.focus_handle(cx));
6590 editor.toggle_code_actions(
6591 &crate::actions::ToggleCodeActions {
6592 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6593 display_row,
6594 )),
6595 quick_launch: false,
6596 },
6597 window,
6598 cx,
6599 );
6600 }))
6601 .into_any_element()
6602 }
6603
6604 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6605 &self.context_menu
6606 }
6607
6608 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6609 let newest_selection = self.selections.newest_anchor().clone();
6610 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6611 let buffer = self.buffer.read(cx);
6612 if newest_selection.head().diff_base_anchor.is_some() {
6613 return None;
6614 }
6615 let (start_buffer, start) =
6616 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6617 let (end_buffer, end) =
6618 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6619 if start_buffer != end_buffer {
6620 return None;
6621 }
6622
6623 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6624 cx.background_executor()
6625 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6626 .await;
6627
6628 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6629 let providers = this.code_action_providers.clone();
6630 let tasks = this
6631 .code_action_providers
6632 .iter()
6633 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6634 .collect::<Vec<_>>();
6635 (providers, tasks)
6636 })?;
6637
6638 let mut actions = Vec::new();
6639 for (provider, provider_actions) in
6640 providers.into_iter().zip(future::join_all(tasks).await)
6641 {
6642 if let Some(provider_actions) = provider_actions.log_err() {
6643 actions.extend(provider_actions.into_iter().map(|action| {
6644 AvailableCodeAction {
6645 excerpt_id: newest_selection.start.excerpt_id,
6646 action,
6647 provider: provider.clone(),
6648 }
6649 }));
6650 }
6651 }
6652
6653 this.update(cx, |this, cx| {
6654 this.available_code_actions = if actions.is_empty() {
6655 None
6656 } else {
6657 Some((
6658 Location {
6659 buffer: start_buffer,
6660 range: start..end,
6661 },
6662 actions.into(),
6663 ))
6664 };
6665 cx.notify();
6666 })
6667 }));
6668 None
6669 }
6670
6671 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6672 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6673 self.show_git_blame_inline = false;
6674
6675 self.show_git_blame_inline_delay_task =
6676 Some(cx.spawn_in(window, async move |this, cx| {
6677 cx.background_executor().timer(delay).await;
6678
6679 this.update(cx, |this, cx| {
6680 this.show_git_blame_inline = true;
6681 cx.notify();
6682 })
6683 .log_err();
6684 }));
6685 }
6686 }
6687
6688 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6689 let snapshot = self.snapshot(window, cx);
6690 let cursor = self.selections.newest::<Point>(cx).head();
6691 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6692 else {
6693 return;
6694 };
6695
6696 let Some(blame) = self.blame.as_ref() else {
6697 return;
6698 };
6699
6700 let row_info = RowInfo {
6701 buffer_id: Some(buffer.remote_id()),
6702 buffer_row: Some(point.row),
6703 ..Default::default()
6704 };
6705 let Some((buffer, blame_entry)) = blame
6706 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6707 .flatten()
6708 else {
6709 return;
6710 };
6711
6712 let anchor = self.selections.newest_anchor().head();
6713 let position = self.to_pixel_point(anchor, &snapshot, window);
6714 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6715 self.show_blame_popover(
6716 buffer,
6717 &blame_entry,
6718 position + last_bounds.origin,
6719 true,
6720 cx,
6721 );
6722 };
6723 }
6724
6725 fn show_blame_popover(
6726 &mut self,
6727 buffer: BufferId,
6728 blame_entry: &BlameEntry,
6729 position: gpui::Point<Pixels>,
6730 ignore_timeout: bool,
6731 cx: &mut Context<Self>,
6732 ) {
6733 if let Some(state) = &mut self.inline_blame_popover {
6734 state.hide_task.take();
6735 } else {
6736 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6737 let blame_entry = blame_entry.clone();
6738 let show_task = cx.spawn(async move |editor, cx| {
6739 if !ignore_timeout {
6740 cx.background_executor()
6741 .timer(std::time::Duration::from_millis(blame_popover_delay))
6742 .await;
6743 }
6744 editor
6745 .update(cx, |editor, cx| {
6746 editor.inline_blame_popover_show_task.take();
6747 let Some(blame) = editor.blame.as_ref() else {
6748 return;
6749 };
6750 let blame = blame.read(cx);
6751 let details = blame.details_for_entry(buffer, &blame_entry);
6752 let markdown = cx.new(|cx| {
6753 Markdown::new(
6754 details
6755 .as_ref()
6756 .map(|message| message.message.clone())
6757 .unwrap_or_default(),
6758 None,
6759 None,
6760 cx,
6761 )
6762 });
6763 editor.inline_blame_popover = Some(InlineBlamePopover {
6764 position,
6765 hide_task: None,
6766 popover_bounds: None,
6767 popover_state: InlineBlamePopoverState {
6768 scroll_handle: ScrollHandle::new(),
6769 commit_message: details,
6770 markdown,
6771 },
6772 keyboard_grace: ignore_timeout,
6773 });
6774 cx.notify();
6775 })
6776 .ok();
6777 });
6778 self.inline_blame_popover_show_task = Some(show_task);
6779 }
6780 }
6781
6782 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6783 self.inline_blame_popover_show_task.take();
6784 if let Some(state) = &mut self.inline_blame_popover {
6785 let hide_task = cx.spawn(async move |editor, cx| {
6786 cx.background_executor()
6787 .timer(std::time::Duration::from_millis(100))
6788 .await;
6789 editor
6790 .update(cx, |editor, cx| {
6791 editor.inline_blame_popover.take();
6792 cx.notify();
6793 })
6794 .ok();
6795 });
6796 state.hide_task = Some(hide_task);
6797 }
6798 }
6799
6800 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6801 if self.pending_rename.is_some() {
6802 return None;
6803 }
6804
6805 let provider = self.semantics_provider.clone()?;
6806 let buffer = self.buffer.read(cx);
6807 let newest_selection = self.selections.newest_anchor().clone();
6808 let cursor_position = newest_selection.head();
6809 let (cursor_buffer, cursor_buffer_position) =
6810 buffer.text_anchor_for_position(cursor_position, cx)?;
6811 let (tail_buffer, tail_buffer_position) =
6812 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6813 if cursor_buffer != tail_buffer {
6814 return None;
6815 }
6816
6817 let snapshot = cursor_buffer.read(cx).snapshot();
6818 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6819 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6820 if start_word_range != end_word_range {
6821 self.document_highlights_task.take();
6822 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6823 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6824 return None;
6825 }
6826
6827 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6828 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6829 cx.background_executor()
6830 .timer(Duration::from_millis(debounce))
6831 .await;
6832
6833 let highlights = if let Some(highlights) = cx
6834 .update(|cx| {
6835 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6836 })
6837 .ok()
6838 .flatten()
6839 {
6840 highlights.await.log_err()
6841 } else {
6842 None
6843 };
6844
6845 if let Some(highlights) = highlights {
6846 this.update(cx, |this, cx| {
6847 if this.pending_rename.is_some() {
6848 return;
6849 }
6850
6851 let buffer = this.buffer.read(cx);
6852 if buffer
6853 .text_anchor_for_position(cursor_position, cx)
6854 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6855 {
6856 return;
6857 }
6858
6859 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6860 let mut write_ranges = Vec::new();
6861 let mut read_ranges = Vec::new();
6862 for highlight in highlights {
6863 let buffer_id = cursor_buffer.read(cx).remote_id();
6864 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6865 {
6866 let start = highlight
6867 .range
6868 .start
6869 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6870 let end = highlight
6871 .range
6872 .end
6873 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6874 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6875 continue;
6876 }
6877
6878 let range = Anchor::range_in_buffer(excerpt_id, buffer_id, start..end);
6879 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6880 write_ranges.push(range);
6881 } else {
6882 read_ranges.push(range);
6883 }
6884 }
6885 }
6886
6887 this.highlight_background::<DocumentHighlightRead>(
6888 &read_ranges,
6889 |theme| theme.colors().editor_document_highlight_read_background,
6890 cx,
6891 );
6892 this.highlight_background::<DocumentHighlightWrite>(
6893 &write_ranges,
6894 |theme| theme.colors().editor_document_highlight_write_background,
6895 cx,
6896 );
6897 cx.notify();
6898 })
6899 .log_err();
6900 }
6901 }));
6902 None
6903 }
6904
6905 fn prepare_highlight_query_from_selection(
6906 &mut self,
6907 cx: &mut Context<Editor>,
6908 ) -> Option<(String, Range<Anchor>)> {
6909 if matches!(self.mode, EditorMode::SingleLine) {
6910 return None;
6911 }
6912 if !EditorSettings::get_global(cx).selection_highlight {
6913 return None;
6914 }
6915 if self.selections.count() != 1 || self.selections.line_mode() {
6916 return None;
6917 }
6918 let selection = self.selections.newest::<Point>(cx);
6919 if selection.is_empty() || selection.start.row != selection.end.row {
6920 return None;
6921 }
6922 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6923 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6924 let query = multi_buffer_snapshot
6925 .text_for_range(selection_anchor_range.clone())
6926 .collect::<String>();
6927 if query.trim().is_empty() {
6928 return None;
6929 }
6930 Some((query, selection_anchor_range))
6931 }
6932
6933 fn update_selection_occurrence_highlights(
6934 &mut self,
6935 query_text: String,
6936 query_range: Range<Anchor>,
6937 multi_buffer_range_to_query: Range<Point>,
6938 use_debounce: bool,
6939 window: &mut Window,
6940 cx: &mut Context<Editor>,
6941 ) -> Task<()> {
6942 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6943 cx.spawn_in(window, async move |editor, cx| {
6944 if use_debounce {
6945 cx.background_executor()
6946 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6947 .await;
6948 }
6949 let match_task = cx.background_spawn(async move {
6950 let buffer_ranges = multi_buffer_snapshot
6951 .range_to_buffer_ranges(multi_buffer_range_to_query)
6952 .into_iter()
6953 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6954 let mut match_ranges = Vec::new();
6955 let Ok(regex) = project::search::SearchQuery::text(
6956 query_text.clone(),
6957 false,
6958 false,
6959 false,
6960 Default::default(),
6961 Default::default(),
6962 false,
6963 None,
6964 ) else {
6965 return Vec::default();
6966 };
6967 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6968 match_ranges.extend(
6969 regex
6970 .search(buffer_snapshot, Some(search_range.clone()))
6971 .await
6972 .into_iter()
6973 .filter_map(|match_range| {
6974 let match_start = buffer_snapshot
6975 .anchor_after(search_range.start + match_range.start);
6976 let match_end = buffer_snapshot
6977 .anchor_before(search_range.start + match_range.end);
6978 let match_anchor_range = Anchor::range_in_buffer(
6979 excerpt_id,
6980 buffer_snapshot.remote_id(),
6981 match_start..match_end,
6982 );
6983 (match_anchor_range != query_range).then_some(match_anchor_range)
6984 }),
6985 );
6986 }
6987 match_ranges
6988 });
6989 let match_ranges = match_task.await;
6990 editor
6991 .update_in(cx, |editor, _, cx| {
6992 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6993 if !match_ranges.is_empty() {
6994 editor.highlight_background::<SelectedTextHighlight>(
6995 &match_ranges,
6996 |theme| theme.colors().editor_document_highlight_bracket_background,
6997 cx,
6998 )
6999 }
7000 })
7001 .log_err();
7002 })
7003 }
7004
7005 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7006 struct NewlineFold;
7007 let type_id = std::any::TypeId::of::<NewlineFold>();
7008 if !self.mode.is_single_line() {
7009 return;
7010 }
7011 let snapshot = self.snapshot(window, cx);
7012 if snapshot.buffer_snapshot.max_point().row == 0 {
7013 return;
7014 }
7015 let task = cx.background_spawn(async move {
7016 let new_newlines = snapshot
7017 .buffer_chars_at(0)
7018 .filter_map(|(c, i)| {
7019 if c == '\n' {
7020 Some(
7021 snapshot.buffer_snapshot.anchor_after(i)
7022 ..snapshot.buffer_snapshot.anchor_before(i + 1),
7023 )
7024 } else {
7025 None
7026 }
7027 })
7028 .collect::<Vec<_>>();
7029 let existing_newlines = snapshot
7030 .folds_in_range(0..snapshot.buffer_snapshot.len())
7031 .filter_map(|fold| {
7032 if fold.placeholder.type_tag == Some(type_id) {
7033 Some(fold.range.start..fold.range.end)
7034 } else {
7035 None
7036 }
7037 })
7038 .collect::<Vec<_>>();
7039
7040 (new_newlines, existing_newlines)
7041 });
7042 self.folding_newlines = cx.spawn(async move |this, cx| {
7043 let (new_newlines, existing_newlines) = task.await;
7044 if new_newlines == existing_newlines {
7045 return;
7046 }
7047 let placeholder = FoldPlaceholder {
7048 render: Arc::new(move |_, _, cx| {
7049 div()
7050 .bg(cx.theme().status().hint_background)
7051 .border_b_1()
7052 .size_full()
7053 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7054 .border_color(cx.theme().status().hint)
7055 .child("\\n")
7056 .into_any()
7057 }),
7058 constrain_width: false,
7059 merge_adjacent: false,
7060 type_tag: Some(type_id),
7061 };
7062 let creases = new_newlines
7063 .into_iter()
7064 .map(|range| Crease::simple(range, placeholder.clone()))
7065 .collect();
7066 this.update(cx, |this, cx| {
7067 this.display_map.update(cx, |display_map, cx| {
7068 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7069 display_map.fold(creases, cx);
7070 });
7071 })
7072 .ok();
7073 });
7074 }
7075
7076 fn refresh_selected_text_highlights(
7077 &mut self,
7078 on_buffer_edit: bool,
7079 window: &mut Window,
7080 cx: &mut Context<Editor>,
7081 ) {
7082 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7083 else {
7084 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7085 self.quick_selection_highlight_task.take();
7086 self.debounced_selection_highlight_task.take();
7087 return;
7088 };
7089 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7090 if on_buffer_edit
7091 || self
7092 .quick_selection_highlight_task
7093 .as_ref()
7094 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7095 {
7096 let multi_buffer_visible_start = self
7097 .scroll_manager
7098 .anchor()
7099 .anchor
7100 .to_point(&multi_buffer_snapshot);
7101 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7102 multi_buffer_visible_start
7103 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7104 Bias::Left,
7105 );
7106 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7107 self.quick_selection_highlight_task = Some((
7108 query_range.clone(),
7109 self.update_selection_occurrence_highlights(
7110 query_text.clone(),
7111 query_range.clone(),
7112 multi_buffer_visible_range,
7113 false,
7114 window,
7115 cx,
7116 ),
7117 ));
7118 }
7119 if on_buffer_edit
7120 || self
7121 .debounced_selection_highlight_task
7122 .as_ref()
7123 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7124 {
7125 let multi_buffer_start = multi_buffer_snapshot
7126 .anchor_before(0)
7127 .to_point(&multi_buffer_snapshot);
7128 let multi_buffer_end = multi_buffer_snapshot
7129 .anchor_after(multi_buffer_snapshot.len())
7130 .to_point(&multi_buffer_snapshot);
7131 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7132 self.debounced_selection_highlight_task = Some((
7133 query_range.clone(),
7134 self.update_selection_occurrence_highlights(
7135 query_text,
7136 query_range,
7137 multi_buffer_full_range,
7138 true,
7139 window,
7140 cx,
7141 ),
7142 ));
7143 }
7144 }
7145
7146 pub fn refresh_edit_prediction(
7147 &mut self,
7148 debounce: bool,
7149 user_requested: bool,
7150 window: &mut Window,
7151 cx: &mut Context<Self>,
7152 ) -> Option<()> {
7153 if DisableAiSettings::get_global(cx).disable_ai {
7154 return None;
7155 }
7156
7157 let provider = self.edit_prediction_provider()?;
7158 let cursor = self.selections.newest_anchor().head();
7159 let (buffer, cursor_buffer_position) =
7160 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7161
7162 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7163 self.discard_edit_prediction(false, cx);
7164 return None;
7165 }
7166
7167 self.update_visible_edit_prediction(window, cx);
7168
7169 if !user_requested
7170 && (!self.should_show_edit_predictions()
7171 || !self.is_focused(window)
7172 || buffer.read(cx).is_empty())
7173 {
7174 self.discard_edit_prediction(false, cx);
7175 return None;
7176 }
7177
7178 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7179 Some(())
7180 }
7181
7182 fn show_edit_predictions_in_menu(&self) -> bool {
7183 match self.edit_prediction_settings {
7184 EditPredictionSettings::Disabled => false,
7185 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7186 }
7187 }
7188
7189 pub fn edit_predictions_enabled(&self) -> bool {
7190 match self.edit_prediction_settings {
7191 EditPredictionSettings::Disabled => false,
7192 EditPredictionSettings::Enabled { .. } => true,
7193 }
7194 }
7195
7196 fn edit_prediction_requires_modifier(&self) -> bool {
7197 match self.edit_prediction_settings {
7198 EditPredictionSettings::Disabled => false,
7199 EditPredictionSettings::Enabled {
7200 preview_requires_modifier,
7201 ..
7202 } => preview_requires_modifier,
7203 }
7204 }
7205
7206 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7207 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7208 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7209 self.discard_edit_prediction(false, cx);
7210 } else {
7211 let selection = self.selections.newest_anchor();
7212 let cursor = selection.head();
7213
7214 if let Some((buffer, cursor_buffer_position)) =
7215 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7216 {
7217 self.edit_prediction_settings =
7218 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7219 }
7220 }
7221 }
7222
7223 fn edit_prediction_settings_at_position(
7224 &self,
7225 buffer: &Entity<Buffer>,
7226 buffer_position: language::Anchor,
7227 cx: &App,
7228 ) -> EditPredictionSettings {
7229 if !self.mode.is_full()
7230 || !self.show_edit_predictions_override.unwrap_or(true)
7231 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7232 {
7233 return EditPredictionSettings::Disabled;
7234 }
7235
7236 let buffer = buffer.read(cx);
7237
7238 let file = buffer.file();
7239
7240 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7241 return EditPredictionSettings::Disabled;
7242 };
7243
7244 let by_provider = matches!(
7245 self.menu_edit_predictions_policy,
7246 MenuEditPredictionsPolicy::ByProvider
7247 );
7248
7249 let show_in_menu = by_provider
7250 && self
7251 .edit_prediction_provider
7252 .as_ref()
7253 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7254
7255 let preview_requires_modifier =
7256 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7257
7258 EditPredictionSettings::Enabled {
7259 show_in_menu,
7260 preview_requires_modifier,
7261 }
7262 }
7263
7264 fn should_show_edit_predictions(&self) -> bool {
7265 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7266 }
7267
7268 pub fn edit_prediction_preview_is_active(&self) -> bool {
7269 matches!(
7270 self.edit_prediction_preview,
7271 EditPredictionPreview::Active { .. }
7272 )
7273 }
7274
7275 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7276 let cursor = self.selections.newest_anchor().head();
7277 if let Some((buffer, cursor_position)) =
7278 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7279 {
7280 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7281 } else {
7282 false
7283 }
7284 }
7285
7286 pub fn supports_minimap(&self, cx: &App) -> bool {
7287 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7288 }
7289
7290 fn edit_predictions_enabled_in_buffer(
7291 &self,
7292 buffer: &Entity<Buffer>,
7293 buffer_position: language::Anchor,
7294 cx: &App,
7295 ) -> bool {
7296 maybe!({
7297 if self.read_only(cx) {
7298 return Some(false);
7299 }
7300 let provider = self.edit_prediction_provider()?;
7301 if !provider.is_enabled(buffer, buffer_position, cx) {
7302 return Some(false);
7303 }
7304 let buffer = buffer.read(cx);
7305 let Some(file) = buffer.file() else {
7306 return Some(true);
7307 };
7308 let settings = all_language_settings(Some(file), cx);
7309 Some(settings.edit_predictions_enabled_for_file(file, cx))
7310 })
7311 .unwrap_or(false)
7312 }
7313
7314 fn cycle_edit_prediction(
7315 &mut self,
7316 direction: Direction,
7317 window: &mut Window,
7318 cx: &mut Context<Self>,
7319 ) -> Option<()> {
7320 let provider = self.edit_prediction_provider()?;
7321 let cursor = self.selections.newest_anchor().head();
7322 let (buffer, cursor_buffer_position) =
7323 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7324 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7325 return None;
7326 }
7327
7328 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7329 self.update_visible_edit_prediction(window, cx);
7330
7331 Some(())
7332 }
7333
7334 pub fn show_edit_prediction(
7335 &mut self,
7336 _: &ShowEditPrediction,
7337 window: &mut Window,
7338 cx: &mut Context<Self>,
7339 ) {
7340 if !self.has_active_edit_prediction() {
7341 self.refresh_edit_prediction(false, true, window, cx);
7342 return;
7343 }
7344
7345 self.update_visible_edit_prediction(window, cx);
7346 }
7347
7348 pub fn display_cursor_names(
7349 &mut self,
7350 _: &DisplayCursorNames,
7351 window: &mut Window,
7352 cx: &mut Context<Self>,
7353 ) {
7354 self.show_cursor_names(window, cx);
7355 }
7356
7357 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7358 self.show_cursor_names = true;
7359 cx.notify();
7360 cx.spawn_in(window, async move |this, cx| {
7361 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7362 this.update(cx, |this, cx| {
7363 this.show_cursor_names = false;
7364 cx.notify()
7365 })
7366 .ok()
7367 })
7368 .detach();
7369 }
7370
7371 pub fn next_edit_prediction(
7372 &mut self,
7373 _: &NextEditPrediction,
7374 window: &mut Window,
7375 cx: &mut Context<Self>,
7376 ) {
7377 if self.has_active_edit_prediction() {
7378 self.cycle_edit_prediction(Direction::Next, window, cx);
7379 } else {
7380 let is_copilot_disabled = self
7381 .refresh_edit_prediction(false, true, window, cx)
7382 .is_none();
7383 if is_copilot_disabled {
7384 cx.propagate();
7385 }
7386 }
7387 }
7388
7389 pub fn previous_edit_prediction(
7390 &mut self,
7391 _: &PreviousEditPrediction,
7392 window: &mut Window,
7393 cx: &mut Context<Self>,
7394 ) {
7395 if self.has_active_edit_prediction() {
7396 self.cycle_edit_prediction(Direction::Prev, window, cx);
7397 } else {
7398 let is_copilot_disabled = self
7399 .refresh_edit_prediction(false, true, window, cx)
7400 .is_none();
7401 if is_copilot_disabled {
7402 cx.propagate();
7403 }
7404 }
7405 }
7406
7407 pub fn accept_edit_prediction(
7408 &mut self,
7409 _: &AcceptEditPrediction,
7410 window: &mut Window,
7411 cx: &mut Context<Self>,
7412 ) {
7413 if self.show_edit_predictions_in_menu() {
7414 self.hide_context_menu(window, cx);
7415 }
7416
7417 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7418 return;
7419 };
7420
7421 match &active_edit_prediction.completion {
7422 EditPrediction::MoveWithin { target, .. } => {
7423 let target = *target;
7424
7425 if let Some(position_map) = &self.last_position_map {
7426 if position_map
7427 .visible_row_range
7428 .contains(&target.to_display_point(&position_map.snapshot).row())
7429 || !self.edit_prediction_requires_modifier()
7430 {
7431 self.unfold_ranges(&[target..target], true, false, cx);
7432 // Note that this is also done in vim's handler of the Tab action.
7433 self.change_selections(
7434 SelectionEffects::scroll(Autoscroll::newest()),
7435 window,
7436 cx,
7437 |selections| {
7438 selections.select_anchor_ranges([target..target]);
7439 },
7440 );
7441 self.clear_row_highlights::<EditPredictionPreview>();
7442
7443 self.edit_prediction_preview
7444 .set_previous_scroll_position(None);
7445 } else {
7446 self.edit_prediction_preview
7447 .set_previous_scroll_position(Some(
7448 position_map.snapshot.scroll_anchor,
7449 ));
7450
7451 self.highlight_rows::<EditPredictionPreview>(
7452 target..target,
7453 cx.theme().colors().editor_highlighted_line_background,
7454 RowHighlightOptions {
7455 autoscroll: true,
7456 ..Default::default()
7457 },
7458 cx,
7459 );
7460 self.request_autoscroll(Autoscroll::fit(), cx);
7461 }
7462 }
7463 }
7464 EditPrediction::MoveOutside { snapshot, target } => {
7465 if let Some(workspace) = self.workspace() {
7466 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7467 .detach_and_log_err(cx);
7468 }
7469 }
7470 EditPrediction::Edit { edits, .. } => {
7471 self.report_edit_prediction_event(
7472 active_edit_prediction.completion_id.clone(),
7473 true,
7474 cx,
7475 );
7476
7477 if let Some(provider) = self.edit_prediction_provider() {
7478 provider.accept(cx);
7479 }
7480
7481 // Store the transaction ID and selections before applying the edit
7482 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7483
7484 let snapshot = self.buffer.read(cx).snapshot(cx);
7485 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7486
7487 self.buffer.update(cx, |buffer, cx| {
7488 buffer.edit(edits.iter().cloned(), None, cx)
7489 });
7490
7491 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7492 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7493 });
7494
7495 let selections = self.selections.disjoint_anchors_arc();
7496 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7497 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7498 if has_new_transaction {
7499 self.selection_history
7500 .insert_transaction(transaction_id_now, selections);
7501 }
7502 }
7503
7504 self.update_visible_edit_prediction(window, cx);
7505 if self.active_edit_prediction.is_none() {
7506 self.refresh_edit_prediction(true, true, window, cx);
7507 }
7508
7509 cx.notify();
7510 }
7511 }
7512
7513 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7514 }
7515
7516 pub fn accept_partial_edit_prediction(
7517 &mut self,
7518 _: &AcceptPartialEditPrediction,
7519 window: &mut Window,
7520 cx: &mut Context<Self>,
7521 ) {
7522 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7523 return;
7524 };
7525 if self.selections.count() != 1 {
7526 return;
7527 }
7528
7529 match &active_edit_prediction.completion {
7530 EditPrediction::MoveWithin { target, .. } => {
7531 let target = *target;
7532 self.change_selections(
7533 SelectionEffects::scroll(Autoscroll::newest()),
7534 window,
7535 cx,
7536 |selections| {
7537 selections.select_anchor_ranges([target..target]);
7538 },
7539 );
7540 }
7541 EditPrediction::MoveOutside { snapshot, target } => {
7542 if let Some(workspace) = self.workspace() {
7543 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7544 .detach_and_log_err(cx);
7545 }
7546 }
7547 EditPrediction::Edit { edits, .. } => {
7548 self.report_edit_prediction_event(
7549 active_edit_prediction.completion_id.clone(),
7550 true,
7551 cx,
7552 );
7553
7554 // Find an insertion that starts at the cursor position.
7555 let snapshot = self.buffer.read(cx).snapshot(cx);
7556 let cursor_offset = self.selections.newest::<usize>(cx).head();
7557 let insertion = edits.iter().find_map(|(range, text)| {
7558 let range = range.to_offset(&snapshot);
7559 if range.is_empty() && range.start == cursor_offset {
7560 Some(text)
7561 } else {
7562 None
7563 }
7564 });
7565
7566 if let Some(text) = insertion {
7567 let mut partial_completion = text
7568 .chars()
7569 .by_ref()
7570 .take_while(|c| c.is_alphabetic())
7571 .collect::<String>();
7572 if partial_completion.is_empty() {
7573 partial_completion = text
7574 .chars()
7575 .by_ref()
7576 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7577 .collect::<String>();
7578 }
7579
7580 cx.emit(EditorEvent::InputHandled {
7581 utf16_range_to_replace: None,
7582 text: partial_completion.clone().into(),
7583 });
7584
7585 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7586
7587 self.refresh_edit_prediction(true, true, window, cx);
7588 cx.notify();
7589 } else {
7590 self.accept_edit_prediction(&Default::default(), window, cx);
7591 }
7592 }
7593 }
7594 }
7595
7596 fn discard_edit_prediction(
7597 &mut self,
7598 should_report_edit_prediction_event: bool,
7599 cx: &mut Context<Self>,
7600 ) -> bool {
7601 if should_report_edit_prediction_event {
7602 let completion_id = self
7603 .active_edit_prediction
7604 .as_ref()
7605 .and_then(|active_completion| active_completion.completion_id.clone());
7606
7607 self.report_edit_prediction_event(completion_id, false, cx);
7608 }
7609
7610 if let Some(provider) = self.edit_prediction_provider() {
7611 provider.discard(cx);
7612 }
7613
7614 self.take_active_edit_prediction(cx)
7615 }
7616
7617 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7618 let Some(provider) = self.edit_prediction_provider() else {
7619 return;
7620 };
7621
7622 let Some((_, buffer, _)) = self
7623 .buffer
7624 .read(cx)
7625 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7626 else {
7627 return;
7628 };
7629
7630 let extension = buffer
7631 .read(cx)
7632 .file()
7633 .and_then(|file| Some(file.path().extension()?.to_string()));
7634
7635 let event_type = match accepted {
7636 true => "Edit Prediction Accepted",
7637 false => "Edit Prediction Discarded",
7638 };
7639 telemetry::event!(
7640 event_type,
7641 provider = provider.name(),
7642 prediction_id = id,
7643 suggestion_accepted = accepted,
7644 file_extension = extension,
7645 );
7646 }
7647
7648 fn open_editor_at_anchor(
7649 snapshot: &language::BufferSnapshot,
7650 target: language::Anchor,
7651 workspace: &Entity<Workspace>,
7652 window: &mut Window,
7653 cx: &mut App,
7654 ) -> Task<Result<()>> {
7655 workspace.update(cx, |workspace, cx| {
7656 let path = snapshot.file().map(|file| file.full_path(cx));
7657 let Some(path) =
7658 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7659 else {
7660 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7661 };
7662 let target = text::ToPoint::to_point(&target, snapshot);
7663 let item = workspace.open_path(path, None, true, window, cx);
7664 window.spawn(cx, async move |cx| {
7665 let Some(editor) = item.await?.downcast::<Editor>() else {
7666 return Ok(());
7667 };
7668 editor
7669 .update_in(cx, |editor, window, cx| {
7670 editor.go_to_singleton_buffer_point(target, window, cx);
7671 })
7672 .ok();
7673 anyhow::Ok(())
7674 })
7675 })
7676 }
7677
7678 pub fn has_active_edit_prediction(&self) -> bool {
7679 self.active_edit_prediction.is_some()
7680 }
7681
7682 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7683 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7684 return false;
7685 };
7686
7687 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7688 self.clear_highlights::<EditPredictionHighlight>(cx);
7689 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7690 true
7691 }
7692
7693 /// Returns true when we're displaying the edit prediction popover below the cursor
7694 /// like we are not previewing and the LSP autocomplete menu is visible
7695 /// or we are in `when_holding_modifier` mode.
7696 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7697 if self.edit_prediction_preview_is_active()
7698 || !self.show_edit_predictions_in_menu()
7699 || !self.edit_predictions_enabled()
7700 {
7701 return false;
7702 }
7703
7704 if self.has_visible_completions_menu() {
7705 return true;
7706 }
7707
7708 has_completion && self.edit_prediction_requires_modifier()
7709 }
7710
7711 fn handle_modifiers_changed(
7712 &mut self,
7713 modifiers: Modifiers,
7714 position_map: &PositionMap,
7715 window: &mut Window,
7716 cx: &mut Context<Self>,
7717 ) {
7718 if self.show_edit_predictions_in_menu() {
7719 self.update_edit_prediction_preview(&modifiers, window, cx);
7720 }
7721
7722 self.update_selection_mode(&modifiers, position_map, window, cx);
7723
7724 let mouse_position = window.mouse_position();
7725 if !position_map.text_hitbox.is_hovered(window) {
7726 return;
7727 }
7728
7729 self.update_hovered_link(
7730 position_map.point_for_position(mouse_position),
7731 &position_map.snapshot,
7732 modifiers,
7733 window,
7734 cx,
7735 )
7736 }
7737
7738 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7739 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7740 if invert {
7741 match multi_cursor_setting {
7742 MultiCursorModifier::Alt => modifiers.alt,
7743 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7744 }
7745 } else {
7746 match multi_cursor_setting {
7747 MultiCursorModifier::Alt => modifiers.secondary(),
7748 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7749 }
7750 }
7751 }
7752
7753 fn columnar_selection_mode(
7754 modifiers: &Modifiers,
7755 cx: &mut Context<Self>,
7756 ) -> Option<ColumnarMode> {
7757 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7758 if Self::multi_cursor_modifier(false, modifiers, cx) {
7759 Some(ColumnarMode::FromMouse)
7760 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7761 Some(ColumnarMode::FromSelection)
7762 } else {
7763 None
7764 }
7765 } else {
7766 None
7767 }
7768 }
7769
7770 fn update_selection_mode(
7771 &mut self,
7772 modifiers: &Modifiers,
7773 position_map: &PositionMap,
7774 window: &mut Window,
7775 cx: &mut Context<Self>,
7776 ) {
7777 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7778 return;
7779 };
7780 if self.selections.pending_anchor().is_none() {
7781 return;
7782 }
7783
7784 let mouse_position = window.mouse_position();
7785 let point_for_position = position_map.point_for_position(mouse_position);
7786 let position = point_for_position.previous_valid;
7787
7788 self.select(
7789 SelectPhase::BeginColumnar {
7790 position,
7791 reset: false,
7792 mode,
7793 goal_column: point_for_position.exact_unclipped.column(),
7794 },
7795 window,
7796 cx,
7797 );
7798 }
7799
7800 fn update_edit_prediction_preview(
7801 &mut self,
7802 modifiers: &Modifiers,
7803 window: &mut Window,
7804 cx: &mut Context<Self>,
7805 ) {
7806 let mut modifiers_held = false;
7807 if let Some(accept_keystroke) = self
7808 .accept_edit_prediction_keybind(false, window, cx)
7809 .keystroke()
7810 {
7811 modifiers_held = modifiers_held
7812 || (accept_keystroke.modifiers() == modifiers
7813 && accept_keystroke.modifiers().modified());
7814 };
7815 if let Some(accept_partial_keystroke) = self
7816 .accept_edit_prediction_keybind(true, window, cx)
7817 .keystroke()
7818 {
7819 modifiers_held = modifiers_held
7820 || (accept_partial_keystroke.modifiers() == modifiers
7821 && accept_partial_keystroke.modifiers().modified());
7822 }
7823
7824 if modifiers_held {
7825 if matches!(
7826 self.edit_prediction_preview,
7827 EditPredictionPreview::Inactive { .. }
7828 ) {
7829 self.edit_prediction_preview = EditPredictionPreview::Active {
7830 previous_scroll_position: None,
7831 since: Instant::now(),
7832 };
7833
7834 self.update_visible_edit_prediction(window, cx);
7835 cx.notify();
7836 }
7837 } else if let EditPredictionPreview::Active {
7838 previous_scroll_position,
7839 since,
7840 } = self.edit_prediction_preview
7841 {
7842 if let (Some(previous_scroll_position), Some(position_map)) =
7843 (previous_scroll_position, self.last_position_map.as_ref())
7844 {
7845 self.set_scroll_position(
7846 previous_scroll_position
7847 .scroll_position(&position_map.snapshot.display_snapshot),
7848 window,
7849 cx,
7850 );
7851 }
7852
7853 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7854 released_too_fast: since.elapsed() < Duration::from_millis(200),
7855 };
7856 self.clear_row_highlights::<EditPredictionPreview>();
7857 self.update_visible_edit_prediction(window, cx);
7858 cx.notify();
7859 }
7860 }
7861
7862 fn update_visible_edit_prediction(
7863 &mut self,
7864 _window: &mut Window,
7865 cx: &mut Context<Self>,
7866 ) -> Option<()> {
7867 if DisableAiSettings::get_global(cx).disable_ai {
7868 return None;
7869 }
7870
7871 if self.ime_transaction.is_some() {
7872 self.discard_edit_prediction(false, cx);
7873 return None;
7874 }
7875
7876 let selection = self.selections.newest_anchor();
7877 let cursor = selection.head();
7878 let multibuffer = self.buffer.read(cx).snapshot(cx);
7879 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7880 let excerpt_id = cursor.excerpt_id;
7881
7882 let show_in_menu = self.show_edit_predictions_in_menu();
7883 let completions_menu_has_precedence = !show_in_menu
7884 && (self.context_menu.borrow().is_some()
7885 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7886
7887 if completions_menu_has_precedence
7888 || !offset_selection.is_empty()
7889 || self
7890 .active_edit_prediction
7891 .as_ref()
7892 .is_some_and(|completion| {
7893 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7894 return false;
7895 };
7896 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7897 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7898 !invalidation_range.contains(&offset_selection.head())
7899 })
7900 {
7901 self.discard_edit_prediction(false, cx);
7902 return None;
7903 }
7904
7905 self.take_active_edit_prediction(cx);
7906 let Some(provider) = self.edit_prediction_provider() else {
7907 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7908 return None;
7909 };
7910
7911 let (buffer, cursor_buffer_position) =
7912 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7913
7914 self.edit_prediction_settings =
7915 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7916
7917 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7918
7919 if self.edit_prediction_indent_conflict {
7920 let cursor_point = cursor.to_point(&multibuffer);
7921
7922 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7923
7924 if let Some((_, indent)) = indents.iter().next()
7925 && indent.len == cursor_point.column
7926 {
7927 self.edit_prediction_indent_conflict = false;
7928 }
7929 }
7930
7931 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7932
7933 let (completion_id, edits, edit_preview) = match edit_prediction {
7934 edit_prediction::EditPrediction::Local {
7935 id,
7936 edits,
7937 edit_preview,
7938 } => (id, edits, edit_preview),
7939 edit_prediction::EditPrediction::Jump {
7940 id,
7941 snapshot,
7942 target,
7943 } => {
7944 self.stale_edit_prediction_in_menu = None;
7945 self.active_edit_prediction = Some(EditPredictionState {
7946 inlay_ids: vec![],
7947 completion: EditPrediction::MoveOutside { snapshot, target },
7948 completion_id: id,
7949 invalidation_range: None,
7950 });
7951 cx.notify();
7952 return Some(());
7953 }
7954 };
7955
7956 let edits = edits
7957 .into_iter()
7958 .flat_map(|(range, new_text)| {
7959 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7960 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7961 Some((start..end, new_text))
7962 })
7963 .collect::<Vec<_>>();
7964 if edits.is_empty() {
7965 return None;
7966 }
7967
7968 let first_edit_start = edits.first().unwrap().0.start;
7969 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7970 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7971
7972 let last_edit_end = edits.last().unwrap().0.end;
7973 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7974 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7975
7976 let cursor_row = cursor.to_point(&multibuffer).row;
7977
7978 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7979
7980 let mut inlay_ids = Vec::new();
7981 let invalidation_row_range;
7982 let move_invalidation_row_range = if cursor_row < edit_start_row {
7983 Some(cursor_row..edit_end_row)
7984 } else if cursor_row > edit_end_row {
7985 Some(edit_start_row..cursor_row)
7986 } else {
7987 None
7988 };
7989 let supports_jump = self
7990 .edit_prediction_provider
7991 .as_ref()
7992 .map(|provider| provider.provider.supports_jump_to_edit())
7993 .unwrap_or(true);
7994
7995 let is_move = supports_jump
7996 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7997 let completion = if is_move {
7998 invalidation_row_range =
7999 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8000 let target = first_edit_start;
8001 EditPrediction::MoveWithin { target, snapshot }
8002 } else {
8003 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8004 && !self.edit_predictions_hidden_for_vim_mode;
8005
8006 if show_completions_in_buffer {
8007 if edits
8008 .iter()
8009 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8010 {
8011 let mut inlays = Vec::new();
8012 for (range, new_text) in &edits {
8013 let inlay = Inlay::edit_prediction(
8014 post_inc(&mut self.next_inlay_id),
8015 range.start,
8016 new_text.as_str(),
8017 );
8018 inlay_ids.push(inlay.id);
8019 inlays.push(inlay);
8020 }
8021
8022 self.splice_inlays(&[], inlays, cx);
8023 } else {
8024 let background_color = cx.theme().status().deleted_background;
8025 self.highlight_text::<EditPredictionHighlight>(
8026 edits.iter().map(|(range, _)| range.clone()).collect(),
8027 HighlightStyle {
8028 background_color: Some(background_color),
8029 ..Default::default()
8030 },
8031 cx,
8032 );
8033 }
8034 }
8035
8036 invalidation_row_range = edit_start_row..edit_end_row;
8037
8038 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8039 if provider.show_tab_accept_marker() {
8040 EditDisplayMode::TabAccept
8041 } else {
8042 EditDisplayMode::Inline
8043 }
8044 } else {
8045 EditDisplayMode::DiffPopover
8046 };
8047
8048 EditPrediction::Edit {
8049 edits,
8050 edit_preview,
8051 display_mode,
8052 snapshot,
8053 }
8054 };
8055
8056 let invalidation_range = multibuffer
8057 .anchor_before(Point::new(invalidation_row_range.start, 0))
8058 ..multibuffer.anchor_after(Point::new(
8059 invalidation_row_range.end,
8060 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8061 ));
8062
8063 self.stale_edit_prediction_in_menu = None;
8064 self.active_edit_prediction = Some(EditPredictionState {
8065 inlay_ids,
8066 completion,
8067 completion_id,
8068 invalidation_range: Some(invalidation_range),
8069 });
8070
8071 cx.notify();
8072
8073 Some(())
8074 }
8075
8076 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8077 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8078 }
8079
8080 fn clear_tasks(&mut self) {
8081 self.tasks.clear()
8082 }
8083
8084 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8085 if self.tasks.insert(key, value).is_some() {
8086 // This case should hopefully be rare, but just in case...
8087 log::error!(
8088 "multiple different run targets found on a single line, only the last target will be rendered"
8089 )
8090 }
8091 }
8092
8093 /// Get all display points of breakpoints that will be rendered within editor
8094 ///
8095 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8096 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8097 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8098 fn active_breakpoints(
8099 &self,
8100 range: Range<DisplayRow>,
8101 window: &mut Window,
8102 cx: &mut Context<Self>,
8103 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8104 let mut breakpoint_display_points = HashMap::default();
8105
8106 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8107 return breakpoint_display_points;
8108 };
8109
8110 let snapshot = self.snapshot(window, cx);
8111
8112 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8113 let Some(project) = self.project() else {
8114 return breakpoint_display_points;
8115 };
8116
8117 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8118 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8119
8120 for (buffer_snapshot, range, excerpt_id) in
8121 multi_buffer_snapshot.range_to_buffer_ranges(range)
8122 {
8123 let Some(buffer) = project
8124 .read(cx)
8125 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8126 else {
8127 continue;
8128 };
8129 let breakpoints = breakpoint_store.read(cx).breakpoints(
8130 &buffer,
8131 Some(
8132 buffer_snapshot.anchor_before(range.start)
8133 ..buffer_snapshot.anchor_after(range.end),
8134 ),
8135 buffer_snapshot,
8136 cx,
8137 );
8138 for (breakpoint, state) in breakpoints {
8139 let multi_buffer_anchor =
8140 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8141 let position = multi_buffer_anchor
8142 .to_point(multi_buffer_snapshot)
8143 .to_display_point(&snapshot);
8144
8145 breakpoint_display_points.insert(
8146 position.row(),
8147 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8148 );
8149 }
8150 }
8151
8152 breakpoint_display_points
8153 }
8154
8155 fn breakpoint_context_menu(
8156 &self,
8157 anchor: Anchor,
8158 window: &mut Window,
8159 cx: &mut Context<Self>,
8160 ) -> Entity<ui::ContextMenu> {
8161 let weak_editor = cx.weak_entity();
8162 let focus_handle = self.focus_handle(cx);
8163
8164 let row = self
8165 .buffer
8166 .read(cx)
8167 .snapshot(cx)
8168 .summary_for_anchor::<Point>(&anchor)
8169 .row;
8170
8171 let breakpoint = self
8172 .breakpoint_at_row(row, window, cx)
8173 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8174
8175 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8176 "Edit Log Breakpoint"
8177 } else {
8178 "Set Log Breakpoint"
8179 };
8180
8181 let condition_breakpoint_msg = if breakpoint
8182 .as_ref()
8183 .is_some_and(|bp| bp.1.condition.is_some())
8184 {
8185 "Edit Condition Breakpoint"
8186 } else {
8187 "Set Condition Breakpoint"
8188 };
8189
8190 let hit_condition_breakpoint_msg = if breakpoint
8191 .as_ref()
8192 .is_some_and(|bp| bp.1.hit_condition.is_some())
8193 {
8194 "Edit Hit Condition Breakpoint"
8195 } else {
8196 "Set Hit Condition Breakpoint"
8197 };
8198
8199 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8200 "Unset Breakpoint"
8201 } else {
8202 "Set Breakpoint"
8203 };
8204
8205 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8206
8207 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8208 BreakpointState::Enabled => Some("Disable"),
8209 BreakpointState::Disabled => Some("Enable"),
8210 });
8211
8212 let (anchor, breakpoint) =
8213 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8214
8215 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8216 menu.on_blur_subscription(Subscription::new(|| {}))
8217 .context(focus_handle)
8218 .when(run_to_cursor, |this| {
8219 let weak_editor = weak_editor.clone();
8220 this.entry("Run to cursor", None, move |window, cx| {
8221 weak_editor
8222 .update(cx, |editor, cx| {
8223 editor.change_selections(
8224 SelectionEffects::no_scroll(),
8225 window,
8226 cx,
8227 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8228 );
8229 })
8230 .ok();
8231
8232 window.dispatch_action(Box::new(RunToCursor), cx);
8233 })
8234 .separator()
8235 })
8236 .when_some(toggle_state_msg, |this, msg| {
8237 this.entry(msg, None, {
8238 let weak_editor = weak_editor.clone();
8239 let breakpoint = breakpoint.clone();
8240 move |_window, cx| {
8241 weak_editor
8242 .update(cx, |this, cx| {
8243 this.edit_breakpoint_at_anchor(
8244 anchor,
8245 breakpoint.as_ref().clone(),
8246 BreakpointEditAction::InvertState,
8247 cx,
8248 );
8249 })
8250 .log_err();
8251 }
8252 })
8253 })
8254 .entry(set_breakpoint_msg, None, {
8255 let weak_editor = weak_editor.clone();
8256 let breakpoint = breakpoint.clone();
8257 move |_window, cx| {
8258 weak_editor
8259 .update(cx, |this, cx| {
8260 this.edit_breakpoint_at_anchor(
8261 anchor,
8262 breakpoint.as_ref().clone(),
8263 BreakpointEditAction::Toggle,
8264 cx,
8265 );
8266 })
8267 .log_err();
8268 }
8269 })
8270 .entry(log_breakpoint_msg, None, {
8271 let breakpoint = breakpoint.clone();
8272 let weak_editor = weak_editor.clone();
8273 move |window, cx| {
8274 weak_editor
8275 .update(cx, |this, cx| {
8276 this.add_edit_breakpoint_block(
8277 anchor,
8278 breakpoint.as_ref(),
8279 BreakpointPromptEditAction::Log,
8280 window,
8281 cx,
8282 );
8283 })
8284 .log_err();
8285 }
8286 })
8287 .entry(condition_breakpoint_msg, None, {
8288 let breakpoint = breakpoint.clone();
8289 let weak_editor = weak_editor.clone();
8290 move |window, cx| {
8291 weak_editor
8292 .update(cx, |this, cx| {
8293 this.add_edit_breakpoint_block(
8294 anchor,
8295 breakpoint.as_ref(),
8296 BreakpointPromptEditAction::Condition,
8297 window,
8298 cx,
8299 );
8300 })
8301 .log_err();
8302 }
8303 })
8304 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8305 weak_editor
8306 .update(cx, |this, cx| {
8307 this.add_edit_breakpoint_block(
8308 anchor,
8309 breakpoint.as_ref(),
8310 BreakpointPromptEditAction::HitCondition,
8311 window,
8312 cx,
8313 );
8314 })
8315 .log_err();
8316 })
8317 })
8318 }
8319
8320 fn render_breakpoint(
8321 &self,
8322 position: Anchor,
8323 row: DisplayRow,
8324 breakpoint: &Breakpoint,
8325 state: Option<BreakpointSessionState>,
8326 cx: &mut Context<Self>,
8327 ) -> IconButton {
8328 let is_rejected = state.is_some_and(|s| !s.verified);
8329 // Is it a breakpoint that shows up when hovering over gutter?
8330 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8331 (false, false),
8332 |PhantomBreakpointIndicator {
8333 is_active,
8334 display_row,
8335 collides_with_existing_breakpoint,
8336 }| {
8337 (
8338 is_active && display_row == row,
8339 collides_with_existing_breakpoint,
8340 )
8341 },
8342 );
8343
8344 let (color, icon) = {
8345 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8346 (false, false) => ui::IconName::DebugBreakpoint,
8347 (true, false) => ui::IconName::DebugLogBreakpoint,
8348 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8349 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8350 };
8351
8352 let color = if is_phantom {
8353 Color::Hint
8354 } else if is_rejected {
8355 Color::Disabled
8356 } else {
8357 Color::Debugger
8358 };
8359
8360 (color, icon)
8361 };
8362
8363 let breakpoint = Arc::from(breakpoint.clone());
8364
8365 let alt_as_text = gpui::Keystroke {
8366 modifiers: Modifiers::secondary_key(),
8367 ..Default::default()
8368 };
8369 let primary_action_text = if breakpoint.is_disabled() {
8370 "Enable breakpoint"
8371 } else if is_phantom && !collides_with_existing {
8372 "Set breakpoint"
8373 } else {
8374 "Unset breakpoint"
8375 };
8376 let focus_handle = self.focus_handle.clone();
8377
8378 let meta = if is_rejected {
8379 SharedString::from("No executable code is associated with this line.")
8380 } else if collides_with_existing && !breakpoint.is_disabled() {
8381 SharedString::from(format!(
8382 "{alt_as_text}-click to disable,\nright-click for more options."
8383 ))
8384 } else {
8385 SharedString::from("Right-click for more options.")
8386 };
8387 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8388 .icon_size(IconSize::XSmall)
8389 .size(ui::ButtonSize::None)
8390 .when(is_rejected, |this| {
8391 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8392 })
8393 .icon_color(color)
8394 .style(ButtonStyle::Transparent)
8395 .on_click(cx.listener({
8396 move |editor, event: &ClickEvent, window, cx| {
8397 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8398 BreakpointEditAction::InvertState
8399 } else {
8400 BreakpointEditAction::Toggle
8401 };
8402
8403 window.focus(&editor.focus_handle(cx));
8404 editor.edit_breakpoint_at_anchor(
8405 position,
8406 breakpoint.as_ref().clone(),
8407 edit_action,
8408 cx,
8409 );
8410 }
8411 }))
8412 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8413 editor.set_breakpoint_context_menu(
8414 row,
8415 Some(position),
8416 event.position(),
8417 window,
8418 cx,
8419 );
8420 }))
8421 .tooltip(move |window, cx| {
8422 Tooltip::with_meta_in(
8423 primary_action_text,
8424 Some(&ToggleBreakpoint),
8425 meta.clone(),
8426 &focus_handle,
8427 window,
8428 cx,
8429 )
8430 })
8431 }
8432
8433 fn build_tasks_context(
8434 project: &Entity<Project>,
8435 buffer: &Entity<Buffer>,
8436 buffer_row: u32,
8437 tasks: &Arc<RunnableTasks>,
8438 cx: &mut Context<Self>,
8439 ) -> Task<Option<task::TaskContext>> {
8440 let position = Point::new(buffer_row, tasks.column);
8441 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8442 let location = Location {
8443 buffer: buffer.clone(),
8444 range: range_start..range_start,
8445 };
8446 // Fill in the environmental variables from the tree-sitter captures
8447 let mut captured_task_variables = TaskVariables::default();
8448 for (capture_name, value) in tasks.extra_variables.clone() {
8449 captured_task_variables.insert(
8450 task::VariableName::Custom(capture_name.into()),
8451 value.clone(),
8452 );
8453 }
8454 project.update(cx, |project, cx| {
8455 project.task_store().update(cx, |task_store, cx| {
8456 task_store.task_context_for_location(captured_task_variables, location, cx)
8457 })
8458 })
8459 }
8460
8461 pub fn spawn_nearest_task(
8462 &mut self,
8463 action: &SpawnNearestTask,
8464 window: &mut Window,
8465 cx: &mut Context<Self>,
8466 ) {
8467 let Some((workspace, _)) = self.workspace.clone() else {
8468 return;
8469 };
8470 let Some(project) = self.project.clone() else {
8471 return;
8472 };
8473
8474 // Try to find a closest, enclosing node using tree-sitter that has a task
8475 let Some((buffer, buffer_row, tasks)) = self
8476 .find_enclosing_node_task(cx)
8477 // Or find the task that's closest in row-distance.
8478 .or_else(|| self.find_closest_task(cx))
8479 else {
8480 return;
8481 };
8482
8483 let reveal_strategy = action.reveal;
8484 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8485 cx.spawn_in(window, async move |_, cx| {
8486 let context = task_context.await?;
8487 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8488
8489 let resolved = &mut resolved_task.resolved;
8490 resolved.reveal = reveal_strategy;
8491
8492 workspace
8493 .update_in(cx, |workspace, window, cx| {
8494 workspace.schedule_resolved_task(
8495 task_source_kind,
8496 resolved_task,
8497 false,
8498 window,
8499 cx,
8500 );
8501 })
8502 .ok()
8503 })
8504 .detach();
8505 }
8506
8507 fn find_closest_task(
8508 &mut self,
8509 cx: &mut Context<Self>,
8510 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8511 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8512
8513 let ((buffer_id, row), tasks) = self
8514 .tasks
8515 .iter()
8516 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8517
8518 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8519 let tasks = Arc::new(tasks.to_owned());
8520 Some((buffer, *row, tasks))
8521 }
8522
8523 fn find_enclosing_node_task(
8524 &mut self,
8525 cx: &mut Context<Self>,
8526 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8527 let snapshot = self.buffer.read(cx).snapshot(cx);
8528 let offset = self.selections.newest::<usize>(cx).head();
8529 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8530 let buffer_id = excerpt.buffer().remote_id();
8531
8532 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8533 let mut cursor = layer.node().walk();
8534
8535 while cursor.goto_first_child_for_byte(offset).is_some() {
8536 if cursor.node().end_byte() == offset {
8537 cursor.goto_next_sibling();
8538 }
8539 }
8540
8541 // Ascend to the smallest ancestor that contains the range and has a task.
8542 loop {
8543 let node = cursor.node();
8544 let node_range = node.byte_range();
8545 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8546
8547 // Check if this node contains our offset
8548 if node_range.start <= offset && node_range.end >= offset {
8549 // If it contains offset, check for task
8550 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8551 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8552 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8553 }
8554 }
8555
8556 if !cursor.goto_parent() {
8557 break;
8558 }
8559 }
8560 None
8561 }
8562
8563 fn render_run_indicator(
8564 &self,
8565 _style: &EditorStyle,
8566 is_active: bool,
8567 row: DisplayRow,
8568 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8569 cx: &mut Context<Self>,
8570 ) -> IconButton {
8571 let color = Color::Muted;
8572 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8573
8574 IconButton::new(
8575 ("run_indicator", row.0 as usize),
8576 ui::IconName::PlayOutlined,
8577 )
8578 .shape(ui::IconButtonShape::Square)
8579 .icon_size(IconSize::XSmall)
8580 .icon_color(color)
8581 .toggle_state(is_active)
8582 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8583 let quick_launch = match e {
8584 ClickEvent::Keyboard(_) => true,
8585 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8586 };
8587
8588 window.focus(&editor.focus_handle(cx));
8589 editor.toggle_code_actions(
8590 &ToggleCodeActions {
8591 deployed_from: Some(CodeActionSource::RunMenu(row)),
8592 quick_launch,
8593 },
8594 window,
8595 cx,
8596 );
8597 }))
8598 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8599 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8600 }))
8601 }
8602
8603 pub fn context_menu_visible(&self) -> bool {
8604 !self.edit_prediction_preview_is_active()
8605 && self
8606 .context_menu
8607 .borrow()
8608 .as_ref()
8609 .is_some_and(|menu| menu.visible())
8610 }
8611
8612 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8613 self.context_menu
8614 .borrow()
8615 .as_ref()
8616 .map(|menu| menu.origin())
8617 }
8618
8619 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8620 self.context_menu_options = Some(options);
8621 }
8622
8623 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8624 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8625
8626 fn render_edit_prediction_popover(
8627 &mut self,
8628 text_bounds: &Bounds<Pixels>,
8629 content_origin: gpui::Point<Pixels>,
8630 right_margin: Pixels,
8631 editor_snapshot: &EditorSnapshot,
8632 visible_row_range: Range<DisplayRow>,
8633 scroll_top: ScrollOffset,
8634 scroll_bottom: ScrollOffset,
8635 line_layouts: &[LineWithInvisibles],
8636 line_height: Pixels,
8637 scroll_position: gpui::Point<ScrollOffset>,
8638 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8639 newest_selection_head: Option<DisplayPoint>,
8640 editor_width: Pixels,
8641 style: &EditorStyle,
8642 window: &mut Window,
8643 cx: &mut App,
8644 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8645 if self.mode().is_minimap() {
8646 return None;
8647 }
8648 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8649
8650 if self.edit_prediction_visible_in_cursor_popover(true) {
8651 return None;
8652 }
8653
8654 match &active_edit_prediction.completion {
8655 EditPrediction::MoveWithin { target, .. } => {
8656 let target_display_point = target.to_display_point(editor_snapshot);
8657
8658 if self.edit_prediction_requires_modifier() {
8659 if !self.edit_prediction_preview_is_active() {
8660 return None;
8661 }
8662
8663 self.render_edit_prediction_modifier_jump_popover(
8664 text_bounds,
8665 content_origin,
8666 visible_row_range,
8667 line_layouts,
8668 line_height,
8669 scroll_pixel_position,
8670 newest_selection_head,
8671 target_display_point,
8672 window,
8673 cx,
8674 )
8675 } else {
8676 self.render_edit_prediction_eager_jump_popover(
8677 text_bounds,
8678 content_origin,
8679 editor_snapshot,
8680 visible_row_range,
8681 scroll_top,
8682 scroll_bottom,
8683 line_height,
8684 scroll_pixel_position,
8685 target_display_point,
8686 editor_width,
8687 window,
8688 cx,
8689 )
8690 }
8691 }
8692 EditPrediction::Edit {
8693 display_mode: EditDisplayMode::Inline,
8694 ..
8695 } => None,
8696 EditPrediction::Edit {
8697 display_mode: EditDisplayMode::TabAccept,
8698 edits,
8699 ..
8700 } => {
8701 let range = &edits.first()?.0;
8702 let target_display_point = range.end.to_display_point(editor_snapshot);
8703
8704 self.render_edit_prediction_end_of_line_popover(
8705 "Accept",
8706 editor_snapshot,
8707 visible_row_range,
8708 target_display_point,
8709 line_height,
8710 scroll_pixel_position,
8711 content_origin,
8712 editor_width,
8713 window,
8714 cx,
8715 )
8716 }
8717 EditPrediction::Edit {
8718 edits,
8719 edit_preview,
8720 display_mode: EditDisplayMode::DiffPopover,
8721 snapshot,
8722 } => self.render_edit_prediction_diff_popover(
8723 text_bounds,
8724 content_origin,
8725 right_margin,
8726 editor_snapshot,
8727 visible_row_range,
8728 line_layouts,
8729 line_height,
8730 scroll_position,
8731 scroll_pixel_position,
8732 newest_selection_head,
8733 editor_width,
8734 style,
8735 edits,
8736 edit_preview,
8737 snapshot,
8738 window,
8739 cx,
8740 ),
8741 EditPrediction::MoveOutside { snapshot, .. } => {
8742 let file_name = snapshot
8743 .file()
8744 .map(|file| file.file_name(cx))
8745 .unwrap_or("untitled");
8746 let mut element = self
8747 .render_edit_prediction_line_popover(
8748 format!("Jump to {file_name}"),
8749 Some(IconName::ZedPredict),
8750 window,
8751 cx,
8752 )
8753 .into_any();
8754
8755 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8756 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8757 let origin_y = text_bounds.size.height - size.height - px(30.);
8758 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8759 element.prepaint_at(origin, window, cx);
8760
8761 Some((element, origin))
8762 }
8763 }
8764 }
8765
8766 fn render_edit_prediction_modifier_jump_popover(
8767 &mut self,
8768 text_bounds: &Bounds<Pixels>,
8769 content_origin: gpui::Point<Pixels>,
8770 visible_row_range: Range<DisplayRow>,
8771 line_layouts: &[LineWithInvisibles],
8772 line_height: Pixels,
8773 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8774 newest_selection_head: Option<DisplayPoint>,
8775 target_display_point: DisplayPoint,
8776 window: &mut Window,
8777 cx: &mut App,
8778 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8779 let scrolled_content_origin =
8780 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8781
8782 const SCROLL_PADDING_Y: Pixels = px(12.);
8783
8784 if target_display_point.row() < visible_row_range.start {
8785 return self.render_edit_prediction_scroll_popover(
8786 |_| SCROLL_PADDING_Y,
8787 IconName::ArrowUp,
8788 visible_row_range,
8789 line_layouts,
8790 newest_selection_head,
8791 scrolled_content_origin,
8792 window,
8793 cx,
8794 );
8795 } else if target_display_point.row() >= visible_row_range.end {
8796 return self.render_edit_prediction_scroll_popover(
8797 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8798 IconName::ArrowDown,
8799 visible_row_range,
8800 line_layouts,
8801 newest_selection_head,
8802 scrolled_content_origin,
8803 window,
8804 cx,
8805 );
8806 }
8807
8808 const POLE_WIDTH: Pixels = px(2.);
8809
8810 let line_layout =
8811 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8812 let target_column = target_display_point.column() as usize;
8813
8814 let target_x = line_layout.x_for_index(target_column);
8815 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8816 - scroll_pixel_position.y;
8817
8818 let flag_on_right = target_x < text_bounds.size.width / 2.;
8819
8820 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8821 border_color.l += 0.001;
8822
8823 let mut element = v_flex()
8824 .items_end()
8825 .when(flag_on_right, |el| el.items_start())
8826 .child(if flag_on_right {
8827 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8828 .rounded_bl(px(0.))
8829 .rounded_tl(px(0.))
8830 .border_l_2()
8831 .border_color(border_color)
8832 } else {
8833 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8834 .rounded_br(px(0.))
8835 .rounded_tr(px(0.))
8836 .border_r_2()
8837 .border_color(border_color)
8838 })
8839 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8840 .into_any();
8841
8842 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8843
8844 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8845 - point(
8846 if flag_on_right {
8847 POLE_WIDTH
8848 } else {
8849 size.width - POLE_WIDTH
8850 },
8851 size.height - line_height,
8852 );
8853
8854 origin.x = origin.x.max(content_origin.x);
8855
8856 element.prepaint_at(origin, window, cx);
8857
8858 Some((element, origin))
8859 }
8860
8861 fn render_edit_prediction_scroll_popover(
8862 &mut self,
8863 to_y: impl Fn(Size<Pixels>) -> Pixels,
8864 scroll_icon: IconName,
8865 visible_row_range: Range<DisplayRow>,
8866 line_layouts: &[LineWithInvisibles],
8867 newest_selection_head: Option<DisplayPoint>,
8868 scrolled_content_origin: gpui::Point<Pixels>,
8869 window: &mut Window,
8870 cx: &mut App,
8871 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8872 let mut element = self
8873 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8874 .into_any();
8875
8876 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8877
8878 let cursor = newest_selection_head?;
8879 let cursor_row_layout =
8880 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8881 let cursor_column = cursor.column() as usize;
8882
8883 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8884
8885 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8886
8887 element.prepaint_at(origin, window, cx);
8888 Some((element, origin))
8889 }
8890
8891 fn render_edit_prediction_eager_jump_popover(
8892 &mut self,
8893 text_bounds: &Bounds<Pixels>,
8894 content_origin: gpui::Point<Pixels>,
8895 editor_snapshot: &EditorSnapshot,
8896 visible_row_range: Range<DisplayRow>,
8897 scroll_top: ScrollOffset,
8898 scroll_bottom: ScrollOffset,
8899 line_height: Pixels,
8900 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8901 target_display_point: DisplayPoint,
8902 editor_width: Pixels,
8903 window: &mut Window,
8904 cx: &mut App,
8905 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8906 if target_display_point.row().as_f64() < scroll_top {
8907 let mut element = self
8908 .render_edit_prediction_line_popover(
8909 "Jump to Edit",
8910 Some(IconName::ArrowUp),
8911 window,
8912 cx,
8913 )
8914 .into_any();
8915
8916 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8917 let offset = point(
8918 (text_bounds.size.width - size.width) / 2.,
8919 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8920 );
8921
8922 let origin = text_bounds.origin + offset;
8923 element.prepaint_at(origin, window, cx);
8924 Some((element, origin))
8925 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8926 let mut element = self
8927 .render_edit_prediction_line_popover(
8928 "Jump to Edit",
8929 Some(IconName::ArrowDown),
8930 window,
8931 cx,
8932 )
8933 .into_any();
8934
8935 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8936 let offset = point(
8937 (text_bounds.size.width - size.width) / 2.,
8938 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8939 );
8940
8941 let origin = text_bounds.origin + offset;
8942 element.prepaint_at(origin, window, cx);
8943 Some((element, origin))
8944 } else {
8945 self.render_edit_prediction_end_of_line_popover(
8946 "Jump to Edit",
8947 editor_snapshot,
8948 visible_row_range,
8949 target_display_point,
8950 line_height,
8951 scroll_pixel_position,
8952 content_origin,
8953 editor_width,
8954 window,
8955 cx,
8956 )
8957 }
8958 }
8959
8960 fn render_edit_prediction_end_of_line_popover(
8961 self: &mut Editor,
8962 label: &'static str,
8963 editor_snapshot: &EditorSnapshot,
8964 visible_row_range: Range<DisplayRow>,
8965 target_display_point: DisplayPoint,
8966 line_height: Pixels,
8967 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8968 content_origin: gpui::Point<Pixels>,
8969 editor_width: Pixels,
8970 window: &mut Window,
8971 cx: &mut App,
8972 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8973 let target_line_end = DisplayPoint::new(
8974 target_display_point.row(),
8975 editor_snapshot.line_len(target_display_point.row()),
8976 );
8977
8978 let mut element = self
8979 .render_edit_prediction_line_popover(label, None, window, cx)
8980 .into_any();
8981
8982 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8983
8984 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8985
8986 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8987 let mut origin = start_point
8988 + line_origin
8989 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8990 origin.x = origin.x.max(content_origin.x);
8991
8992 let max_x = content_origin.x + editor_width - size.width;
8993
8994 if origin.x > max_x {
8995 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8996
8997 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8998 origin.y += offset;
8999 IconName::ArrowUp
9000 } else {
9001 origin.y -= offset;
9002 IconName::ArrowDown
9003 };
9004
9005 element = self
9006 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9007 .into_any();
9008
9009 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9010
9011 origin.x = content_origin.x + editor_width - size.width - px(2.);
9012 }
9013
9014 element.prepaint_at(origin, window, cx);
9015 Some((element, origin))
9016 }
9017
9018 fn render_edit_prediction_diff_popover(
9019 self: &Editor,
9020 text_bounds: &Bounds<Pixels>,
9021 content_origin: gpui::Point<Pixels>,
9022 right_margin: Pixels,
9023 editor_snapshot: &EditorSnapshot,
9024 visible_row_range: Range<DisplayRow>,
9025 line_layouts: &[LineWithInvisibles],
9026 line_height: Pixels,
9027 scroll_position: gpui::Point<ScrollOffset>,
9028 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9029 newest_selection_head: Option<DisplayPoint>,
9030 editor_width: Pixels,
9031 style: &EditorStyle,
9032 edits: &Vec<(Range<Anchor>, String)>,
9033 edit_preview: &Option<language::EditPreview>,
9034 snapshot: &language::BufferSnapshot,
9035 window: &mut Window,
9036 cx: &mut App,
9037 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9038 let edit_start = edits
9039 .first()
9040 .unwrap()
9041 .0
9042 .start
9043 .to_display_point(editor_snapshot);
9044 let edit_end = edits
9045 .last()
9046 .unwrap()
9047 .0
9048 .end
9049 .to_display_point(editor_snapshot);
9050
9051 let is_visible = visible_row_range.contains(&edit_start.row())
9052 || visible_row_range.contains(&edit_end.row());
9053 if !is_visible {
9054 return None;
9055 }
9056
9057 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9058 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9059 } else {
9060 // Fallback for providers without edit_preview
9061 crate::edit_prediction_fallback_text(edits, cx)
9062 };
9063
9064 let styled_text = highlighted_edits.to_styled_text(&style.text);
9065 let line_count = highlighted_edits.text.lines().count();
9066
9067 const BORDER_WIDTH: Pixels = px(1.);
9068
9069 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9070 let has_keybind = keybind.is_some();
9071
9072 let mut element = h_flex()
9073 .items_start()
9074 .child(
9075 h_flex()
9076 .bg(cx.theme().colors().editor_background)
9077 .border(BORDER_WIDTH)
9078 .shadow_xs()
9079 .border_color(cx.theme().colors().border)
9080 .rounded_l_lg()
9081 .when(line_count > 1, |el| el.rounded_br_lg())
9082 .pr_1()
9083 .child(styled_text),
9084 )
9085 .child(
9086 h_flex()
9087 .h(line_height + BORDER_WIDTH * 2.)
9088 .px_1p5()
9089 .gap_1()
9090 // Workaround: For some reason, there's a gap if we don't do this
9091 .ml(-BORDER_WIDTH)
9092 .shadow(vec![gpui::BoxShadow {
9093 color: gpui::black().opacity(0.05),
9094 offset: point(px(1.), px(1.)),
9095 blur_radius: px(2.),
9096 spread_radius: px(0.),
9097 }])
9098 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9099 .border(BORDER_WIDTH)
9100 .border_color(cx.theme().colors().border)
9101 .rounded_r_lg()
9102 .id("edit_prediction_diff_popover_keybind")
9103 .when(!has_keybind, |el| {
9104 let status_colors = cx.theme().status();
9105
9106 el.bg(status_colors.error_background)
9107 .border_color(status_colors.error.opacity(0.6))
9108 .child(Icon::new(IconName::Info).color(Color::Error))
9109 .cursor_default()
9110 .hoverable_tooltip(move |_window, cx| {
9111 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9112 })
9113 })
9114 .children(keybind),
9115 )
9116 .into_any();
9117
9118 let longest_row =
9119 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9120 let longest_line_width = if visible_row_range.contains(&longest_row) {
9121 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9122 } else {
9123 layout_line(
9124 longest_row,
9125 editor_snapshot,
9126 style,
9127 editor_width,
9128 |_| false,
9129 window,
9130 cx,
9131 )
9132 .width
9133 };
9134
9135 let viewport_bounds =
9136 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9137 right: -right_margin,
9138 ..Default::default()
9139 });
9140
9141 let x_after_longest = Pixels::from(
9142 ScrollPixelOffset::from(
9143 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9144 ) - scroll_pixel_position.x,
9145 );
9146
9147 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9148
9149 // Fully visible if it can be displayed within the window (allow overlapping other
9150 // panes). However, this is only allowed if the popover starts within text_bounds.
9151 let can_position_to_the_right = x_after_longest < text_bounds.right()
9152 && x_after_longest + element_bounds.width < viewport_bounds.right();
9153
9154 let mut origin = if can_position_to_the_right {
9155 point(
9156 x_after_longest,
9157 text_bounds.origin.y
9158 + Pixels::from(
9159 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9160 - scroll_pixel_position.y,
9161 ),
9162 )
9163 } else {
9164 let cursor_row = newest_selection_head.map(|head| head.row());
9165 let above_edit = edit_start
9166 .row()
9167 .0
9168 .checked_sub(line_count as u32)
9169 .map(DisplayRow);
9170 let below_edit = Some(edit_end.row() + 1);
9171 let above_cursor =
9172 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9173 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9174
9175 // Place the edit popover adjacent to the edit if there is a location
9176 // available that is onscreen and does not obscure the cursor. Otherwise,
9177 // place it adjacent to the cursor.
9178 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9179 .into_iter()
9180 .flatten()
9181 .find(|&start_row| {
9182 let end_row = start_row + line_count as u32;
9183 visible_row_range.contains(&start_row)
9184 && visible_row_range.contains(&end_row)
9185 && cursor_row
9186 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9187 })?;
9188
9189 content_origin
9190 + point(
9191 Pixels::from(-scroll_pixel_position.x),
9192 Pixels::from(
9193 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9194 ),
9195 )
9196 };
9197
9198 origin.x -= BORDER_WIDTH;
9199
9200 window.defer_draw(element, origin, 1);
9201
9202 // Do not return an element, since it will already be drawn due to defer_draw.
9203 None
9204 }
9205
9206 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9207 px(30.)
9208 }
9209
9210 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9211 if self.read_only(cx) {
9212 cx.theme().players().read_only()
9213 } else {
9214 self.style.as_ref().unwrap().local_player
9215 }
9216 }
9217
9218 fn render_edit_prediction_accept_keybind(
9219 &self,
9220 window: &mut Window,
9221 cx: &App,
9222 ) -> Option<AnyElement> {
9223 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9224 let accept_keystroke = accept_binding.keystroke()?;
9225
9226 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9227
9228 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9229 Color::Accent
9230 } else {
9231 Color::Muted
9232 };
9233
9234 h_flex()
9235 .px_0p5()
9236 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9237 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9238 .text_size(TextSize::XSmall.rems(cx))
9239 .child(h_flex().children(ui::render_modifiers(
9240 accept_keystroke.modifiers(),
9241 PlatformStyle::platform(),
9242 Some(modifiers_color),
9243 Some(IconSize::XSmall.rems().into()),
9244 true,
9245 )))
9246 .when(is_platform_style_mac, |parent| {
9247 parent.child(accept_keystroke.key().to_string())
9248 })
9249 .when(!is_platform_style_mac, |parent| {
9250 parent.child(
9251 Key::new(
9252 util::capitalize(accept_keystroke.key()),
9253 Some(Color::Default),
9254 )
9255 .size(Some(IconSize::XSmall.rems().into())),
9256 )
9257 })
9258 .into_any()
9259 .into()
9260 }
9261
9262 fn render_edit_prediction_line_popover(
9263 &self,
9264 label: impl Into<SharedString>,
9265 icon: Option<IconName>,
9266 window: &mut Window,
9267 cx: &App,
9268 ) -> Stateful<Div> {
9269 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9270
9271 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9272 let has_keybind = keybind.is_some();
9273
9274 h_flex()
9275 .id("ep-line-popover")
9276 .py_0p5()
9277 .pl_1()
9278 .pr(padding_right)
9279 .gap_1()
9280 .rounded_md()
9281 .border_1()
9282 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9283 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9284 .shadow_xs()
9285 .when(!has_keybind, |el| {
9286 let status_colors = cx.theme().status();
9287
9288 el.bg(status_colors.error_background)
9289 .border_color(status_colors.error.opacity(0.6))
9290 .pl_2()
9291 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9292 .cursor_default()
9293 .hoverable_tooltip(move |_window, cx| {
9294 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9295 })
9296 })
9297 .children(keybind)
9298 .child(
9299 Label::new(label)
9300 .size(LabelSize::Small)
9301 .when(!has_keybind, |el| {
9302 el.color(cx.theme().status().error.into()).strikethrough()
9303 }),
9304 )
9305 .when(!has_keybind, |el| {
9306 el.child(
9307 h_flex().ml_1().child(
9308 Icon::new(IconName::Info)
9309 .size(IconSize::Small)
9310 .color(cx.theme().status().error.into()),
9311 ),
9312 )
9313 })
9314 .when_some(icon, |element, icon| {
9315 element.child(
9316 div()
9317 .mt(px(1.5))
9318 .child(Icon::new(icon).size(IconSize::Small)),
9319 )
9320 })
9321 }
9322
9323 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9324 let accent_color = cx.theme().colors().text_accent;
9325 let editor_bg_color = cx.theme().colors().editor_background;
9326 editor_bg_color.blend(accent_color.opacity(0.1))
9327 }
9328
9329 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9330 let accent_color = cx.theme().colors().text_accent;
9331 let editor_bg_color = cx.theme().colors().editor_background;
9332 editor_bg_color.blend(accent_color.opacity(0.6))
9333 }
9334 fn get_prediction_provider_icon_name(
9335 provider: &Option<RegisteredEditPredictionProvider>,
9336 ) -> IconName {
9337 match provider {
9338 Some(provider) => match provider.provider.name() {
9339 "copilot" => IconName::Copilot,
9340 "supermaven" => IconName::Supermaven,
9341 _ => IconName::ZedPredict,
9342 },
9343 None => IconName::ZedPredict,
9344 }
9345 }
9346
9347 fn render_edit_prediction_cursor_popover(
9348 &self,
9349 min_width: Pixels,
9350 max_width: Pixels,
9351 cursor_point: Point,
9352 style: &EditorStyle,
9353 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9354 _window: &Window,
9355 cx: &mut Context<Editor>,
9356 ) -> Option<AnyElement> {
9357 let provider = self.edit_prediction_provider.as_ref()?;
9358 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9359
9360 let is_refreshing = provider.provider.is_refreshing(cx);
9361
9362 fn pending_completion_container(icon: IconName) -> Div {
9363 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9364 }
9365
9366 let completion = match &self.active_edit_prediction {
9367 Some(prediction) => {
9368 if !self.has_visible_completions_menu() {
9369 const RADIUS: Pixels = px(6.);
9370 const BORDER_WIDTH: Pixels = px(1.);
9371
9372 return Some(
9373 h_flex()
9374 .elevation_2(cx)
9375 .border(BORDER_WIDTH)
9376 .border_color(cx.theme().colors().border)
9377 .when(accept_keystroke.is_none(), |el| {
9378 el.border_color(cx.theme().status().error)
9379 })
9380 .rounded(RADIUS)
9381 .rounded_tl(px(0.))
9382 .overflow_hidden()
9383 .child(div().px_1p5().child(match &prediction.completion {
9384 EditPrediction::MoveWithin { target, snapshot } => {
9385 use text::ToPoint as _;
9386 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9387 {
9388 Icon::new(IconName::ZedPredictDown)
9389 } else {
9390 Icon::new(IconName::ZedPredictUp)
9391 }
9392 }
9393 EditPrediction::MoveOutside { .. } => {
9394 // TODO [zeta2] custom icon for external jump?
9395 Icon::new(provider_icon)
9396 }
9397 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9398 }))
9399 .child(
9400 h_flex()
9401 .gap_1()
9402 .py_1()
9403 .px_2()
9404 .rounded_r(RADIUS - BORDER_WIDTH)
9405 .border_l_1()
9406 .border_color(cx.theme().colors().border)
9407 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9408 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9409 el.child(
9410 Label::new("Hold")
9411 .size(LabelSize::Small)
9412 .when(accept_keystroke.is_none(), |el| {
9413 el.strikethrough()
9414 })
9415 .line_height_style(LineHeightStyle::UiLabel),
9416 )
9417 })
9418 .id("edit_prediction_cursor_popover_keybind")
9419 .when(accept_keystroke.is_none(), |el| {
9420 let status_colors = cx.theme().status();
9421
9422 el.bg(status_colors.error_background)
9423 .border_color(status_colors.error.opacity(0.6))
9424 .child(Icon::new(IconName::Info).color(Color::Error))
9425 .cursor_default()
9426 .hoverable_tooltip(move |_window, cx| {
9427 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9428 .into()
9429 })
9430 })
9431 .when_some(
9432 accept_keystroke.as_ref(),
9433 |el, accept_keystroke| {
9434 el.child(h_flex().children(ui::render_modifiers(
9435 accept_keystroke.modifiers(),
9436 PlatformStyle::platform(),
9437 Some(Color::Default),
9438 Some(IconSize::XSmall.rems().into()),
9439 false,
9440 )))
9441 },
9442 ),
9443 )
9444 .into_any(),
9445 );
9446 }
9447
9448 self.render_edit_prediction_cursor_popover_preview(
9449 prediction,
9450 cursor_point,
9451 style,
9452 cx,
9453 )?
9454 }
9455
9456 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9457 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9458 stale_completion,
9459 cursor_point,
9460 style,
9461 cx,
9462 )?,
9463
9464 None => pending_completion_container(provider_icon)
9465 .child(Label::new("...").size(LabelSize::Small)),
9466 },
9467
9468 None => pending_completion_container(provider_icon)
9469 .child(Label::new("...").size(LabelSize::Small)),
9470 };
9471
9472 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9473 completion
9474 .with_animation(
9475 "loading-completion",
9476 Animation::new(Duration::from_secs(2))
9477 .repeat()
9478 .with_easing(pulsating_between(0.4, 0.8)),
9479 |label, delta| label.opacity(delta),
9480 )
9481 .into_any_element()
9482 } else {
9483 completion.into_any_element()
9484 };
9485
9486 let has_completion = self.active_edit_prediction.is_some();
9487
9488 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9489 Some(
9490 h_flex()
9491 .min_w(min_width)
9492 .max_w(max_width)
9493 .flex_1()
9494 .elevation_2(cx)
9495 .border_color(cx.theme().colors().border)
9496 .child(
9497 div()
9498 .flex_1()
9499 .py_1()
9500 .px_2()
9501 .overflow_hidden()
9502 .child(completion),
9503 )
9504 .when_some(accept_keystroke, |el, accept_keystroke| {
9505 if !accept_keystroke.modifiers().modified() {
9506 return el;
9507 }
9508
9509 el.child(
9510 h_flex()
9511 .h_full()
9512 .border_l_1()
9513 .rounded_r_lg()
9514 .border_color(cx.theme().colors().border)
9515 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9516 .gap_1()
9517 .py_1()
9518 .px_2()
9519 .child(
9520 h_flex()
9521 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9522 .when(is_platform_style_mac, |parent| parent.gap_1())
9523 .child(h_flex().children(ui::render_modifiers(
9524 accept_keystroke.modifiers(),
9525 PlatformStyle::platform(),
9526 Some(if !has_completion {
9527 Color::Muted
9528 } else {
9529 Color::Default
9530 }),
9531 None,
9532 false,
9533 ))),
9534 )
9535 .child(Label::new("Preview").into_any_element())
9536 .opacity(if has_completion { 1.0 } else { 0.4 }),
9537 )
9538 })
9539 .into_any(),
9540 )
9541 }
9542
9543 fn render_edit_prediction_cursor_popover_preview(
9544 &self,
9545 completion: &EditPredictionState,
9546 cursor_point: Point,
9547 style: &EditorStyle,
9548 cx: &mut Context<Editor>,
9549 ) -> Option<Div> {
9550 use text::ToPoint as _;
9551
9552 fn render_relative_row_jump(
9553 prefix: impl Into<String>,
9554 current_row: u32,
9555 target_row: u32,
9556 ) -> Div {
9557 let (row_diff, arrow) = if target_row < current_row {
9558 (current_row - target_row, IconName::ArrowUp)
9559 } else {
9560 (target_row - current_row, IconName::ArrowDown)
9561 };
9562
9563 h_flex()
9564 .child(
9565 Label::new(format!("{}{}", prefix.into(), row_diff))
9566 .color(Color::Muted)
9567 .size(LabelSize::Small),
9568 )
9569 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9570 }
9571
9572 let supports_jump = self
9573 .edit_prediction_provider
9574 .as_ref()
9575 .map(|provider| provider.provider.supports_jump_to_edit())
9576 .unwrap_or(true);
9577
9578 match &completion.completion {
9579 EditPrediction::MoveWithin {
9580 target, snapshot, ..
9581 } => {
9582 if !supports_jump {
9583 return None;
9584 }
9585
9586 Some(
9587 h_flex()
9588 .px_2()
9589 .gap_2()
9590 .flex_1()
9591 .child(
9592 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9593 Icon::new(IconName::ZedPredictDown)
9594 } else {
9595 Icon::new(IconName::ZedPredictUp)
9596 },
9597 )
9598 .child(Label::new("Jump to Edit")),
9599 )
9600 }
9601 EditPrediction::MoveOutside { snapshot, .. } => {
9602 let file_name = snapshot
9603 .file()
9604 .map(|file| file.file_name(cx))
9605 .unwrap_or("untitled");
9606 Some(
9607 h_flex()
9608 .px_2()
9609 .gap_2()
9610 .flex_1()
9611 .child(Icon::new(IconName::ZedPredict))
9612 .child(Label::new(format!("Jump to {file_name}"))),
9613 )
9614 }
9615 EditPrediction::Edit {
9616 edits,
9617 edit_preview,
9618 snapshot,
9619 display_mode: _,
9620 } => {
9621 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9622
9623 let (highlighted_edits, has_more_lines) =
9624 if let Some(edit_preview) = edit_preview.as_ref() {
9625 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9626 .first_line_preview()
9627 } else {
9628 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9629 };
9630
9631 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9632 .with_default_highlights(&style.text, highlighted_edits.highlights);
9633
9634 let preview = h_flex()
9635 .gap_1()
9636 .min_w_16()
9637 .child(styled_text)
9638 .when(has_more_lines, |parent| parent.child("…"));
9639
9640 let left = if supports_jump && first_edit_row != cursor_point.row {
9641 render_relative_row_jump("", cursor_point.row, first_edit_row)
9642 .into_any_element()
9643 } else {
9644 let icon_name =
9645 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9646 Icon::new(icon_name).into_any_element()
9647 };
9648
9649 Some(
9650 h_flex()
9651 .h_full()
9652 .flex_1()
9653 .gap_2()
9654 .pr_1()
9655 .overflow_x_hidden()
9656 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9657 .child(left)
9658 .child(preview),
9659 )
9660 }
9661 }
9662 }
9663
9664 pub fn render_context_menu(
9665 &self,
9666 style: &EditorStyle,
9667 max_height_in_lines: u32,
9668 window: &mut Window,
9669 cx: &mut Context<Editor>,
9670 ) -> Option<AnyElement> {
9671 let menu = self.context_menu.borrow();
9672 let menu = menu.as_ref()?;
9673 if !menu.visible() {
9674 return None;
9675 };
9676 Some(menu.render(style, max_height_in_lines, window, cx))
9677 }
9678
9679 fn render_context_menu_aside(
9680 &mut self,
9681 max_size: Size<Pixels>,
9682 window: &mut Window,
9683 cx: &mut Context<Editor>,
9684 ) -> Option<AnyElement> {
9685 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9686 if menu.visible() {
9687 menu.render_aside(max_size, window, cx)
9688 } else {
9689 None
9690 }
9691 })
9692 }
9693
9694 fn hide_context_menu(
9695 &mut self,
9696 window: &mut Window,
9697 cx: &mut Context<Self>,
9698 ) -> Option<CodeContextMenu> {
9699 cx.notify();
9700 self.completion_tasks.clear();
9701 let context_menu = self.context_menu.borrow_mut().take();
9702 self.stale_edit_prediction_in_menu.take();
9703 self.update_visible_edit_prediction(window, cx);
9704 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9705 && let Some(completion_provider) = &self.completion_provider
9706 {
9707 completion_provider.selection_changed(None, window, cx);
9708 }
9709 context_menu
9710 }
9711
9712 fn show_snippet_choices(
9713 &mut self,
9714 choices: &Vec<String>,
9715 selection: Range<Anchor>,
9716 cx: &mut Context<Self>,
9717 ) {
9718 let Some((_, buffer, _)) = self
9719 .buffer()
9720 .read(cx)
9721 .excerpt_containing(selection.start, cx)
9722 else {
9723 return;
9724 };
9725 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9726 else {
9727 return;
9728 };
9729 if buffer != end_buffer {
9730 log::error!("expected anchor range to have matching buffer IDs");
9731 return;
9732 }
9733
9734 let id = post_inc(&mut self.next_completion_id);
9735 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9736 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9737 CompletionsMenu::new_snippet_choices(
9738 id,
9739 true,
9740 choices,
9741 selection,
9742 buffer,
9743 snippet_sort_order,
9744 ),
9745 ));
9746 }
9747
9748 pub fn insert_snippet(
9749 &mut self,
9750 insertion_ranges: &[Range<usize>],
9751 snippet: Snippet,
9752 window: &mut Window,
9753 cx: &mut Context<Self>,
9754 ) -> Result<()> {
9755 struct Tabstop<T> {
9756 is_end_tabstop: bool,
9757 ranges: Vec<Range<T>>,
9758 choices: Option<Vec<String>>,
9759 }
9760
9761 let tabstops = self.buffer.update(cx, |buffer, cx| {
9762 let snippet_text: Arc<str> = snippet.text.clone().into();
9763 let edits = insertion_ranges
9764 .iter()
9765 .cloned()
9766 .map(|range| (range, snippet_text.clone()));
9767 let autoindent_mode = AutoindentMode::Block {
9768 original_indent_columns: Vec::new(),
9769 };
9770 buffer.edit(edits, Some(autoindent_mode), cx);
9771
9772 let snapshot = &*buffer.read(cx);
9773 let snippet = &snippet;
9774 snippet
9775 .tabstops
9776 .iter()
9777 .map(|tabstop| {
9778 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9779 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9780 });
9781 let mut tabstop_ranges = tabstop
9782 .ranges
9783 .iter()
9784 .flat_map(|tabstop_range| {
9785 let mut delta = 0_isize;
9786 insertion_ranges.iter().map(move |insertion_range| {
9787 let insertion_start = insertion_range.start as isize + delta;
9788 delta +=
9789 snippet.text.len() as isize - insertion_range.len() as isize;
9790
9791 let start = ((insertion_start + tabstop_range.start) as usize)
9792 .min(snapshot.len());
9793 let end = ((insertion_start + tabstop_range.end) as usize)
9794 .min(snapshot.len());
9795 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9796 })
9797 })
9798 .collect::<Vec<_>>();
9799 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9800
9801 Tabstop {
9802 is_end_tabstop,
9803 ranges: tabstop_ranges,
9804 choices: tabstop.choices.clone(),
9805 }
9806 })
9807 .collect::<Vec<_>>()
9808 });
9809 if let Some(tabstop) = tabstops.first() {
9810 self.change_selections(Default::default(), window, cx, |s| {
9811 // Reverse order so that the first range is the newest created selection.
9812 // Completions will use it and autoscroll will prioritize it.
9813 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9814 });
9815
9816 if let Some(choices) = &tabstop.choices
9817 && let Some(selection) = tabstop.ranges.first()
9818 {
9819 self.show_snippet_choices(choices, selection.clone(), cx)
9820 }
9821
9822 // If we're already at the last tabstop and it's at the end of the snippet,
9823 // we're done, we don't need to keep the state around.
9824 if !tabstop.is_end_tabstop {
9825 let choices = tabstops
9826 .iter()
9827 .map(|tabstop| tabstop.choices.clone())
9828 .collect();
9829
9830 let ranges = tabstops
9831 .into_iter()
9832 .map(|tabstop| tabstop.ranges)
9833 .collect::<Vec<_>>();
9834
9835 self.snippet_stack.push(SnippetState {
9836 active_index: 0,
9837 ranges,
9838 choices,
9839 });
9840 }
9841
9842 // Check whether the just-entered snippet ends with an auto-closable bracket.
9843 if self.autoclose_regions.is_empty() {
9844 let snapshot = self.buffer.read(cx).snapshot(cx);
9845 let mut all_selections = self.selections.all::<Point>(cx);
9846 for selection in &mut all_selections {
9847 let selection_head = selection.head();
9848 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9849 continue;
9850 };
9851
9852 let mut bracket_pair = None;
9853 let max_lookup_length = scope
9854 .brackets()
9855 .map(|(pair, _)| {
9856 pair.start
9857 .as_str()
9858 .chars()
9859 .count()
9860 .max(pair.end.as_str().chars().count())
9861 })
9862 .max();
9863 if let Some(max_lookup_length) = max_lookup_length {
9864 let next_text = snapshot
9865 .chars_at(selection_head)
9866 .take(max_lookup_length)
9867 .collect::<String>();
9868 let prev_text = snapshot
9869 .reversed_chars_at(selection_head)
9870 .take(max_lookup_length)
9871 .collect::<String>();
9872
9873 for (pair, enabled) in scope.brackets() {
9874 if enabled
9875 && pair.close
9876 && prev_text.starts_with(pair.start.as_str())
9877 && next_text.starts_with(pair.end.as_str())
9878 {
9879 bracket_pair = Some(pair.clone());
9880 break;
9881 }
9882 }
9883 }
9884
9885 if let Some(pair) = bracket_pair {
9886 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9887 let autoclose_enabled =
9888 self.use_autoclose && snapshot_settings.use_autoclose;
9889 if autoclose_enabled {
9890 let start = snapshot.anchor_after(selection_head);
9891 let end = snapshot.anchor_after(selection_head);
9892 self.autoclose_regions.push(AutocloseRegion {
9893 selection_id: selection.id,
9894 range: start..end,
9895 pair,
9896 });
9897 }
9898 }
9899 }
9900 }
9901 }
9902 Ok(())
9903 }
9904
9905 pub fn move_to_next_snippet_tabstop(
9906 &mut self,
9907 window: &mut Window,
9908 cx: &mut Context<Self>,
9909 ) -> bool {
9910 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9911 }
9912
9913 pub fn move_to_prev_snippet_tabstop(
9914 &mut self,
9915 window: &mut Window,
9916 cx: &mut Context<Self>,
9917 ) -> bool {
9918 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9919 }
9920
9921 pub fn move_to_snippet_tabstop(
9922 &mut self,
9923 bias: Bias,
9924 window: &mut Window,
9925 cx: &mut Context<Self>,
9926 ) -> bool {
9927 if let Some(mut snippet) = self.snippet_stack.pop() {
9928 match bias {
9929 Bias::Left => {
9930 if snippet.active_index > 0 {
9931 snippet.active_index -= 1;
9932 } else {
9933 self.snippet_stack.push(snippet);
9934 return false;
9935 }
9936 }
9937 Bias::Right => {
9938 if snippet.active_index + 1 < snippet.ranges.len() {
9939 snippet.active_index += 1;
9940 } else {
9941 self.snippet_stack.push(snippet);
9942 return false;
9943 }
9944 }
9945 }
9946 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9947 self.change_selections(Default::default(), window, cx, |s| {
9948 // Reverse order so that the first range is the newest created selection.
9949 // Completions will use it and autoscroll will prioritize it.
9950 s.select_ranges(current_ranges.iter().rev().cloned())
9951 });
9952
9953 if let Some(choices) = &snippet.choices[snippet.active_index]
9954 && let Some(selection) = current_ranges.first()
9955 {
9956 self.show_snippet_choices(choices, selection.clone(), cx);
9957 }
9958
9959 // If snippet state is not at the last tabstop, push it back on the stack
9960 if snippet.active_index + 1 < snippet.ranges.len() {
9961 self.snippet_stack.push(snippet);
9962 }
9963 return true;
9964 }
9965 }
9966
9967 false
9968 }
9969
9970 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9971 self.transact(window, cx, |this, window, cx| {
9972 this.select_all(&SelectAll, window, cx);
9973 this.insert("", window, cx);
9974 });
9975 }
9976
9977 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9978 if self.read_only(cx) {
9979 return;
9980 }
9981 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9982 self.transact(window, cx, |this, window, cx| {
9983 this.select_autoclose_pair(window, cx);
9984 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9985 if !this.linked_edit_ranges.is_empty() {
9986 let selections = this.selections.all::<MultiBufferPoint>(cx);
9987 let snapshot = this.buffer.read(cx).snapshot(cx);
9988
9989 for selection in selections.iter() {
9990 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9991 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9992 if selection_start.buffer_id != selection_end.buffer_id {
9993 continue;
9994 }
9995 if let Some(ranges) =
9996 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9997 {
9998 for (buffer, entries) in ranges {
9999 linked_ranges.entry(buffer).or_default().extend(entries);
10000 }
10001 }
10002 }
10003 }
10004
10005 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
10006 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10007 for selection in &mut selections {
10008 if selection.is_empty() {
10009 let old_head = selection.head();
10010 let mut new_head =
10011 movement::left(&display_map, old_head.to_display_point(&display_map))
10012 .to_point(&display_map);
10013 if let Some((buffer, line_buffer_range)) = display_map
10014 .buffer_snapshot
10015 .buffer_line_for_row(MultiBufferRow(old_head.row))
10016 {
10017 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10018 let indent_len = match indent_size.kind {
10019 IndentKind::Space => {
10020 buffer.settings_at(line_buffer_range.start, cx).tab_size
10021 }
10022 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10023 };
10024 if old_head.column <= indent_size.len && old_head.column > 0 {
10025 let indent_len = indent_len.get();
10026 new_head = cmp::min(
10027 new_head,
10028 MultiBufferPoint::new(
10029 old_head.row,
10030 ((old_head.column - 1) / indent_len) * indent_len,
10031 ),
10032 );
10033 }
10034 }
10035
10036 selection.set_head(new_head, SelectionGoal::None);
10037 }
10038 }
10039
10040 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10041 this.insert("", window, cx);
10042 let empty_str: Arc<str> = Arc::from("");
10043 for (buffer, edits) in linked_ranges {
10044 let snapshot = buffer.read(cx).snapshot();
10045 use text::ToPoint as TP;
10046
10047 let edits = edits
10048 .into_iter()
10049 .map(|range| {
10050 let end_point = TP::to_point(&range.end, &snapshot);
10051 let mut start_point = TP::to_point(&range.start, &snapshot);
10052
10053 if end_point == start_point {
10054 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10055 .saturating_sub(1);
10056 start_point =
10057 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10058 };
10059
10060 (start_point..end_point, empty_str.clone())
10061 })
10062 .sorted_by_key(|(range, _)| range.start)
10063 .collect::<Vec<_>>();
10064 buffer.update(cx, |this, cx| {
10065 this.edit(edits, None, cx);
10066 })
10067 }
10068 this.refresh_edit_prediction(true, false, window, cx);
10069 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
10070 });
10071 }
10072
10073 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10074 if self.read_only(cx) {
10075 return;
10076 }
10077 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10078 self.transact(window, cx, |this, window, cx| {
10079 this.change_selections(Default::default(), window, cx, |s| {
10080 s.move_with(|map, selection| {
10081 if selection.is_empty() {
10082 let cursor = movement::right(map, selection.head());
10083 selection.end = cursor;
10084 selection.reversed = true;
10085 selection.goal = SelectionGoal::None;
10086 }
10087 })
10088 });
10089 this.insert("", window, cx);
10090 this.refresh_edit_prediction(true, false, window, cx);
10091 });
10092 }
10093
10094 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10095 if self.mode.is_single_line() {
10096 cx.propagate();
10097 return;
10098 }
10099
10100 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10101 if self.move_to_prev_snippet_tabstop(window, cx) {
10102 return;
10103 }
10104 self.outdent(&Outdent, window, cx);
10105 }
10106
10107 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10108 if self.mode.is_single_line() {
10109 cx.propagate();
10110 return;
10111 }
10112
10113 if self.move_to_next_snippet_tabstop(window, cx) {
10114 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10115 return;
10116 }
10117 if self.read_only(cx) {
10118 return;
10119 }
10120 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10121 let mut selections = self.selections.all_adjusted(cx);
10122 let buffer = self.buffer.read(cx);
10123 let snapshot = buffer.snapshot(cx);
10124 let rows_iter = selections.iter().map(|s| s.head().row);
10125 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10126
10127 let has_some_cursor_in_whitespace = selections
10128 .iter()
10129 .filter(|selection| selection.is_empty())
10130 .any(|selection| {
10131 let cursor = selection.head();
10132 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10133 cursor.column < current_indent.len
10134 });
10135
10136 let mut edits = Vec::new();
10137 let mut prev_edited_row = 0;
10138 let mut row_delta = 0;
10139 for selection in &mut selections {
10140 if selection.start.row != prev_edited_row {
10141 row_delta = 0;
10142 }
10143 prev_edited_row = selection.end.row;
10144
10145 // If the selection is non-empty, then increase the indentation of the selected lines.
10146 if !selection.is_empty() {
10147 row_delta =
10148 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10149 continue;
10150 }
10151
10152 let cursor = selection.head();
10153 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10154 if let Some(suggested_indent) =
10155 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10156 {
10157 // Don't do anything if already at suggested indent
10158 // and there is any other cursor which is not
10159 if has_some_cursor_in_whitespace
10160 && cursor.column == current_indent.len
10161 && current_indent.len == suggested_indent.len
10162 {
10163 continue;
10164 }
10165
10166 // Adjust line and move cursor to suggested indent
10167 // if cursor is not at suggested indent
10168 if cursor.column < suggested_indent.len
10169 && cursor.column <= current_indent.len
10170 && current_indent.len <= suggested_indent.len
10171 {
10172 selection.start = Point::new(cursor.row, suggested_indent.len);
10173 selection.end = selection.start;
10174 if row_delta == 0 {
10175 edits.extend(Buffer::edit_for_indent_size_adjustment(
10176 cursor.row,
10177 current_indent,
10178 suggested_indent,
10179 ));
10180 row_delta = suggested_indent.len - current_indent.len;
10181 }
10182 continue;
10183 }
10184
10185 // If current indent is more than suggested indent
10186 // only move cursor to current indent and skip indent
10187 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10188 selection.start = Point::new(cursor.row, current_indent.len);
10189 selection.end = selection.start;
10190 continue;
10191 }
10192 }
10193
10194 // Otherwise, insert a hard or soft tab.
10195 let settings = buffer.language_settings_at(cursor, cx);
10196 let tab_size = if settings.hard_tabs {
10197 IndentSize::tab()
10198 } else {
10199 let tab_size = settings.tab_size.get();
10200 let indent_remainder = snapshot
10201 .text_for_range(Point::new(cursor.row, 0)..cursor)
10202 .flat_map(str::chars)
10203 .fold(row_delta % tab_size, |counter: u32, c| {
10204 if c == '\t' {
10205 0
10206 } else {
10207 (counter + 1) % tab_size
10208 }
10209 });
10210
10211 let chars_to_next_tab_stop = tab_size - indent_remainder;
10212 IndentSize::spaces(chars_to_next_tab_stop)
10213 };
10214 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10215 selection.end = selection.start;
10216 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10217 row_delta += tab_size.len;
10218 }
10219
10220 self.transact(window, cx, |this, window, cx| {
10221 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10222 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10223 this.refresh_edit_prediction(true, false, window, cx);
10224 });
10225 }
10226
10227 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10228 if self.read_only(cx) {
10229 return;
10230 }
10231 if self.mode.is_single_line() {
10232 cx.propagate();
10233 return;
10234 }
10235
10236 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10237 let mut selections = self.selections.all::<Point>(cx);
10238 let mut prev_edited_row = 0;
10239 let mut row_delta = 0;
10240 let mut edits = Vec::new();
10241 let buffer = self.buffer.read(cx);
10242 let snapshot = buffer.snapshot(cx);
10243 for selection in &mut selections {
10244 if selection.start.row != prev_edited_row {
10245 row_delta = 0;
10246 }
10247 prev_edited_row = selection.end.row;
10248
10249 row_delta =
10250 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10251 }
10252
10253 self.transact(window, cx, |this, window, cx| {
10254 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10255 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10256 });
10257 }
10258
10259 fn indent_selection(
10260 buffer: &MultiBuffer,
10261 snapshot: &MultiBufferSnapshot,
10262 selection: &mut Selection<Point>,
10263 edits: &mut Vec<(Range<Point>, String)>,
10264 delta_for_start_row: u32,
10265 cx: &App,
10266 ) -> u32 {
10267 let settings = buffer.language_settings_at(selection.start, cx);
10268 let tab_size = settings.tab_size.get();
10269 let indent_kind = if settings.hard_tabs {
10270 IndentKind::Tab
10271 } else {
10272 IndentKind::Space
10273 };
10274 let mut start_row = selection.start.row;
10275 let mut end_row = selection.end.row + 1;
10276
10277 // If a selection ends at the beginning of a line, don't indent
10278 // that last line.
10279 if selection.end.column == 0 && selection.end.row > selection.start.row {
10280 end_row -= 1;
10281 }
10282
10283 // Avoid re-indenting a row that has already been indented by a
10284 // previous selection, but still update this selection's column
10285 // to reflect that indentation.
10286 if delta_for_start_row > 0 {
10287 start_row += 1;
10288 selection.start.column += delta_for_start_row;
10289 if selection.end.row == selection.start.row {
10290 selection.end.column += delta_for_start_row;
10291 }
10292 }
10293
10294 let mut delta_for_end_row = 0;
10295 let has_multiple_rows = start_row + 1 != end_row;
10296 for row in start_row..end_row {
10297 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10298 let indent_delta = match (current_indent.kind, indent_kind) {
10299 (IndentKind::Space, IndentKind::Space) => {
10300 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10301 IndentSize::spaces(columns_to_next_tab_stop)
10302 }
10303 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10304 (_, IndentKind::Tab) => IndentSize::tab(),
10305 };
10306
10307 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10308 0
10309 } else {
10310 selection.start.column
10311 };
10312 let row_start = Point::new(row, start);
10313 edits.push((
10314 row_start..row_start,
10315 indent_delta.chars().collect::<String>(),
10316 ));
10317
10318 // Update this selection's endpoints to reflect the indentation.
10319 if row == selection.start.row {
10320 selection.start.column += indent_delta.len;
10321 }
10322 if row == selection.end.row {
10323 selection.end.column += indent_delta.len;
10324 delta_for_end_row = indent_delta.len;
10325 }
10326 }
10327
10328 if selection.start.row == selection.end.row {
10329 delta_for_start_row + delta_for_end_row
10330 } else {
10331 delta_for_end_row
10332 }
10333 }
10334
10335 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10336 if self.read_only(cx) {
10337 return;
10338 }
10339 if self.mode.is_single_line() {
10340 cx.propagate();
10341 return;
10342 }
10343
10344 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10345 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10346 let selections = self.selections.all::<Point>(cx);
10347 let mut deletion_ranges = Vec::new();
10348 let mut last_outdent = None;
10349 {
10350 let buffer = self.buffer.read(cx);
10351 let snapshot = buffer.snapshot(cx);
10352 for selection in &selections {
10353 let settings = buffer.language_settings_at(selection.start, cx);
10354 let tab_size = settings.tab_size.get();
10355 let mut rows = selection.spanned_rows(false, &display_map);
10356
10357 // Avoid re-outdenting a row that has already been outdented by a
10358 // previous selection.
10359 if let Some(last_row) = last_outdent
10360 && last_row == rows.start
10361 {
10362 rows.start = rows.start.next_row();
10363 }
10364 let has_multiple_rows = rows.len() > 1;
10365 for row in rows.iter_rows() {
10366 let indent_size = snapshot.indent_size_for_line(row);
10367 if indent_size.len > 0 {
10368 let deletion_len = match indent_size.kind {
10369 IndentKind::Space => {
10370 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10371 if columns_to_prev_tab_stop == 0 {
10372 tab_size
10373 } else {
10374 columns_to_prev_tab_stop
10375 }
10376 }
10377 IndentKind::Tab => 1,
10378 };
10379 let start = if has_multiple_rows
10380 || deletion_len > selection.start.column
10381 || indent_size.len < selection.start.column
10382 {
10383 0
10384 } else {
10385 selection.start.column - deletion_len
10386 };
10387 deletion_ranges.push(
10388 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10389 );
10390 last_outdent = Some(row);
10391 }
10392 }
10393 }
10394 }
10395
10396 self.transact(window, cx, |this, window, cx| {
10397 this.buffer.update(cx, |buffer, cx| {
10398 let empty_str: Arc<str> = Arc::default();
10399 buffer.edit(
10400 deletion_ranges
10401 .into_iter()
10402 .map(|range| (range, empty_str.clone())),
10403 None,
10404 cx,
10405 );
10406 });
10407 let selections = this.selections.all::<usize>(cx);
10408 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10409 });
10410 }
10411
10412 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10413 if self.read_only(cx) {
10414 return;
10415 }
10416 if self.mode.is_single_line() {
10417 cx.propagate();
10418 return;
10419 }
10420
10421 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10422 let selections = self
10423 .selections
10424 .all::<usize>(cx)
10425 .into_iter()
10426 .map(|s| s.range());
10427
10428 self.transact(window, cx, |this, window, cx| {
10429 this.buffer.update(cx, |buffer, cx| {
10430 buffer.autoindent_ranges(selections, cx);
10431 });
10432 let selections = this.selections.all::<usize>(cx);
10433 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10434 });
10435 }
10436
10437 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10438 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10439 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10440 let selections = self.selections.all::<Point>(cx);
10441
10442 let mut new_cursors = Vec::new();
10443 let mut edit_ranges = Vec::new();
10444 let mut selections = selections.iter().peekable();
10445 while let Some(selection) = selections.next() {
10446 let mut rows = selection.spanned_rows(false, &display_map);
10447
10448 // Accumulate contiguous regions of rows that we want to delete.
10449 while let Some(next_selection) = selections.peek() {
10450 let next_rows = next_selection.spanned_rows(false, &display_map);
10451 if next_rows.start <= rows.end {
10452 rows.end = next_rows.end;
10453 selections.next().unwrap();
10454 } else {
10455 break;
10456 }
10457 }
10458
10459 let buffer = &display_map.buffer_snapshot;
10460 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10461 let edit_end = if buffer.max_point().row >= rows.end.0 {
10462 // If there's a line after the range, delete the \n from the end of the row range
10463 Point::new(rows.end.0, 0).to_offset(buffer)
10464 } else {
10465 // If there isn't a line after the range, delete the \n from the line before the
10466 // start of the row range
10467 edit_start = edit_start.saturating_sub(1);
10468 buffer.len()
10469 };
10470
10471 let (cursor, goal) = movement::down_by_rows(
10472 &display_map,
10473 selection.head().to_display_point(&display_map),
10474 rows.len() as u32,
10475 selection.goal,
10476 false,
10477 &self.text_layout_details(window),
10478 );
10479
10480 new_cursors.push((
10481 selection.id,
10482 buffer.anchor_after(cursor.to_point(&display_map)),
10483 goal,
10484 ));
10485 edit_ranges.push(edit_start..edit_end);
10486 }
10487
10488 self.transact(window, cx, |this, window, cx| {
10489 let buffer = this.buffer.update(cx, |buffer, cx| {
10490 let empty_str: Arc<str> = Arc::default();
10491 buffer.edit(
10492 edit_ranges
10493 .into_iter()
10494 .map(|range| (range, empty_str.clone())),
10495 None,
10496 cx,
10497 );
10498 buffer.snapshot(cx)
10499 });
10500 let new_selections = new_cursors
10501 .into_iter()
10502 .map(|(id, cursor, goal)| {
10503 let cursor = cursor.to_point(&buffer);
10504 Selection {
10505 id,
10506 start: cursor,
10507 end: cursor,
10508 reversed: false,
10509 goal,
10510 }
10511 })
10512 .collect();
10513
10514 this.change_selections(Default::default(), window, cx, |s| {
10515 s.select(new_selections);
10516 });
10517 });
10518 }
10519
10520 pub fn join_lines_impl(
10521 &mut self,
10522 insert_whitespace: bool,
10523 window: &mut Window,
10524 cx: &mut Context<Self>,
10525 ) {
10526 if self.read_only(cx) {
10527 return;
10528 }
10529 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10530 for selection in self.selections.all::<Point>(cx) {
10531 let start = MultiBufferRow(selection.start.row);
10532 // Treat single line selections as if they include the next line. Otherwise this action
10533 // would do nothing for single line selections individual cursors.
10534 let end = if selection.start.row == selection.end.row {
10535 MultiBufferRow(selection.start.row + 1)
10536 } else {
10537 MultiBufferRow(selection.end.row)
10538 };
10539
10540 if let Some(last_row_range) = row_ranges.last_mut()
10541 && start <= last_row_range.end
10542 {
10543 last_row_range.end = end;
10544 continue;
10545 }
10546 row_ranges.push(start..end);
10547 }
10548
10549 let snapshot = self.buffer.read(cx).snapshot(cx);
10550 let mut cursor_positions = Vec::new();
10551 for row_range in &row_ranges {
10552 let anchor = snapshot.anchor_before(Point::new(
10553 row_range.end.previous_row().0,
10554 snapshot.line_len(row_range.end.previous_row()),
10555 ));
10556 cursor_positions.push(anchor..anchor);
10557 }
10558
10559 self.transact(window, cx, |this, window, cx| {
10560 for row_range in row_ranges.into_iter().rev() {
10561 for row in row_range.iter_rows().rev() {
10562 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10563 let next_line_row = row.next_row();
10564 let indent = snapshot.indent_size_for_line(next_line_row);
10565 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10566
10567 let replace =
10568 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10569 " "
10570 } else {
10571 ""
10572 };
10573
10574 this.buffer.update(cx, |buffer, cx| {
10575 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10576 });
10577 }
10578 }
10579
10580 this.change_selections(Default::default(), window, cx, |s| {
10581 s.select_anchor_ranges(cursor_positions)
10582 });
10583 });
10584 }
10585
10586 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10587 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10588 self.join_lines_impl(true, window, cx);
10589 }
10590
10591 pub fn sort_lines_case_sensitive(
10592 &mut self,
10593 _: &SortLinesCaseSensitive,
10594 window: &mut Window,
10595 cx: &mut Context<Self>,
10596 ) {
10597 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10598 }
10599
10600 pub fn sort_lines_by_length(
10601 &mut self,
10602 _: &SortLinesByLength,
10603 window: &mut Window,
10604 cx: &mut Context<Self>,
10605 ) {
10606 self.manipulate_immutable_lines(window, cx, |lines| {
10607 lines.sort_by_key(|&line| line.chars().count())
10608 })
10609 }
10610
10611 pub fn sort_lines_case_insensitive(
10612 &mut self,
10613 _: &SortLinesCaseInsensitive,
10614 window: &mut Window,
10615 cx: &mut Context<Self>,
10616 ) {
10617 self.manipulate_immutable_lines(window, cx, |lines| {
10618 lines.sort_by_key(|line| line.to_lowercase())
10619 })
10620 }
10621
10622 pub fn unique_lines_case_insensitive(
10623 &mut self,
10624 _: &UniqueLinesCaseInsensitive,
10625 window: &mut Window,
10626 cx: &mut Context<Self>,
10627 ) {
10628 self.manipulate_immutable_lines(window, cx, |lines| {
10629 let mut seen = HashSet::default();
10630 lines.retain(|line| seen.insert(line.to_lowercase()));
10631 })
10632 }
10633
10634 pub fn unique_lines_case_sensitive(
10635 &mut self,
10636 _: &UniqueLinesCaseSensitive,
10637 window: &mut Window,
10638 cx: &mut Context<Self>,
10639 ) {
10640 self.manipulate_immutable_lines(window, cx, |lines| {
10641 let mut seen = HashSet::default();
10642 lines.retain(|line| seen.insert(*line));
10643 })
10644 }
10645
10646 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10647 let snapshot = self.buffer.read(cx).snapshot(cx);
10648 for selection in self.selections.disjoint_anchors_arc().iter() {
10649 if snapshot
10650 .language_at(selection.start)
10651 .and_then(|lang| lang.config().wrap_characters.as_ref())
10652 .is_some()
10653 {
10654 return true;
10655 }
10656 }
10657 false
10658 }
10659
10660 fn wrap_selections_in_tag(
10661 &mut self,
10662 _: &WrapSelectionsInTag,
10663 window: &mut Window,
10664 cx: &mut Context<Self>,
10665 ) {
10666 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10667
10668 let snapshot = self.buffer.read(cx).snapshot(cx);
10669
10670 let mut edits = Vec::new();
10671 let mut boundaries = Vec::new();
10672
10673 for selection in self.selections.all::<Point>(cx).iter() {
10674 let Some(wrap_config) = snapshot
10675 .language_at(selection.start)
10676 .and_then(|lang| lang.config().wrap_characters.clone())
10677 else {
10678 continue;
10679 };
10680
10681 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10682 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10683
10684 let start_before = snapshot.anchor_before(selection.start);
10685 let end_after = snapshot.anchor_after(selection.end);
10686
10687 edits.push((start_before..start_before, open_tag));
10688 edits.push((end_after..end_after, close_tag));
10689
10690 boundaries.push((
10691 start_before,
10692 end_after,
10693 wrap_config.start_prefix.len(),
10694 wrap_config.end_suffix.len(),
10695 ));
10696 }
10697
10698 if edits.is_empty() {
10699 return;
10700 }
10701
10702 self.transact(window, cx, |this, window, cx| {
10703 let buffer = this.buffer.update(cx, |buffer, cx| {
10704 buffer.edit(edits, None, cx);
10705 buffer.snapshot(cx)
10706 });
10707
10708 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10709 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10710 boundaries.into_iter()
10711 {
10712 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10713 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10714 new_selections.push(open_offset..open_offset);
10715 new_selections.push(close_offset..close_offset);
10716 }
10717
10718 this.change_selections(Default::default(), window, cx, |s| {
10719 s.select_ranges(new_selections);
10720 });
10721
10722 this.request_autoscroll(Autoscroll::fit(), cx);
10723 });
10724 }
10725
10726 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10727 let Some(project) = self.project.clone() else {
10728 return;
10729 };
10730 self.reload(project, window, cx)
10731 .detach_and_notify_err(window, cx);
10732 }
10733
10734 pub fn restore_file(
10735 &mut self,
10736 _: &::git::RestoreFile,
10737 window: &mut Window,
10738 cx: &mut Context<Self>,
10739 ) {
10740 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10741 let mut buffer_ids = HashSet::default();
10742 let snapshot = self.buffer().read(cx).snapshot(cx);
10743 for selection in self.selections.all::<usize>(cx) {
10744 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10745 }
10746
10747 let buffer = self.buffer().read(cx);
10748 let ranges = buffer_ids
10749 .into_iter()
10750 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10751 .collect::<Vec<_>>();
10752
10753 self.restore_hunks_in_ranges(ranges, window, cx);
10754 }
10755
10756 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10757 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10758 let selections = self
10759 .selections
10760 .all(cx)
10761 .into_iter()
10762 .map(|s| s.range())
10763 .collect();
10764 self.restore_hunks_in_ranges(selections, window, cx);
10765 }
10766
10767 pub fn restore_hunks_in_ranges(
10768 &mut self,
10769 ranges: Vec<Range<Point>>,
10770 window: &mut Window,
10771 cx: &mut Context<Editor>,
10772 ) {
10773 let mut revert_changes = HashMap::default();
10774 let chunk_by = self
10775 .snapshot(window, cx)
10776 .hunks_for_ranges(ranges)
10777 .into_iter()
10778 .chunk_by(|hunk| hunk.buffer_id);
10779 for (buffer_id, hunks) in &chunk_by {
10780 let hunks = hunks.collect::<Vec<_>>();
10781 for hunk in &hunks {
10782 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10783 }
10784 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10785 }
10786 drop(chunk_by);
10787 if !revert_changes.is_empty() {
10788 self.transact(window, cx, |editor, window, cx| {
10789 editor.restore(revert_changes, window, cx);
10790 });
10791 }
10792 }
10793
10794 pub fn open_active_item_in_terminal(
10795 &mut self,
10796 _: &OpenInTerminal,
10797 window: &mut Window,
10798 cx: &mut Context<Self>,
10799 ) {
10800 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10801 let project_path = buffer.read(cx).project_path(cx)?;
10802 let project = self.project()?.read(cx);
10803 let entry = project.entry_for_path(&project_path, cx)?;
10804 let parent = match &entry.canonical_path {
10805 Some(canonical_path) => canonical_path.to_path_buf(),
10806 None => project.absolute_path(&project_path, cx)?,
10807 }
10808 .parent()?
10809 .to_path_buf();
10810 Some(parent)
10811 }) {
10812 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10813 }
10814 }
10815
10816 fn set_breakpoint_context_menu(
10817 &mut self,
10818 display_row: DisplayRow,
10819 position: Option<Anchor>,
10820 clicked_point: gpui::Point<Pixels>,
10821 window: &mut Window,
10822 cx: &mut Context<Self>,
10823 ) {
10824 let source = self
10825 .buffer
10826 .read(cx)
10827 .snapshot(cx)
10828 .anchor_before(Point::new(display_row.0, 0u32));
10829
10830 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10831
10832 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10833 self,
10834 source,
10835 clicked_point,
10836 context_menu,
10837 window,
10838 cx,
10839 );
10840 }
10841
10842 fn add_edit_breakpoint_block(
10843 &mut self,
10844 anchor: Anchor,
10845 breakpoint: &Breakpoint,
10846 edit_action: BreakpointPromptEditAction,
10847 window: &mut Window,
10848 cx: &mut Context<Self>,
10849 ) {
10850 let weak_editor = cx.weak_entity();
10851 let bp_prompt = cx.new(|cx| {
10852 BreakpointPromptEditor::new(
10853 weak_editor,
10854 anchor,
10855 breakpoint.clone(),
10856 edit_action,
10857 window,
10858 cx,
10859 )
10860 });
10861
10862 let height = bp_prompt.update(cx, |this, cx| {
10863 this.prompt
10864 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10865 });
10866 let cloned_prompt = bp_prompt.clone();
10867 let blocks = vec![BlockProperties {
10868 style: BlockStyle::Sticky,
10869 placement: BlockPlacement::Above(anchor),
10870 height: Some(height),
10871 render: Arc::new(move |cx| {
10872 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10873 cloned_prompt.clone().into_any_element()
10874 }),
10875 priority: 0,
10876 }];
10877
10878 let focus_handle = bp_prompt.focus_handle(cx);
10879 window.focus(&focus_handle);
10880
10881 let block_ids = self.insert_blocks(blocks, None, cx);
10882 bp_prompt.update(cx, |prompt, _| {
10883 prompt.add_block_ids(block_ids);
10884 });
10885 }
10886
10887 pub(crate) fn breakpoint_at_row(
10888 &self,
10889 row: u32,
10890 window: &mut Window,
10891 cx: &mut Context<Self>,
10892 ) -> Option<(Anchor, Breakpoint)> {
10893 let snapshot = self.snapshot(window, cx);
10894 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10895
10896 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10897 }
10898
10899 pub(crate) fn breakpoint_at_anchor(
10900 &self,
10901 breakpoint_position: Anchor,
10902 snapshot: &EditorSnapshot,
10903 cx: &mut Context<Self>,
10904 ) -> Option<(Anchor, Breakpoint)> {
10905 let buffer = self
10906 .buffer
10907 .read(cx)
10908 .buffer_for_anchor(breakpoint_position, cx)?;
10909
10910 let enclosing_excerpt = breakpoint_position.excerpt_id;
10911 let buffer_snapshot = buffer.read(cx).snapshot();
10912
10913 let row = buffer_snapshot
10914 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10915 .row;
10916
10917 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10918 let anchor_end = snapshot
10919 .buffer_snapshot
10920 .anchor_after(Point::new(row, line_len));
10921
10922 self.breakpoint_store
10923 .as_ref()?
10924 .read_with(cx, |breakpoint_store, cx| {
10925 breakpoint_store
10926 .breakpoints(
10927 &buffer,
10928 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10929 &buffer_snapshot,
10930 cx,
10931 )
10932 .next()
10933 .and_then(|(bp, _)| {
10934 let breakpoint_row = buffer_snapshot
10935 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10936 .row;
10937
10938 if breakpoint_row == row {
10939 snapshot
10940 .buffer_snapshot
10941 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10942 .map(|position| (position, bp.bp.clone()))
10943 } else {
10944 None
10945 }
10946 })
10947 })
10948 }
10949
10950 pub fn edit_log_breakpoint(
10951 &mut self,
10952 _: &EditLogBreakpoint,
10953 window: &mut Window,
10954 cx: &mut Context<Self>,
10955 ) {
10956 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10957 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10958 message: None,
10959 state: BreakpointState::Enabled,
10960 condition: None,
10961 hit_condition: None,
10962 });
10963
10964 self.add_edit_breakpoint_block(
10965 anchor,
10966 &breakpoint,
10967 BreakpointPromptEditAction::Log,
10968 window,
10969 cx,
10970 );
10971 }
10972 }
10973
10974 fn breakpoints_at_cursors(
10975 &self,
10976 window: &mut Window,
10977 cx: &mut Context<Self>,
10978 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10979 let snapshot = self.snapshot(window, cx);
10980 let cursors = self
10981 .selections
10982 .disjoint_anchors_arc()
10983 .iter()
10984 .map(|selection| {
10985 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10986
10987 let breakpoint_position = self
10988 .breakpoint_at_row(cursor_position.row, window, cx)
10989 .map(|bp| bp.0)
10990 .unwrap_or_else(|| {
10991 snapshot
10992 .display_snapshot
10993 .buffer_snapshot
10994 .anchor_after(Point::new(cursor_position.row, 0))
10995 });
10996
10997 let breakpoint = self
10998 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10999 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11000
11001 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11002 })
11003 // 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.
11004 .collect::<HashMap<Anchor, _>>();
11005
11006 cursors.into_iter().collect()
11007 }
11008
11009 pub fn enable_breakpoint(
11010 &mut self,
11011 _: &crate::actions::EnableBreakpoint,
11012 window: &mut Window,
11013 cx: &mut Context<Self>,
11014 ) {
11015 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11016 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11017 continue;
11018 };
11019 self.edit_breakpoint_at_anchor(
11020 anchor,
11021 breakpoint,
11022 BreakpointEditAction::InvertState,
11023 cx,
11024 );
11025 }
11026 }
11027
11028 pub fn disable_breakpoint(
11029 &mut self,
11030 _: &crate::actions::DisableBreakpoint,
11031 window: &mut Window,
11032 cx: &mut Context<Self>,
11033 ) {
11034 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11035 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11036 continue;
11037 };
11038 self.edit_breakpoint_at_anchor(
11039 anchor,
11040 breakpoint,
11041 BreakpointEditAction::InvertState,
11042 cx,
11043 );
11044 }
11045 }
11046
11047 pub fn toggle_breakpoint(
11048 &mut self,
11049 _: &crate::actions::ToggleBreakpoint,
11050 window: &mut Window,
11051 cx: &mut Context<Self>,
11052 ) {
11053 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11054 if let Some(breakpoint) = breakpoint {
11055 self.edit_breakpoint_at_anchor(
11056 anchor,
11057 breakpoint,
11058 BreakpointEditAction::Toggle,
11059 cx,
11060 );
11061 } else {
11062 self.edit_breakpoint_at_anchor(
11063 anchor,
11064 Breakpoint::new_standard(),
11065 BreakpointEditAction::Toggle,
11066 cx,
11067 );
11068 }
11069 }
11070 }
11071
11072 pub fn edit_breakpoint_at_anchor(
11073 &mut self,
11074 breakpoint_position: Anchor,
11075 breakpoint: Breakpoint,
11076 edit_action: BreakpointEditAction,
11077 cx: &mut Context<Self>,
11078 ) {
11079 let Some(breakpoint_store) = &self.breakpoint_store else {
11080 return;
11081 };
11082
11083 let Some(buffer) = self
11084 .buffer
11085 .read(cx)
11086 .buffer_for_anchor(breakpoint_position, cx)
11087 else {
11088 return;
11089 };
11090
11091 breakpoint_store.update(cx, |breakpoint_store, cx| {
11092 breakpoint_store.toggle_breakpoint(
11093 buffer,
11094 BreakpointWithPosition {
11095 position: breakpoint_position.text_anchor,
11096 bp: breakpoint,
11097 },
11098 edit_action,
11099 cx,
11100 );
11101 });
11102
11103 cx.notify();
11104 }
11105
11106 #[cfg(any(test, feature = "test-support"))]
11107 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11108 self.breakpoint_store.clone()
11109 }
11110
11111 pub fn prepare_restore_change(
11112 &self,
11113 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11114 hunk: &MultiBufferDiffHunk,
11115 cx: &mut App,
11116 ) -> Option<()> {
11117 if hunk.is_created_file() {
11118 return None;
11119 }
11120 let buffer = self.buffer.read(cx);
11121 let diff = buffer.diff_for(hunk.buffer_id)?;
11122 let buffer = buffer.buffer(hunk.buffer_id)?;
11123 let buffer = buffer.read(cx);
11124 let original_text = diff
11125 .read(cx)
11126 .base_text()
11127 .as_rope()
11128 .slice(hunk.diff_base_byte_range.clone());
11129 let buffer_snapshot = buffer.snapshot();
11130 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11131 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11132 probe
11133 .0
11134 .start
11135 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11136 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11137 }) {
11138 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11139 Some(())
11140 } else {
11141 None
11142 }
11143 }
11144
11145 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11146 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11147 }
11148
11149 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11150 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11151 }
11152
11153 fn manipulate_lines<M>(
11154 &mut self,
11155 window: &mut Window,
11156 cx: &mut Context<Self>,
11157 mut manipulate: M,
11158 ) where
11159 M: FnMut(&str) -> LineManipulationResult,
11160 {
11161 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11162
11163 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11164 let buffer = self.buffer.read(cx).snapshot(cx);
11165
11166 let mut edits = Vec::new();
11167
11168 let selections = self.selections.all::<Point>(cx);
11169 let mut selections = selections.iter().peekable();
11170 let mut contiguous_row_selections = Vec::new();
11171 let mut new_selections = Vec::new();
11172 let mut added_lines = 0;
11173 let mut removed_lines = 0;
11174
11175 while let Some(selection) = selections.next() {
11176 let (start_row, end_row) = consume_contiguous_rows(
11177 &mut contiguous_row_selections,
11178 selection,
11179 &display_map,
11180 &mut selections,
11181 );
11182
11183 let start_point = Point::new(start_row.0, 0);
11184 let end_point = Point::new(
11185 end_row.previous_row().0,
11186 buffer.line_len(end_row.previous_row()),
11187 );
11188 let text = buffer
11189 .text_for_range(start_point..end_point)
11190 .collect::<String>();
11191
11192 let LineManipulationResult {
11193 new_text,
11194 line_count_before,
11195 line_count_after,
11196 } = manipulate(&text);
11197
11198 edits.push((start_point..end_point, new_text));
11199
11200 // Selections must change based on added and removed line count
11201 let start_row =
11202 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11203 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11204 new_selections.push(Selection {
11205 id: selection.id,
11206 start: start_row,
11207 end: end_row,
11208 goal: SelectionGoal::None,
11209 reversed: selection.reversed,
11210 });
11211
11212 if line_count_after > line_count_before {
11213 added_lines += line_count_after - line_count_before;
11214 } else if line_count_before > line_count_after {
11215 removed_lines += line_count_before - line_count_after;
11216 }
11217 }
11218
11219 self.transact(window, cx, |this, window, cx| {
11220 let buffer = this.buffer.update(cx, |buffer, cx| {
11221 buffer.edit(edits, None, cx);
11222 buffer.snapshot(cx)
11223 });
11224
11225 // Recalculate offsets on newly edited buffer
11226 let new_selections = new_selections
11227 .iter()
11228 .map(|s| {
11229 let start_point = Point::new(s.start.0, 0);
11230 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11231 Selection {
11232 id: s.id,
11233 start: buffer.point_to_offset(start_point),
11234 end: buffer.point_to_offset(end_point),
11235 goal: s.goal,
11236 reversed: s.reversed,
11237 }
11238 })
11239 .collect();
11240
11241 this.change_selections(Default::default(), window, cx, |s| {
11242 s.select(new_selections);
11243 });
11244
11245 this.request_autoscroll(Autoscroll::fit(), cx);
11246 });
11247 }
11248
11249 fn manipulate_immutable_lines<Fn>(
11250 &mut self,
11251 window: &mut Window,
11252 cx: &mut Context<Self>,
11253 mut callback: Fn,
11254 ) where
11255 Fn: FnMut(&mut Vec<&str>),
11256 {
11257 self.manipulate_lines(window, cx, |text| {
11258 let mut lines: Vec<&str> = text.split('\n').collect();
11259 let line_count_before = lines.len();
11260
11261 callback(&mut lines);
11262
11263 LineManipulationResult {
11264 new_text: lines.join("\n"),
11265 line_count_before,
11266 line_count_after: lines.len(),
11267 }
11268 });
11269 }
11270
11271 fn manipulate_mutable_lines<Fn>(
11272 &mut self,
11273 window: &mut Window,
11274 cx: &mut Context<Self>,
11275 mut callback: Fn,
11276 ) where
11277 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11278 {
11279 self.manipulate_lines(window, cx, |text| {
11280 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11281 let line_count_before = lines.len();
11282
11283 callback(&mut lines);
11284
11285 LineManipulationResult {
11286 new_text: lines.join("\n"),
11287 line_count_before,
11288 line_count_after: lines.len(),
11289 }
11290 });
11291 }
11292
11293 pub fn convert_indentation_to_spaces(
11294 &mut self,
11295 _: &ConvertIndentationToSpaces,
11296 window: &mut Window,
11297 cx: &mut Context<Self>,
11298 ) {
11299 let settings = self.buffer.read(cx).language_settings(cx);
11300 let tab_size = settings.tab_size.get() as usize;
11301
11302 self.manipulate_mutable_lines(window, cx, |lines| {
11303 // Allocates a reasonably sized scratch buffer once for the whole loop
11304 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11305 // Avoids recomputing spaces that could be inserted many times
11306 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11307 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11308 .collect();
11309
11310 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11311 let mut chars = line.as_ref().chars();
11312 let mut col = 0;
11313 let mut changed = false;
11314
11315 for ch in chars.by_ref() {
11316 match ch {
11317 ' ' => {
11318 reindented_line.push(' ');
11319 col += 1;
11320 }
11321 '\t' => {
11322 // \t are converted to spaces depending on the current column
11323 let spaces_len = tab_size - (col % tab_size);
11324 reindented_line.extend(&space_cache[spaces_len - 1]);
11325 col += spaces_len;
11326 changed = true;
11327 }
11328 _ => {
11329 // If we dont append before break, the character is consumed
11330 reindented_line.push(ch);
11331 break;
11332 }
11333 }
11334 }
11335
11336 if !changed {
11337 reindented_line.clear();
11338 continue;
11339 }
11340 // Append the rest of the line and replace old reference with new one
11341 reindented_line.extend(chars);
11342 *line = Cow::Owned(reindented_line.clone());
11343 reindented_line.clear();
11344 }
11345 });
11346 }
11347
11348 pub fn convert_indentation_to_tabs(
11349 &mut self,
11350 _: &ConvertIndentationToTabs,
11351 window: &mut Window,
11352 cx: &mut Context<Self>,
11353 ) {
11354 let settings = self.buffer.read(cx).language_settings(cx);
11355 let tab_size = settings.tab_size.get() as usize;
11356
11357 self.manipulate_mutable_lines(window, cx, |lines| {
11358 // Allocates a reasonably sized buffer once for the whole loop
11359 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11360 // Avoids recomputing spaces that could be inserted many times
11361 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11362 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11363 .collect();
11364
11365 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11366 let mut chars = line.chars();
11367 let mut spaces_count = 0;
11368 let mut first_non_indent_char = None;
11369 let mut changed = false;
11370
11371 for ch in chars.by_ref() {
11372 match ch {
11373 ' ' => {
11374 // Keep track of spaces. Append \t when we reach tab_size
11375 spaces_count += 1;
11376 changed = true;
11377 if spaces_count == tab_size {
11378 reindented_line.push('\t');
11379 spaces_count = 0;
11380 }
11381 }
11382 '\t' => {
11383 reindented_line.push('\t');
11384 spaces_count = 0;
11385 }
11386 _ => {
11387 // Dont append it yet, we might have remaining spaces
11388 first_non_indent_char = Some(ch);
11389 break;
11390 }
11391 }
11392 }
11393
11394 if !changed {
11395 reindented_line.clear();
11396 continue;
11397 }
11398 // Remaining spaces that didn't make a full tab stop
11399 if spaces_count > 0 {
11400 reindented_line.extend(&space_cache[spaces_count - 1]);
11401 }
11402 // If we consume an extra character that was not indentation, add it back
11403 if let Some(extra_char) = first_non_indent_char {
11404 reindented_line.push(extra_char);
11405 }
11406 // Append the rest of the line and replace old reference with new one
11407 reindented_line.extend(chars);
11408 *line = Cow::Owned(reindented_line.clone());
11409 reindented_line.clear();
11410 }
11411 });
11412 }
11413
11414 pub fn convert_to_upper_case(
11415 &mut self,
11416 _: &ConvertToUpperCase,
11417 window: &mut Window,
11418 cx: &mut Context<Self>,
11419 ) {
11420 self.manipulate_text(window, cx, |text| text.to_uppercase())
11421 }
11422
11423 pub fn convert_to_lower_case(
11424 &mut self,
11425 _: &ConvertToLowerCase,
11426 window: &mut Window,
11427 cx: &mut Context<Self>,
11428 ) {
11429 self.manipulate_text(window, cx, |text| text.to_lowercase())
11430 }
11431
11432 pub fn convert_to_title_case(
11433 &mut self,
11434 _: &ConvertToTitleCase,
11435 window: &mut Window,
11436 cx: &mut Context<Self>,
11437 ) {
11438 self.manipulate_text(window, cx, |text| {
11439 text.split('\n')
11440 .map(|line| line.to_case(Case::Title))
11441 .join("\n")
11442 })
11443 }
11444
11445 pub fn convert_to_snake_case(
11446 &mut self,
11447 _: &ConvertToSnakeCase,
11448 window: &mut Window,
11449 cx: &mut Context<Self>,
11450 ) {
11451 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11452 }
11453
11454 pub fn convert_to_kebab_case(
11455 &mut self,
11456 _: &ConvertToKebabCase,
11457 window: &mut Window,
11458 cx: &mut Context<Self>,
11459 ) {
11460 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11461 }
11462
11463 pub fn convert_to_upper_camel_case(
11464 &mut self,
11465 _: &ConvertToUpperCamelCase,
11466 window: &mut Window,
11467 cx: &mut Context<Self>,
11468 ) {
11469 self.manipulate_text(window, cx, |text| {
11470 text.split('\n')
11471 .map(|line| line.to_case(Case::UpperCamel))
11472 .join("\n")
11473 })
11474 }
11475
11476 pub fn convert_to_lower_camel_case(
11477 &mut self,
11478 _: &ConvertToLowerCamelCase,
11479 window: &mut Window,
11480 cx: &mut Context<Self>,
11481 ) {
11482 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11483 }
11484
11485 pub fn convert_to_opposite_case(
11486 &mut self,
11487 _: &ConvertToOppositeCase,
11488 window: &mut Window,
11489 cx: &mut Context<Self>,
11490 ) {
11491 self.manipulate_text(window, cx, |text| {
11492 text.chars()
11493 .fold(String::with_capacity(text.len()), |mut t, c| {
11494 if c.is_uppercase() {
11495 t.extend(c.to_lowercase());
11496 } else {
11497 t.extend(c.to_uppercase());
11498 }
11499 t
11500 })
11501 })
11502 }
11503
11504 pub fn convert_to_sentence_case(
11505 &mut self,
11506 _: &ConvertToSentenceCase,
11507 window: &mut Window,
11508 cx: &mut Context<Self>,
11509 ) {
11510 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11511 }
11512
11513 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11514 self.manipulate_text(window, cx, |text| {
11515 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11516 if has_upper_case_characters {
11517 text.to_lowercase()
11518 } else {
11519 text.to_uppercase()
11520 }
11521 })
11522 }
11523
11524 pub fn convert_to_rot13(
11525 &mut self,
11526 _: &ConvertToRot13,
11527 window: &mut Window,
11528 cx: &mut Context<Self>,
11529 ) {
11530 self.manipulate_text(window, cx, |text| {
11531 text.chars()
11532 .map(|c| match c {
11533 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11534 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11535 _ => c,
11536 })
11537 .collect()
11538 })
11539 }
11540
11541 pub fn convert_to_rot47(
11542 &mut self,
11543 _: &ConvertToRot47,
11544 window: &mut Window,
11545 cx: &mut Context<Self>,
11546 ) {
11547 self.manipulate_text(window, cx, |text| {
11548 text.chars()
11549 .map(|c| {
11550 let code_point = c as u32;
11551 if code_point >= 33 && code_point <= 126 {
11552 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11553 }
11554 c
11555 })
11556 .collect()
11557 })
11558 }
11559
11560 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11561 where
11562 Fn: FnMut(&str) -> String,
11563 {
11564 let buffer = self.buffer.read(cx).snapshot(cx);
11565
11566 let mut new_selections = Vec::new();
11567 let mut edits = Vec::new();
11568 let mut selection_adjustment = 0i32;
11569
11570 for selection in self.selections.all_adjusted(cx) {
11571 let selection_is_empty = selection.is_empty();
11572
11573 let (start, end) = if selection_is_empty {
11574 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11575 (word_range.start, word_range.end)
11576 } else {
11577 (
11578 buffer.point_to_offset(selection.start),
11579 buffer.point_to_offset(selection.end),
11580 )
11581 };
11582
11583 let text = buffer.text_for_range(start..end).collect::<String>();
11584 let old_length = text.len() as i32;
11585 let text = callback(&text);
11586
11587 new_selections.push(Selection {
11588 start: (start as i32 - selection_adjustment) as usize,
11589 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11590 goal: SelectionGoal::None,
11591 id: selection.id,
11592 reversed: selection.reversed,
11593 });
11594
11595 selection_adjustment += old_length - text.len() as i32;
11596
11597 edits.push((start..end, text));
11598 }
11599
11600 self.transact(window, cx, |this, window, cx| {
11601 this.buffer.update(cx, |buffer, cx| {
11602 buffer.edit(edits, None, cx);
11603 });
11604
11605 this.change_selections(Default::default(), window, cx, |s| {
11606 s.select(new_selections);
11607 });
11608
11609 this.request_autoscroll(Autoscroll::fit(), cx);
11610 });
11611 }
11612
11613 pub fn move_selection_on_drop(
11614 &mut self,
11615 selection: &Selection<Anchor>,
11616 target: DisplayPoint,
11617 is_cut: bool,
11618 window: &mut Window,
11619 cx: &mut Context<Self>,
11620 ) {
11621 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11622 let buffer = &display_map.buffer_snapshot;
11623 let mut edits = Vec::new();
11624 let insert_point = display_map
11625 .clip_point(target, Bias::Left)
11626 .to_point(&display_map);
11627 let text = buffer
11628 .text_for_range(selection.start..selection.end)
11629 .collect::<String>();
11630 if is_cut {
11631 edits.push(((selection.start..selection.end), String::new()));
11632 }
11633 let insert_anchor = buffer.anchor_before(insert_point);
11634 edits.push(((insert_anchor..insert_anchor), text));
11635 let last_edit_start = insert_anchor.bias_left(buffer);
11636 let last_edit_end = insert_anchor.bias_right(buffer);
11637 self.transact(window, cx, |this, window, cx| {
11638 this.buffer.update(cx, |buffer, cx| {
11639 buffer.edit(edits, None, cx);
11640 });
11641 this.change_selections(Default::default(), window, cx, |s| {
11642 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11643 });
11644 });
11645 }
11646
11647 pub fn clear_selection_drag_state(&mut self) {
11648 self.selection_drag_state = SelectionDragState::None;
11649 }
11650
11651 pub fn duplicate(
11652 &mut self,
11653 upwards: bool,
11654 whole_lines: bool,
11655 window: &mut Window,
11656 cx: &mut Context<Self>,
11657 ) {
11658 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11659
11660 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11661 let buffer = &display_map.buffer_snapshot;
11662 let selections = self.selections.all::<Point>(cx);
11663
11664 let mut edits = Vec::new();
11665 let mut selections_iter = selections.iter().peekable();
11666 while let Some(selection) = selections_iter.next() {
11667 let mut rows = selection.spanned_rows(false, &display_map);
11668 // duplicate line-wise
11669 if whole_lines || selection.start == selection.end {
11670 // Avoid duplicating the same lines twice.
11671 while let Some(next_selection) = selections_iter.peek() {
11672 let next_rows = next_selection.spanned_rows(false, &display_map);
11673 if next_rows.start < rows.end {
11674 rows.end = next_rows.end;
11675 selections_iter.next().unwrap();
11676 } else {
11677 break;
11678 }
11679 }
11680
11681 // Copy the text from the selected row region and splice it either at the start
11682 // or end of the region.
11683 let start = Point::new(rows.start.0, 0);
11684 let end = Point::new(
11685 rows.end.previous_row().0,
11686 buffer.line_len(rows.end.previous_row()),
11687 );
11688 let text = buffer
11689 .text_for_range(start..end)
11690 .chain(Some("\n"))
11691 .collect::<String>();
11692 let insert_location = if upwards {
11693 Point::new(rows.end.0, 0)
11694 } else {
11695 start
11696 };
11697 edits.push((insert_location..insert_location, text));
11698 } else {
11699 // duplicate character-wise
11700 let start = selection.start;
11701 let end = selection.end;
11702 let text = buffer.text_for_range(start..end).collect::<String>();
11703 edits.push((selection.end..selection.end, text));
11704 }
11705 }
11706
11707 self.transact(window, cx, |this, _, cx| {
11708 this.buffer.update(cx, |buffer, cx| {
11709 buffer.edit(edits, None, cx);
11710 });
11711
11712 this.request_autoscroll(Autoscroll::fit(), cx);
11713 });
11714 }
11715
11716 pub fn duplicate_line_up(
11717 &mut self,
11718 _: &DuplicateLineUp,
11719 window: &mut Window,
11720 cx: &mut Context<Self>,
11721 ) {
11722 self.duplicate(true, true, window, cx);
11723 }
11724
11725 pub fn duplicate_line_down(
11726 &mut self,
11727 _: &DuplicateLineDown,
11728 window: &mut Window,
11729 cx: &mut Context<Self>,
11730 ) {
11731 self.duplicate(false, true, window, cx);
11732 }
11733
11734 pub fn duplicate_selection(
11735 &mut self,
11736 _: &DuplicateSelection,
11737 window: &mut Window,
11738 cx: &mut Context<Self>,
11739 ) {
11740 self.duplicate(false, false, window, cx);
11741 }
11742
11743 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11744 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11745 if self.mode.is_single_line() {
11746 cx.propagate();
11747 return;
11748 }
11749
11750 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11751 let buffer = self.buffer.read(cx).snapshot(cx);
11752
11753 let mut edits = Vec::new();
11754 let mut unfold_ranges = Vec::new();
11755 let mut refold_creases = Vec::new();
11756
11757 let selections = self.selections.all::<Point>(cx);
11758 let mut selections = selections.iter().peekable();
11759 let mut contiguous_row_selections = Vec::new();
11760 let mut new_selections = Vec::new();
11761
11762 while let Some(selection) = selections.next() {
11763 // Find all the selections that span a contiguous row range
11764 let (start_row, end_row) = consume_contiguous_rows(
11765 &mut contiguous_row_selections,
11766 selection,
11767 &display_map,
11768 &mut selections,
11769 );
11770
11771 // Move the text spanned by the row range to be before the line preceding the row range
11772 if start_row.0 > 0 {
11773 let range_to_move = Point::new(
11774 start_row.previous_row().0,
11775 buffer.line_len(start_row.previous_row()),
11776 )
11777 ..Point::new(
11778 end_row.previous_row().0,
11779 buffer.line_len(end_row.previous_row()),
11780 );
11781 let insertion_point = display_map
11782 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11783 .0;
11784
11785 // Don't move lines across excerpts
11786 if buffer
11787 .excerpt_containing(insertion_point..range_to_move.end)
11788 .is_some()
11789 {
11790 let text = buffer
11791 .text_for_range(range_to_move.clone())
11792 .flat_map(|s| s.chars())
11793 .skip(1)
11794 .chain(['\n'])
11795 .collect::<String>();
11796
11797 edits.push((
11798 buffer.anchor_after(range_to_move.start)
11799 ..buffer.anchor_before(range_to_move.end),
11800 String::new(),
11801 ));
11802 let insertion_anchor = buffer.anchor_after(insertion_point);
11803 edits.push((insertion_anchor..insertion_anchor, text));
11804
11805 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11806
11807 // Move selections up
11808 new_selections.extend(contiguous_row_selections.drain(..).map(
11809 |mut selection| {
11810 selection.start.row -= row_delta;
11811 selection.end.row -= row_delta;
11812 selection
11813 },
11814 ));
11815
11816 // Move folds up
11817 unfold_ranges.push(range_to_move.clone());
11818 for fold in display_map.folds_in_range(
11819 buffer.anchor_before(range_to_move.start)
11820 ..buffer.anchor_after(range_to_move.end),
11821 ) {
11822 let mut start = fold.range.start.to_point(&buffer);
11823 let mut end = fold.range.end.to_point(&buffer);
11824 start.row -= row_delta;
11825 end.row -= row_delta;
11826 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11827 }
11828 }
11829 }
11830
11831 // If we didn't move line(s), preserve the existing selections
11832 new_selections.append(&mut contiguous_row_selections);
11833 }
11834
11835 self.transact(window, cx, |this, window, cx| {
11836 this.unfold_ranges(&unfold_ranges, true, true, cx);
11837 this.buffer.update(cx, |buffer, cx| {
11838 for (range, text) in edits {
11839 buffer.edit([(range, text)], None, cx);
11840 }
11841 });
11842 this.fold_creases(refold_creases, true, window, cx);
11843 this.change_selections(Default::default(), window, cx, |s| {
11844 s.select(new_selections);
11845 })
11846 });
11847 }
11848
11849 pub fn move_line_down(
11850 &mut self,
11851 _: &MoveLineDown,
11852 window: &mut Window,
11853 cx: &mut Context<Self>,
11854 ) {
11855 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11856 if self.mode.is_single_line() {
11857 cx.propagate();
11858 return;
11859 }
11860
11861 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11862 let buffer = self.buffer.read(cx).snapshot(cx);
11863
11864 let mut edits = Vec::new();
11865 let mut unfold_ranges = Vec::new();
11866 let mut refold_creases = Vec::new();
11867
11868 let selections = self.selections.all::<Point>(cx);
11869 let mut selections = selections.iter().peekable();
11870 let mut contiguous_row_selections = Vec::new();
11871 let mut new_selections = Vec::new();
11872
11873 while let Some(selection) = selections.next() {
11874 // Find all the selections that span a contiguous row range
11875 let (start_row, end_row) = consume_contiguous_rows(
11876 &mut contiguous_row_selections,
11877 selection,
11878 &display_map,
11879 &mut selections,
11880 );
11881
11882 // Move the text spanned by the row range to be after the last line of the row range
11883 if end_row.0 <= buffer.max_point().row {
11884 let range_to_move =
11885 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11886 let insertion_point = display_map
11887 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11888 .0;
11889
11890 // Don't move lines across excerpt boundaries
11891 if buffer
11892 .excerpt_containing(range_to_move.start..insertion_point)
11893 .is_some()
11894 {
11895 let mut text = String::from("\n");
11896 text.extend(buffer.text_for_range(range_to_move.clone()));
11897 text.pop(); // Drop trailing newline
11898 edits.push((
11899 buffer.anchor_after(range_to_move.start)
11900 ..buffer.anchor_before(range_to_move.end),
11901 String::new(),
11902 ));
11903 let insertion_anchor = buffer.anchor_after(insertion_point);
11904 edits.push((insertion_anchor..insertion_anchor, text));
11905
11906 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11907
11908 // Move selections down
11909 new_selections.extend(contiguous_row_selections.drain(..).map(
11910 |mut selection| {
11911 selection.start.row += row_delta;
11912 selection.end.row += row_delta;
11913 selection
11914 },
11915 ));
11916
11917 // Move folds down
11918 unfold_ranges.push(range_to_move.clone());
11919 for fold in display_map.folds_in_range(
11920 buffer.anchor_before(range_to_move.start)
11921 ..buffer.anchor_after(range_to_move.end),
11922 ) {
11923 let mut start = fold.range.start.to_point(&buffer);
11924 let mut end = fold.range.end.to_point(&buffer);
11925 start.row += row_delta;
11926 end.row += row_delta;
11927 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11928 }
11929 }
11930 }
11931
11932 // If we didn't move line(s), preserve the existing selections
11933 new_selections.append(&mut contiguous_row_selections);
11934 }
11935
11936 self.transact(window, cx, |this, window, cx| {
11937 this.unfold_ranges(&unfold_ranges, true, true, cx);
11938 this.buffer.update(cx, |buffer, cx| {
11939 for (range, text) in edits {
11940 buffer.edit([(range, text)], None, cx);
11941 }
11942 });
11943 this.fold_creases(refold_creases, true, window, cx);
11944 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11945 });
11946 }
11947
11948 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11949 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11950 let text_layout_details = &self.text_layout_details(window);
11951 self.transact(window, cx, |this, window, cx| {
11952 let edits = this.change_selections(Default::default(), window, cx, |s| {
11953 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11954 s.move_with(|display_map, selection| {
11955 if !selection.is_empty() {
11956 return;
11957 }
11958
11959 let mut head = selection.head();
11960 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11961 if head.column() == display_map.line_len(head.row()) {
11962 transpose_offset = display_map
11963 .buffer_snapshot
11964 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11965 }
11966
11967 if transpose_offset == 0 {
11968 return;
11969 }
11970
11971 *head.column_mut() += 1;
11972 head = display_map.clip_point(head, Bias::Right);
11973 let goal = SelectionGoal::HorizontalPosition(
11974 display_map
11975 .x_for_display_point(head, text_layout_details)
11976 .into(),
11977 );
11978 selection.collapse_to(head, goal);
11979
11980 let transpose_start = display_map
11981 .buffer_snapshot
11982 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11983 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11984 let transpose_end = display_map
11985 .buffer_snapshot
11986 .clip_offset(transpose_offset + 1, Bias::Right);
11987 if let Some(ch) =
11988 display_map.buffer_snapshot.chars_at(transpose_start).next()
11989 {
11990 edits.push((transpose_start..transpose_offset, String::new()));
11991 edits.push((transpose_end..transpose_end, ch.to_string()));
11992 }
11993 }
11994 });
11995 edits
11996 });
11997 this.buffer
11998 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11999 let selections = this.selections.all::<usize>(cx);
12000 this.change_selections(Default::default(), window, cx, |s| {
12001 s.select(selections);
12002 });
12003 });
12004 }
12005
12006 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12007 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12008 if self.mode.is_single_line() {
12009 cx.propagate();
12010 return;
12011 }
12012
12013 self.rewrap_impl(RewrapOptions::default(), cx)
12014 }
12015
12016 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12017 let buffer = self.buffer.read(cx).snapshot(cx);
12018 let selections = self.selections.all::<Point>(cx);
12019
12020 #[derive(Clone, Debug, PartialEq)]
12021 enum CommentFormat {
12022 /// single line comment, with prefix for line
12023 Line(String),
12024 /// single line within a block comment, with prefix for line
12025 BlockLine(String),
12026 /// a single line of a block comment that includes the initial delimiter
12027 BlockCommentWithStart(BlockCommentConfig),
12028 /// a single line of a block comment that includes the ending delimiter
12029 BlockCommentWithEnd(BlockCommentConfig),
12030 }
12031
12032 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12033 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12034 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12035 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12036 .peekable();
12037
12038 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12039 row
12040 } else {
12041 return Vec::new();
12042 };
12043
12044 let language_settings = buffer.language_settings_at(selection.head(), cx);
12045 let language_scope = buffer.language_scope_at(selection.head());
12046
12047 let indent_and_prefix_for_row =
12048 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12049 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12050 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12051 &language_scope
12052 {
12053 let indent_end = Point::new(row, indent.len);
12054 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12055 let line_text_after_indent = buffer
12056 .text_for_range(indent_end..line_end)
12057 .collect::<String>();
12058
12059 let is_within_comment_override = buffer
12060 .language_scope_at(indent_end)
12061 .is_some_and(|scope| scope.override_name() == Some("comment"));
12062 let comment_delimiters = if is_within_comment_override {
12063 // we are within a comment syntax node, but we don't
12064 // yet know what kind of comment: block, doc or line
12065 match (
12066 language_scope.documentation_comment(),
12067 language_scope.block_comment(),
12068 ) {
12069 (Some(config), _) | (_, Some(config))
12070 if buffer.contains_str_at(indent_end, &config.start) =>
12071 {
12072 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12073 }
12074 (Some(config), _) | (_, Some(config))
12075 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12076 {
12077 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12078 }
12079 (Some(config), _) | (_, Some(config))
12080 if buffer.contains_str_at(indent_end, &config.prefix) =>
12081 {
12082 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12083 }
12084 (_, _) => language_scope
12085 .line_comment_prefixes()
12086 .iter()
12087 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12088 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12089 }
12090 } else {
12091 // we not in an overridden comment node, but we may
12092 // be within a non-overridden line comment node
12093 language_scope
12094 .line_comment_prefixes()
12095 .iter()
12096 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12097 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12098 };
12099
12100 let rewrap_prefix = language_scope
12101 .rewrap_prefixes()
12102 .iter()
12103 .find_map(|prefix_regex| {
12104 prefix_regex.find(&line_text_after_indent).map(|mat| {
12105 if mat.start() == 0 {
12106 Some(mat.as_str().to_string())
12107 } else {
12108 None
12109 }
12110 })
12111 })
12112 .flatten();
12113 (comment_delimiters, rewrap_prefix)
12114 } else {
12115 (None, None)
12116 };
12117 (indent, comment_prefix, rewrap_prefix)
12118 };
12119
12120 let mut ranges = Vec::new();
12121 let from_empty_selection = selection.is_empty();
12122
12123 let mut current_range_start = first_row;
12124 let mut prev_row = first_row;
12125 let (
12126 mut current_range_indent,
12127 mut current_range_comment_delimiters,
12128 mut current_range_rewrap_prefix,
12129 ) = indent_and_prefix_for_row(first_row);
12130
12131 for row in non_blank_rows_iter.skip(1) {
12132 let has_paragraph_break = row > prev_row + 1;
12133
12134 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12135 indent_and_prefix_for_row(row);
12136
12137 let has_indent_change = row_indent != current_range_indent;
12138 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12139
12140 let has_boundary_change = has_comment_change
12141 || row_rewrap_prefix.is_some()
12142 || (has_indent_change && current_range_comment_delimiters.is_some());
12143
12144 if has_paragraph_break || has_boundary_change {
12145 ranges.push((
12146 language_settings.clone(),
12147 Point::new(current_range_start, 0)
12148 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12149 current_range_indent,
12150 current_range_comment_delimiters.clone(),
12151 current_range_rewrap_prefix.clone(),
12152 from_empty_selection,
12153 ));
12154 current_range_start = row;
12155 current_range_indent = row_indent;
12156 current_range_comment_delimiters = row_comment_delimiters;
12157 current_range_rewrap_prefix = row_rewrap_prefix;
12158 }
12159 prev_row = row;
12160 }
12161
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,
12168 current_range_rewrap_prefix,
12169 from_empty_selection,
12170 ));
12171
12172 ranges
12173 });
12174
12175 let mut edits = Vec::new();
12176 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12177
12178 for (
12179 language_settings,
12180 wrap_range,
12181 mut indent_size,
12182 comment_prefix,
12183 rewrap_prefix,
12184 from_empty_selection,
12185 ) in wrap_ranges
12186 {
12187 let mut start_row = wrap_range.start.row;
12188 let mut end_row = wrap_range.end.row;
12189
12190 // Skip selections that overlap with a range that has already been rewrapped.
12191 let selection_range = start_row..end_row;
12192 if rewrapped_row_ranges
12193 .iter()
12194 .any(|range| range.overlaps(&selection_range))
12195 {
12196 continue;
12197 }
12198
12199 let tab_size = language_settings.tab_size;
12200
12201 let (line_prefix, inside_comment) = match &comment_prefix {
12202 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12203 (Some(prefix.as_str()), true)
12204 }
12205 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12206 (Some(prefix.as_ref()), true)
12207 }
12208 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12209 start: _,
12210 end: _,
12211 prefix,
12212 tab_size,
12213 })) => {
12214 indent_size.len += tab_size;
12215 (Some(prefix.as_ref()), true)
12216 }
12217 None => (None, false),
12218 };
12219 let indent_prefix = indent_size.chars().collect::<String>();
12220 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12221
12222 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12223 RewrapBehavior::InComments => inside_comment,
12224 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12225 RewrapBehavior::Anywhere => true,
12226 };
12227
12228 let should_rewrap = options.override_language_settings
12229 || allow_rewrap_based_on_language
12230 || self.hard_wrap.is_some();
12231 if !should_rewrap {
12232 continue;
12233 }
12234
12235 if from_empty_selection {
12236 'expand_upwards: while start_row > 0 {
12237 let prev_row = start_row - 1;
12238 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12239 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12240 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12241 {
12242 start_row = prev_row;
12243 } else {
12244 break 'expand_upwards;
12245 }
12246 }
12247
12248 'expand_downwards: while end_row < buffer.max_point().row {
12249 let next_row = end_row + 1;
12250 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12251 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12252 && !buffer.is_line_blank(MultiBufferRow(next_row))
12253 {
12254 end_row = next_row;
12255 } else {
12256 break 'expand_downwards;
12257 }
12258 }
12259 }
12260
12261 let start = Point::new(start_row, 0);
12262 let start_offset = start.to_offset(&buffer);
12263 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12264 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12265 let mut first_line_delimiter = None;
12266 let mut last_line_delimiter = None;
12267 let Some(lines_without_prefixes) = selection_text
12268 .lines()
12269 .enumerate()
12270 .map(|(ix, line)| {
12271 let line_trimmed = line.trim_start();
12272 if rewrap_prefix.is_some() && ix > 0 {
12273 Ok(line_trimmed)
12274 } else if let Some(
12275 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12276 start,
12277 prefix,
12278 end,
12279 tab_size,
12280 })
12281 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12282 start,
12283 prefix,
12284 end,
12285 tab_size,
12286 }),
12287 ) = &comment_prefix
12288 {
12289 let line_trimmed = line_trimmed
12290 .strip_prefix(start.as_ref())
12291 .map(|s| {
12292 let mut indent_size = indent_size;
12293 indent_size.len -= tab_size;
12294 let indent_prefix: String = indent_size.chars().collect();
12295 first_line_delimiter = Some((indent_prefix, start));
12296 s.trim_start()
12297 })
12298 .unwrap_or(line_trimmed);
12299 let line_trimmed = line_trimmed
12300 .strip_suffix(end.as_ref())
12301 .map(|s| {
12302 last_line_delimiter = Some(end);
12303 s.trim_end()
12304 })
12305 .unwrap_or(line_trimmed);
12306 let line_trimmed = line_trimmed
12307 .strip_prefix(prefix.as_ref())
12308 .unwrap_or(line_trimmed);
12309 Ok(line_trimmed)
12310 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12311 line_trimmed.strip_prefix(prefix).with_context(|| {
12312 format!("line did not start with prefix {prefix:?}: {line:?}")
12313 })
12314 } else {
12315 line_trimmed
12316 .strip_prefix(&line_prefix.trim_start())
12317 .with_context(|| {
12318 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12319 })
12320 }
12321 })
12322 .collect::<Result<Vec<_>, _>>()
12323 .log_err()
12324 else {
12325 continue;
12326 };
12327
12328 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12329 buffer
12330 .language_settings_at(Point::new(start_row, 0), cx)
12331 .preferred_line_length as usize
12332 });
12333
12334 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12335 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12336 } else {
12337 line_prefix.clone()
12338 };
12339
12340 let wrapped_text = {
12341 let mut wrapped_text = wrap_with_prefix(
12342 line_prefix,
12343 subsequent_lines_prefix,
12344 lines_without_prefixes.join("\n"),
12345 wrap_column,
12346 tab_size,
12347 options.preserve_existing_whitespace,
12348 );
12349
12350 if let Some((indent, delimiter)) = first_line_delimiter {
12351 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12352 }
12353 if let Some(last_line) = last_line_delimiter {
12354 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12355 }
12356
12357 wrapped_text
12358 };
12359
12360 // TODO: should always use char-based diff while still supporting cursor behavior that
12361 // matches vim.
12362 let mut diff_options = DiffOptions::default();
12363 if options.override_language_settings {
12364 diff_options.max_word_diff_len = 0;
12365 diff_options.max_word_diff_line_count = 0;
12366 } else {
12367 diff_options.max_word_diff_len = usize::MAX;
12368 diff_options.max_word_diff_line_count = usize::MAX;
12369 }
12370
12371 for (old_range, new_text) in
12372 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12373 {
12374 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12375 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12376 edits.push((edit_start..edit_end, new_text));
12377 }
12378
12379 rewrapped_row_ranges.push(start_row..=end_row);
12380 }
12381
12382 self.buffer
12383 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12384 }
12385
12386 pub fn cut_common(
12387 &mut self,
12388 cut_no_selection_line: bool,
12389 window: &mut Window,
12390 cx: &mut Context<Self>,
12391 ) -> ClipboardItem {
12392 let mut text = String::new();
12393 let buffer = self.buffer.read(cx).snapshot(cx);
12394 let mut selections = self.selections.all::<Point>(cx);
12395 let mut clipboard_selections = Vec::with_capacity(selections.len());
12396 {
12397 let max_point = buffer.max_point();
12398 let mut is_first = true;
12399 for selection in &mut selections {
12400 let is_entire_line =
12401 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12402 if is_entire_line {
12403 selection.start = Point::new(selection.start.row, 0);
12404 if !selection.is_empty() && selection.end.column == 0 {
12405 selection.end = cmp::min(max_point, selection.end);
12406 } else {
12407 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12408 }
12409 selection.goal = SelectionGoal::None;
12410 }
12411 if is_first {
12412 is_first = false;
12413 } else {
12414 text += "\n";
12415 }
12416 let mut len = 0;
12417 for chunk in buffer.text_for_range(selection.start..selection.end) {
12418 text.push_str(chunk);
12419 len += chunk.len();
12420 }
12421 clipboard_selections.push(ClipboardSelection {
12422 len,
12423 is_entire_line,
12424 first_line_indent: buffer
12425 .indent_size_for_line(MultiBufferRow(selection.start.row))
12426 .len,
12427 });
12428 }
12429 }
12430
12431 self.transact(window, cx, |this, window, cx| {
12432 this.change_selections(Default::default(), window, cx, |s| {
12433 s.select(selections);
12434 });
12435 this.insert("", window, cx);
12436 });
12437 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12438 }
12439
12440 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12441 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12442 let item = self.cut_common(true, window, cx);
12443 cx.write_to_clipboard(item);
12444 }
12445
12446 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12447 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12448 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12449 s.move_with(|snapshot, sel| {
12450 if sel.is_empty() {
12451 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12452 }
12453 if sel.is_empty() {
12454 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12455 }
12456 });
12457 });
12458 let item = self.cut_common(false, window, cx);
12459 cx.set_global(KillRing(item))
12460 }
12461
12462 pub fn kill_ring_yank(
12463 &mut self,
12464 _: &KillRingYank,
12465 window: &mut Window,
12466 cx: &mut Context<Self>,
12467 ) {
12468 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12469 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12470 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12471 (kill_ring.text().to_string(), kill_ring.metadata_json())
12472 } else {
12473 return;
12474 }
12475 } else {
12476 return;
12477 };
12478 self.do_paste(&text, metadata, false, window, cx);
12479 }
12480
12481 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12482 self.do_copy(true, cx);
12483 }
12484
12485 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12486 self.do_copy(false, cx);
12487 }
12488
12489 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12490 let selections = self.selections.all::<Point>(cx);
12491 let buffer = self.buffer.read(cx).read(cx);
12492 let mut text = String::new();
12493
12494 let mut clipboard_selections = Vec::with_capacity(selections.len());
12495 {
12496 let max_point = buffer.max_point();
12497 let mut is_first = true;
12498 for selection in &selections {
12499 let mut start = selection.start;
12500 let mut end = selection.end;
12501 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12502 if is_entire_line {
12503 start = Point::new(start.row, 0);
12504 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12505 }
12506
12507 let mut trimmed_selections = Vec::new();
12508 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12509 let row = MultiBufferRow(start.row);
12510 let first_indent = buffer.indent_size_for_line(row);
12511 if first_indent.len == 0 || start.column > first_indent.len {
12512 trimmed_selections.push(start..end);
12513 } else {
12514 trimmed_selections.push(
12515 Point::new(row.0, first_indent.len)
12516 ..Point::new(row.0, buffer.line_len(row)),
12517 );
12518 for row in start.row + 1..=end.row {
12519 let mut line_len = buffer.line_len(MultiBufferRow(row));
12520 if row == end.row {
12521 line_len = end.column;
12522 }
12523 if line_len == 0 {
12524 trimmed_selections
12525 .push(Point::new(row, 0)..Point::new(row, line_len));
12526 continue;
12527 }
12528 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12529 if row_indent_size.len >= first_indent.len {
12530 trimmed_selections.push(
12531 Point::new(row, first_indent.len)..Point::new(row, line_len),
12532 );
12533 } else {
12534 trimmed_selections.clear();
12535 trimmed_selections.push(start..end);
12536 break;
12537 }
12538 }
12539 }
12540 } else {
12541 trimmed_selections.push(start..end);
12542 }
12543
12544 for trimmed_range in trimmed_selections {
12545 if is_first {
12546 is_first = false;
12547 } else {
12548 text += "\n";
12549 }
12550 let mut len = 0;
12551 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12552 text.push_str(chunk);
12553 len += chunk.len();
12554 }
12555 clipboard_selections.push(ClipboardSelection {
12556 len,
12557 is_entire_line,
12558 first_line_indent: buffer
12559 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12560 .len,
12561 });
12562 }
12563 }
12564 }
12565
12566 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12567 text,
12568 clipboard_selections,
12569 ));
12570 }
12571
12572 pub fn do_paste(
12573 &mut self,
12574 text: &String,
12575 clipboard_selections: Option<Vec<ClipboardSelection>>,
12576 handle_entire_lines: bool,
12577 window: &mut Window,
12578 cx: &mut Context<Self>,
12579 ) {
12580 if self.read_only(cx) {
12581 return;
12582 }
12583
12584 let clipboard_text = Cow::Borrowed(text.as_str());
12585
12586 self.transact(window, cx, |this, window, cx| {
12587 let had_active_edit_prediction = this.has_active_edit_prediction();
12588 let old_selections = this.selections.all::<usize>(cx);
12589 let cursor_offset = this.selections.last::<usize>(cx).head();
12590
12591 if let Some(mut clipboard_selections) = clipboard_selections {
12592 let all_selections_were_entire_line =
12593 clipboard_selections.iter().all(|s| s.is_entire_line);
12594 let first_selection_indent_column =
12595 clipboard_selections.first().map(|s| s.first_line_indent);
12596 if clipboard_selections.len() != old_selections.len() {
12597 clipboard_selections.drain(..);
12598 }
12599 let mut auto_indent_on_paste = true;
12600
12601 this.buffer.update(cx, |buffer, cx| {
12602 let snapshot = buffer.read(cx);
12603 auto_indent_on_paste = snapshot
12604 .language_settings_at(cursor_offset, cx)
12605 .auto_indent_on_paste;
12606
12607 let mut start_offset = 0;
12608 let mut edits = Vec::new();
12609 let mut original_indent_columns = Vec::new();
12610 for (ix, selection) in old_selections.iter().enumerate() {
12611 let to_insert;
12612 let entire_line;
12613 let original_indent_column;
12614 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12615 let end_offset = start_offset + clipboard_selection.len;
12616 to_insert = &clipboard_text[start_offset..end_offset];
12617 entire_line = clipboard_selection.is_entire_line;
12618 start_offset = end_offset + 1;
12619 original_indent_column = Some(clipboard_selection.first_line_indent);
12620 } else {
12621 to_insert = &*clipboard_text;
12622 entire_line = all_selections_were_entire_line;
12623 original_indent_column = first_selection_indent_column
12624 }
12625
12626 let (range, to_insert) =
12627 if selection.is_empty() && handle_entire_lines && entire_line {
12628 // If the corresponding selection was empty when this slice of the
12629 // clipboard text was written, then the entire line containing the
12630 // selection was copied. If this selection is also currently empty,
12631 // then paste the line before the current line of the buffer.
12632 let column = selection.start.to_point(&snapshot).column as usize;
12633 let line_start = selection.start - column;
12634 (line_start..line_start, Cow::Borrowed(to_insert))
12635 } else {
12636 let language = snapshot.language_at(selection.head());
12637 let range = selection.range();
12638 if let Some(language) = language
12639 && language.name() == "Markdown".into()
12640 {
12641 edit_for_markdown_paste(
12642 &snapshot,
12643 range,
12644 to_insert,
12645 url::Url::parse(to_insert).ok(),
12646 )
12647 } else {
12648 (range, Cow::Borrowed(to_insert))
12649 }
12650 };
12651
12652 edits.push((range, to_insert));
12653 original_indent_columns.push(original_indent_column);
12654 }
12655 drop(snapshot);
12656
12657 buffer.edit(
12658 edits,
12659 if auto_indent_on_paste {
12660 Some(AutoindentMode::Block {
12661 original_indent_columns,
12662 })
12663 } else {
12664 None
12665 },
12666 cx,
12667 );
12668 });
12669
12670 let selections = this.selections.all::<usize>(cx);
12671 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12672 } else {
12673 let url = url::Url::parse(&clipboard_text).ok();
12674
12675 let auto_indent_mode = if !clipboard_text.is_empty() {
12676 Some(AutoindentMode::Block {
12677 original_indent_columns: Vec::new(),
12678 })
12679 } else {
12680 None
12681 };
12682
12683 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12684 let snapshot = buffer.snapshot(cx);
12685
12686 let anchors = old_selections
12687 .iter()
12688 .map(|s| {
12689 let anchor = snapshot.anchor_after(s.head());
12690 s.map(|_| anchor)
12691 })
12692 .collect::<Vec<_>>();
12693
12694 let mut edits = Vec::new();
12695
12696 for selection in old_selections.iter() {
12697 let language = snapshot.language_at(selection.head());
12698 let range = selection.range();
12699
12700 let (edit_range, edit_text) = if let Some(language) = language
12701 && language.name() == "Markdown".into()
12702 {
12703 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12704 } else {
12705 (range, clipboard_text.clone())
12706 };
12707
12708 edits.push((edit_range, edit_text));
12709 }
12710
12711 drop(snapshot);
12712 buffer.edit(edits, auto_indent_mode, cx);
12713
12714 anchors
12715 });
12716
12717 this.change_selections(Default::default(), window, cx, |s| {
12718 s.select_anchors(selection_anchors);
12719 });
12720 }
12721
12722 let trigger_in_words =
12723 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12724
12725 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12726 });
12727 }
12728
12729 pub fn diff_clipboard_with_selection(
12730 &mut self,
12731 _: &DiffClipboardWithSelection,
12732 window: &mut Window,
12733 cx: &mut Context<Self>,
12734 ) {
12735 let selections = self.selections.all::<usize>(cx);
12736
12737 if selections.is_empty() {
12738 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12739 return;
12740 };
12741
12742 let clipboard_text = match cx.read_from_clipboard() {
12743 Some(item) => match item.entries().first() {
12744 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12745 _ => None,
12746 },
12747 None => None,
12748 };
12749
12750 let Some(clipboard_text) = clipboard_text else {
12751 log::warn!("Clipboard doesn't contain text.");
12752 return;
12753 };
12754
12755 window.dispatch_action(
12756 Box::new(DiffClipboardWithSelectionData {
12757 clipboard_text,
12758 editor: cx.entity(),
12759 }),
12760 cx,
12761 );
12762 }
12763
12764 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12765 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12766 if let Some(item) = cx.read_from_clipboard() {
12767 let entries = item.entries();
12768
12769 match entries.first() {
12770 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12771 // of all the pasted entries.
12772 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12773 .do_paste(
12774 clipboard_string.text(),
12775 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12776 true,
12777 window,
12778 cx,
12779 ),
12780 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12781 }
12782 }
12783 }
12784
12785 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12786 if self.read_only(cx) {
12787 return;
12788 }
12789
12790 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12791
12792 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12793 if let Some((selections, _)) =
12794 self.selection_history.transaction(transaction_id).cloned()
12795 {
12796 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12797 s.select_anchors(selections.to_vec());
12798 });
12799 } else {
12800 log::error!(
12801 "No entry in selection_history found for undo. \
12802 This may correspond to a bug where undo does not update the selection. \
12803 If this is occurring, please add details to \
12804 https://github.com/zed-industries/zed/issues/22692"
12805 );
12806 }
12807 self.request_autoscroll(Autoscroll::fit(), cx);
12808 self.unmark_text(window, cx);
12809 self.refresh_edit_prediction(true, false, window, cx);
12810 cx.emit(EditorEvent::Edited { transaction_id });
12811 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12812 }
12813 }
12814
12815 pub fn redo(&mut self, _: &Redo, 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.redo(cx)) {
12823 if let Some((_, 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 redo. \
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 }
12842 }
12843
12844 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12845 self.buffer
12846 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12847 }
12848
12849 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12850 self.buffer
12851 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12852 }
12853
12854 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12855 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12856 self.change_selections(Default::default(), window, cx, |s| {
12857 s.move_with(|map, selection| {
12858 let cursor = if selection.is_empty() {
12859 movement::left(map, selection.start)
12860 } else {
12861 selection.start
12862 };
12863 selection.collapse_to(cursor, SelectionGoal::None);
12864 });
12865 })
12866 }
12867
12868 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12869 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12870 self.change_selections(Default::default(), window, cx, |s| {
12871 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12872 })
12873 }
12874
12875 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12876 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12877 self.change_selections(Default::default(), window, cx, |s| {
12878 s.move_with(|map, selection| {
12879 let cursor = if selection.is_empty() {
12880 movement::right(map, selection.end)
12881 } else {
12882 selection.end
12883 };
12884 selection.collapse_to(cursor, SelectionGoal::None)
12885 });
12886 })
12887 }
12888
12889 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12890 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12891 self.change_selections(Default::default(), window, cx, |s| {
12892 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12893 });
12894 }
12895
12896 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12897 if self.take_rename(true, window, cx).is_some() {
12898 return;
12899 }
12900
12901 if self.mode.is_single_line() {
12902 cx.propagate();
12903 return;
12904 }
12905
12906 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12907
12908 let text_layout_details = &self.text_layout_details(window);
12909 let selection_count = self.selections.count();
12910 let first_selection = self.selections.first_anchor();
12911
12912 self.change_selections(Default::default(), window, cx, |s| {
12913 s.move_with(|map, selection| {
12914 if !selection.is_empty() {
12915 selection.goal = SelectionGoal::None;
12916 }
12917 let (cursor, goal) = movement::up(
12918 map,
12919 selection.start,
12920 selection.goal,
12921 false,
12922 text_layout_details,
12923 );
12924 selection.collapse_to(cursor, goal);
12925 });
12926 });
12927
12928 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12929 {
12930 cx.propagate();
12931 }
12932 }
12933
12934 pub fn move_up_by_lines(
12935 &mut self,
12936 action: &MoveUpByLines,
12937 window: &mut Window,
12938 cx: &mut Context<Self>,
12939 ) {
12940 if self.take_rename(true, window, cx).is_some() {
12941 return;
12942 }
12943
12944 if self.mode.is_single_line() {
12945 cx.propagate();
12946 return;
12947 }
12948
12949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12950
12951 let text_layout_details = &self.text_layout_details(window);
12952
12953 self.change_selections(Default::default(), window, cx, |s| {
12954 s.move_with(|map, selection| {
12955 if !selection.is_empty() {
12956 selection.goal = SelectionGoal::None;
12957 }
12958 let (cursor, goal) = movement::up_by_rows(
12959 map,
12960 selection.start,
12961 action.lines,
12962 selection.goal,
12963 false,
12964 text_layout_details,
12965 );
12966 selection.collapse_to(cursor, goal);
12967 });
12968 })
12969 }
12970
12971 pub fn move_down_by_lines(
12972 &mut self,
12973 action: &MoveDownByLines,
12974 window: &mut Window,
12975 cx: &mut Context<Self>,
12976 ) {
12977 if self.take_rename(true, window, cx).is_some() {
12978 return;
12979 }
12980
12981 if self.mode.is_single_line() {
12982 cx.propagate();
12983 return;
12984 }
12985
12986 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12987
12988 let text_layout_details = &self.text_layout_details(window);
12989
12990 self.change_selections(Default::default(), window, cx, |s| {
12991 s.move_with(|map, selection| {
12992 if !selection.is_empty() {
12993 selection.goal = SelectionGoal::None;
12994 }
12995 let (cursor, goal) = movement::down_by_rows(
12996 map,
12997 selection.start,
12998 action.lines,
12999 selection.goal,
13000 false,
13001 text_layout_details,
13002 );
13003 selection.collapse_to(cursor, goal);
13004 });
13005 })
13006 }
13007
13008 pub fn select_down_by_lines(
13009 &mut self,
13010 action: &SelectDownByLines,
13011 window: &mut Window,
13012 cx: &mut Context<Self>,
13013 ) {
13014 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13015 let text_layout_details = &self.text_layout_details(window);
13016 self.change_selections(Default::default(), window, cx, |s| {
13017 s.move_heads_with(|map, head, goal| {
13018 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13019 })
13020 })
13021 }
13022
13023 pub fn select_up_by_lines(
13024 &mut self,
13025 action: &SelectUpByLines,
13026 window: &mut Window,
13027 cx: &mut Context<Self>,
13028 ) {
13029 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13030 let text_layout_details = &self.text_layout_details(window);
13031 self.change_selections(Default::default(), window, cx, |s| {
13032 s.move_heads_with(|map, head, goal| {
13033 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13034 })
13035 })
13036 }
13037
13038 pub fn select_page_up(
13039 &mut self,
13040 _: &SelectPageUp,
13041 window: &mut Window,
13042 cx: &mut Context<Self>,
13043 ) {
13044 let Some(row_count) = self.visible_row_count() else {
13045 return;
13046 };
13047
13048 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13049
13050 let text_layout_details = &self.text_layout_details(window);
13051
13052 self.change_selections(Default::default(), window, cx, |s| {
13053 s.move_heads_with(|map, head, goal| {
13054 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13055 })
13056 })
13057 }
13058
13059 pub fn move_page_up(
13060 &mut self,
13061 action: &MovePageUp,
13062 window: &mut Window,
13063 cx: &mut Context<Self>,
13064 ) {
13065 if self.take_rename(true, window, cx).is_some() {
13066 return;
13067 }
13068
13069 if self
13070 .context_menu
13071 .borrow_mut()
13072 .as_mut()
13073 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13074 .unwrap_or(false)
13075 {
13076 return;
13077 }
13078
13079 if matches!(self.mode, EditorMode::SingleLine) {
13080 cx.propagate();
13081 return;
13082 }
13083
13084 let Some(row_count) = self.visible_row_count() else {
13085 return;
13086 };
13087
13088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13089
13090 let effects = if action.center_cursor {
13091 SelectionEffects::scroll(Autoscroll::center())
13092 } else {
13093 SelectionEffects::default()
13094 };
13095
13096 let text_layout_details = &self.text_layout_details(window);
13097
13098 self.change_selections(effects, window, cx, |s| {
13099 s.move_with(|map, selection| {
13100 if !selection.is_empty() {
13101 selection.goal = SelectionGoal::None;
13102 }
13103 let (cursor, goal) = movement::up_by_rows(
13104 map,
13105 selection.end,
13106 row_count,
13107 selection.goal,
13108 false,
13109 text_layout_details,
13110 );
13111 selection.collapse_to(cursor, goal);
13112 });
13113 });
13114 }
13115
13116 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13117 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13118 let text_layout_details = &self.text_layout_details(window);
13119 self.change_selections(Default::default(), window, cx, |s| {
13120 s.move_heads_with(|map, head, goal| {
13121 movement::up(map, head, goal, false, text_layout_details)
13122 })
13123 })
13124 }
13125
13126 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13127 self.take_rename(true, window, cx);
13128
13129 if self.mode.is_single_line() {
13130 cx.propagate();
13131 return;
13132 }
13133
13134 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13135
13136 let text_layout_details = &self.text_layout_details(window);
13137 let selection_count = self.selections.count();
13138 let first_selection = self.selections.first_anchor();
13139
13140 self.change_selections(Default::default(), window, cx, |s| {
13141 s.move_with(|map, selection| {
13142 if !selection.is_empty() {
13143 selection.goal = SelectionGoal::None;
13144 }
13145 let (cursor, goal) = movement::down(
13146 map,
13147 selection.end,
13148 selection.goal,
13149 false,
13150 text_layout_details,
13151 );
13152 selection.collapse_to(cursor, goal);
13153 });
13154 });
13155
13156 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13157 {
13158 cx.propagate();
13159 }
13160 }
13161
13162 pub fn select_page_down(
13163 &mut self,
13164 _: &SelectPageDown,
13165 window: &mut Window,
13166 cx: &mut Context<Self>,
13167 ) {
13168 let Some(row_count) = self.visible_row_count() else {
13169 return;
13170 };
13171
13172 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13173
13174 let text_layout_details = &self.text_layout_details(window);
13175
13176 self.change_selections(Default::default(), window, cx, |s| {
13177 s.move_heads_with(|map, head, goal| {
13178 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13179 })
13180 })
13181 }
13182
13183 pub fn move_page_down(
13184 &mut self,
13185 action: &MovePageDown,
13186 window: &mut Window,
13187 cx: &mut Context<Self>,
13188 ) {
13189 if self.take_rename(true, window, cx).is_some() {
13190 return;
13191 }
13192
13193 if self
13194 .context_menu
13195 .borrow_mut()
13196 .as_mut()
13197 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13198 .unwrap_or(false)
13199 {
13200 return;
13201 }
13202
13203 if matches!(self.mode, EditorMode::SingleLine) {
13204 cx.propagate();
13205 return;
13206 }
13207
13208 let Some(row_count) = self.visible_row_count() else {
13209 return;
13210 };
13211
13212 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13213
13214 let effects = if action.center_cursor {
13215 SelectionEffects::scroll(Autoscroll::center())
13216 } else {
13217 SelectionEffects::default()
13218 };
13219
13220 let text_layout_details = &self.text_layout_details(window);
13221 self.change_selections(effects, window, cx, |s| {
13222 s.move_with(|map, selection| {
13223 if !selection.is_empty() {
13224 selection.goal = SelectionGoal::None;
13225 }
13226 let (cursor, goal) = movement::down_by_rows(
13227 map,
13228 selection.end,
13229 row_count,
13230 selection.goal,
13231 false,
13232 text_layout_details,
13233 );
13234 selection.collapse_to(cursor, goal);
13235 });
13236 });
13237 }
13238
13239 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13240 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13241 let text_layout_details = &self.text_layout_details(window);
13242 self.change_selections(Default::default(), window, cx, |s| {
13243 s.move_heads_with(|map, head, goal| {
13244 movement::down(map, head, goal, false, text_layout_details)
13245 })
13246 });
13247 }
13248
13249 pub fn context_menu_first(
13250 &mut self,
13251 _: &ContextMenuFirst,
13252 window: &mut Window,
13253 cx: &mut Context<Self>,
13254 ) {
13255 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13256 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13257 }
13258 }
13259
13260 pub fn context_menu_prev(
13261 &mut self,
13262 _: &ContextMenuPrevious,
13263 window: &mut Window,
13264 cx: &mut Context<Self>,
13265 ) {
13266 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13267 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13268 }
13269 }
13270
13271 pub fn context_menu_next(
13272 &mut self,
13273 _: &ContextMenuNext,
13274 window: &mut Window,
13275 cx: &mut Context<Self>,
13276 ) {
13277 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13278 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13279 }
13280 }
13281
13282 pub fn context_menu_last(
13283 &mut self,
13284 _: &ContextMenuLast,
13285 window: &mut Window,
13286 cx: &mut Context<Self>,
13287 ) {
13288 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13289 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13290 }
13291 }
13292
13293 pub fn signature_help_prev(
13294 &mut self,
13295 _: &SignatureHelpPrevious,
13296 _: &mut Window,
13297 cx: &mut Context<Self>,
13298 ) {
13299 if let Some(popover) = self.signature_help_state.popover_mut() {
13300 if popover.current_signature == 0 {
13301 popover.current_signature = popover.signatures.len() - 1;
13302 } else {
13303 popover.current_signature -= 1;
13304 }
13305 cx.notify();
13306 }
13307 }
13308
13309 pub fn signature_help_next(
13310 &mut self,
13311 _: &SignatureHelpNext,
13312 _: &mut Window,
13313 cx: &mut Context<Self>,
13314 ) {
13315 if let Some(popover) = self.signature_help_state.popover_mut() {
13316 if popover.current_signature + 1 == popover.signatures.len() {
13317 popover.current_signature = 0;
13318 } else {
13319 popover.current_signature += 1;
13320 }
13321 cx.notify();
13322 }
13323 }
13324
13325 pub fn move_to_previous_word_start(
13326 &mut self,
13327 _: &MoveToPreviousWordStart,
13328 window: &mut Window,
13329 cx: &mut Context<Self>,
13330 ) {
13331 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13332 self.change_selections(Default::default(), window, cx, |s| {
13333 s.move_cursors_with(|map, head, _| {
13334 (
13335 movement::previous_word_start(map, head),
13336 SelectionGoal::None,
13337 )
13338 });
13339 })
13340 }
13341
13342 pub fn move_to_previous_subword_start(
13343 &mut self,
13344 _: &MoveToPreviousSubwordStart,
13345 window: &mut Window,
13346 cx: &mut Context<Self>,
13347 ) {
13348 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13349 self.change_selections(Default::default(), window, cx, |s| {
13350 s.move_cursors_with(|map, head, _| {
13351 (
13352 movement::previous_subword_start(map, head),
13353 SelectionGoal::None,
13354 )
13355 });
13356 })
13357 }
13358
13359 pub fn select_to_previous_word_start(
13360 &mut self,
13361 _: &SelectToPreviousWordStart,
13362 window: &mut Window,
13363 cx: &mut Context<Self>,
13364 ) {
13365 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13366 self.change_selections(Default::default(), window, cx, |s| {
13367 s.move_heads_with(|map, head, _| {
13368 (
13369 movement::previous_word_start(map, head),
13370 SelectionGoal::None,
13371 )
13372 });
13373 })
13374 }
13375
13376 pub fn select_to_previous_subword_start(
13377 &mut self,
13378 _: &SelectToPreviousSubwordStart,
13379 window: &mut Window,
13380 cx: &mut Context<Self>,
13381 ) {
13382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13383 self.change_selections(Default::default(), window, cx, |s| {
13384 s.move_heads_with(|map, head, _| {
13385 (
13386 movement::previous_subword_start(map, head),
13387 SelectionGoal::None,
13388 )
13389 });
13390 })
13391 }
13392
13393 pub fn delete_to_previous_word_start(
13394 &mut self,
13395 action: &DeleteToPreviousWordStart,
13396 window: &mut Window,
13397 cx: &mut Context<Self>,
13398 ) {
13399 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13400 self.transact(window, cx, |this, window, cx| {
13401 this.select_autoclose_pair(window, cx);
13402 this.change_selections(Default::default(), window, cx, |s| {
13403 s.move_with(|map, selection| {
13404 if selection.is_empty() {
13405 let mut cursor = if action.ignore_newlines {
13406 movement::previous_word_start(map, selection.head())
13407 } else {
13408 movement::previous_word_start_or_newline(map, selection.head())
13409 };
13410 cursor = movement::adjust_greedy_deletion(
13411 map,
13412 selection.head(),
13413 cursor,
13414 action.ignore_brackets,
13415 );
13416 selection.set_head(cursor, SelectionGoal::None);
13417 }
13418 });
13419 });
13420 this.insert("", window, cx);
13421 });
13422 }
13423
13424 pub fn delete_to_previous_subword_start(
13425 &mut self,
13426 _: &DeleteToPreviousSubwordStart,
13427 window: &mut Window,
13428 cx: &mut Context<Self>,
13429 ) {
13430 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13431 self.transact(window, cx, |this, window, cx| {
13432 this.select_autoclose_pair(window, cx);
13433 this.change_selections(Default::default(), window, cx, |s| {
13434 s.move_with(|map, selection| {
13435 if selection.is_empty() {
13436 let mut cursor = movement::previous_subword_start(map, selection.head());
13437 cursor =
13438 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13439 selection.set_head(cursor, SelectionGoal::None);
13440 }
13441 });
13442 });
13443 this.insert("", window, cx);
13444 });
13445 }
13446
13447 pub fn move_to_next_word_end(
13448 &mut self,
13449 _: &MoveToNextWordEnd,
13450 window: &mut Window,
13451 cx: &mut Context<Self>,
13452 ) {
13453 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13454 self.change_selections(Default::default(), window, cx, |s| {
13455 s.move_cursors_with(|map, head, _| {
13456 (movement::next_word_end(map, head), SelectionGoal::None)
13457 });
13458 })
13459 }
13460
13461 pub fn move_to_next_subword_end(
13462 &mut self,
13463 _: &MoveToNextSubwordEnd,
13464 window: &mut Window,
13465 cx: &mut Context<Self>,
13466 ) {
13467 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13468 self.change_selections(Default::default(), window, cx, |s| {
13469 s.move_cursors_with(|map, head, _| {
13470 (movement::next_subword_end(map, head), SelectionGoal::None)
13471 });
13472 })
13473 }
13474
13475 pub fn select_to_next_word_end(
13476 &mut self,
13477 _: &SelectToNextWordEnd,
13478 window: &mut Window,
13479 cx: &mut Context<Self>,
13480 ) {
13481 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13482 self.change_selections(Default::default(), window, cx, |s| {
13483 s.move_heads_with(|map, head, _| {
13484 (movement::next_word_end(map, head), SelectionGoal::None)
13485 });
13486 })
13487 }
13488
13489 pub fn select_to_next_subword_end(
13490 &mut self,
13491 _: &SelectToNextSubwordEnd,
13492 window: &mut Window,
13493 cx: &mut Context<Self>,
13494 ) {
13495 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13496 self.change_selections(Default::default(), window, cx, |s| {
13497 s.move_heads_with(|map, head, _| {
13498 (movement::next_subword_end(map, head), SelectionGoal::None)
13499 });
13500 })
13501 }
13502
13503 pub fn delete_to_next_word_end(
13504 &mut self,
13505 action: &DeleteToNextWordEnd,
13506 window: &mut Window,
13507 cx: &mut Context<Self>,
13508 ) {
13509 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13510 self.transact(window, cx, |this, window, cx| {
13511 this.change_selections(Default::default(), window, cx, |s| {
13512 s.move_with(|map, selection| {
13513 if selection.is_empty() {
13514 let mut cursor = if action.ignore_newlines {
13515 movement::next_word_end(map, selection.head())
13516 } else {
13517 movement::next_word_end_or_newline(map, selection.head())
13518 };
13519 cursor = movement::adjust_greedy_deletion(
13520 map,
13521 selection.head(),
13522 cursor,
13523 action.ignore_brackets,
13524 );
13525 selection.set_head(cursor, SelectionGoal::None);
13526 }
13527 });
13528 });
13529 this.insert("", window, cx);
13530 });
13531 }
13532
13533 pub fn delete_to_next_subword_end(
13534 &mut self,
13535 _: &DeleteToNextSubwordEnd,
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 = movement::next_subword_end(map, selection.head());
13545 cursor =
13546 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13547 selection.set_head(cursor, SelectionGoal::None);
13548 }
13549 });
13550 });
13551 this.insert("", window, cx);
13552 });
13553 }
13554
13555 pub fn move_to_beginning_of_line(
13556 &mut self,
13557 action: &MoveToBeginningOfLine,
13558 window: &mut Window,
13559 cx: &mut Context<Self>,
13560 ) {
13561 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13562 self.change_selections(Default::default(), window, cx, |s| {
13563 s.move_cursors_with(|map, head, _| {
13564 (
13565 movement::indented_line_beginning(
13566 map,
13567 head,
13568 action.stop_at_soft_wraps,
13569 action.stop_at_indent,
13570 ),
13571 SelectionGoal::None,
13572 )
13573 });
13574 })
13575 }
13576
13577 pub fn select_to_beginning_of_line(
13578 &mut self,
13579 action: &SelectToBeginningOfLine,
13580 window: &mut Window,
13581 cx: &mut Context<Self>,
13582 ) {
13583 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13584 self.change_selections(Default::default(), window, cx, |s| {
13585 s.move_heads_with(|map, head, _| {
13586 (
13587 movement::indented_line_beginning(
13588 map,
13589 head,
13590 action.stop_at_soft_wraps,
13591 action.stop_at_indent,
13592 ),
13593 SelectionGoal::None,
13594 )
13595 });
13596 });
13597 }
13598
13599 pub fn delete_to_beginning_of_line(
13600 &mut self,
13601 action: &DeleteToBeginningOfLine,
13602 window: &mut Window,
13603 cx: &mut Context<Self>,
13604 ) {
13605 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13606 self.transact(window, cx, |this, window, cx| {
13607 this.change_selections(Default::default(), window, cx, |s| {
13608 s.move_with(|_, selection| {
13609 selection.reversed = true;
13610 });
13611 });
13612
13613 this.select_to_beginning_of_line(
13614 &SelectToBeginningOfLine {
13615 stop_at_soft_wraps: false,
13616 stop_at_indent: action.stop_at_indent,
13617 },
13618 window,
13619 cx,
13620 );
13621 this.backspace(&Backspace, window, cx);
13622 });
13623 }
13624
13625 pub fn move_to_end_of_line(
13626 &mut self,
13627 action: &MoveToEndOfLine,
13628 window: &mut Window,
13629 cx: &mut Context<Self>,
13630 ) {
13631 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13632 self.change_selections(Default::default(), window, cx, |s| {
13633 s.move_cursors_with(|map, head, _| {
13634 (
13635 movement::line_end(map, head, action.stop_at_soft_wraps),
13636 SelectionGoal::None,
13637 )
13638 });
13639 })
13640 }
13641
13642 pub fn select_to_end_of_line(
13643 &mut self,
13644 action: &SelectToEndOfLine,
13645 window: &mut Window,
13646 cx: &mut Context<Self>,
13647 ) {
13648 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13649 self.change_selections(Default::default(), window, cx, |s| {
13650 s.move_heads_with(|map, head, _| {
13651 (
13652 movement::line_end(map, head, action.stop_at_soft_wraps),
13653 SelectionGoal::None,
13654 )
13655 });
13656 })
13657 }
13658
13659 pub fn delete_to_end_of_line(
13660 &mut self,
13661 _: &DeleteToEndOfLine,
13662 window: &mut Window,
13663 cx: &mut Context<Self>,
13664 ) {
13665 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13666 self.transact(window, cx, |this, window, cx| {
13667 this.select_to_end_of_line(
13668 &SelectToEndOfLine {
13669 stop_at_soft_wraps: false,
13670 },
13671 window,
13672 cx,
13673 );
13674 this.delete(&Delete, window, cx);
13675 });
13676 }
13677
13678 pub fn cut_to_end_of_line(
13679 &mut self,
13680 action: &CutToEndOfLine,
13681 window: &mut Window,
13682 cx: &mut Context<Self>,
13683 ) {
13684 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13685 self.transact(window, cx, |this, window, cx| {
13686 this.select_to_end_of_line(
13687 &SelectToEndOfLine {
13688 stop_at_soft_wraps: false,
13689 },
13690 window,
13691 cx,
13692 );
13693 if !action.stop_at_newlines {
13694 this.change_selections(Default::default(), window, cx, |s| {
13695 s.move_with(|_, sel| {
13696 if sel.is_empty() {
13697 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13698 }
13699 });
13700 });
13701 }
13702 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13703 let item = this.cut_common(false, window, cx);
13704 cx.write_to_clipboard(item);
13705 });
13706 }
13707
13708 pub fn move_to_start_of_paragraph(
13709 &mut self,
13710 _: &MoveToStartOfParagraph,
13711 window: &mut Window,
13712 cx: &mut Context<Self>,
13713 ) {
13714 if matches!(self.mode, EditorMode::SingleLine) {
13715 cx.propagate();
13716 return;
13717 }
13718 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13719 self.change_selections(Default::default(), window, cx, |s| {
13720 s.move_with(|map, selection| {
13721 selection.collapse_to(
13722 movement::start_of_paragraph(map, selection.head(), 1),
13723 SelectionGoal::None,
13724 )
13725 });
13726 })
13727 }
13728
13729 pub fn move_to_end_of_paragraph(
13730 &mut self,
13731 _: &MoveToEndOfParagraph,
13732 window: &mut Window,
13733 cx: &mut Context<Self>,
13734 ) {
13735 if matches!(self.mode, EditorMode::SingleLine) {
13736 cx.propagate();
13737 return;
13738 }
13739 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13740 self.change_selections(Default::default(), window, cx, |s| {
13741 s.move_with(|map, selection| {
13742 selection.collapse_to(
13743 movement::end_of_paragraph(map, selection.head(), 1),
13744 SelectionGoal::None,
13745 )
13746 });
13747 })
13748 }
13749
13750 pub fn select_to_start_of_paragraph(
13751 &mut self,
13752 _: &SelectToStartOfParagraph,
13753 window: &mut Window,
13754 cx: &mut Context<Self>,
13755 ) {
13756 if matches!(self.mode, EditorMode::SingleLine) {
13757 cx.propagate();
13758 return;
13759 }
13760 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13761 self.change_selections(Default::default(), window, cx, |s| {
13762 s.move_heads_with(|map, head, _| {
13763 (
13764 movement::start_of_paragraph(map, head, 1),
13765 SelectionGoal::None,
13766 )
13767 });
13768 })
13769 }
13770
13771 pub fn select_to_end_of_paragraph(
13772 &mut self,
13773 _: &SelectToEndOfParagraph,
13774 window: &mut Window,
13775 cx: &mut Context<Self>,
13776 ) {
13777 if matches!(self.mode, EditorMode::SingleLine) {
13778 cx.propagate();
13779 return;
13780 }
13781 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13782 self.change_selections(Default::default(), window, cx, |s| {
13783 s.move_heads_with(|map, head, _| {
13784 (
13785 movement::end_of_paragraph(map, head, 1),
13786 SelectionGoal::None,
13787 )
13788 });
13789 })
13790 }
13791
13792 pub fn move_to_start_of_excerpt(
13793 &mut self,
13794 _: &MoveToStartOfExcerpt,
13795 window: &mut Window,
13796 cx: &mut Context<Self>,
13797 ) {
13798 if matches!(self.mode, EditorMode::SingleLine) {
13799 cx.propagate();
13800 return;
13801 }
13802 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13803 self.change_selections(Default::default(), window, cx, |s| {
13804 s.move_with(|map, selection| {
13805 selection.collapse_to(
13806 movement::start_of_excerpt(
13807 map,
13808 selection.head(),
13809 workspace::searchable::Direction::Prev,
13810 ),
13811 SelectionGoal::None,
13812 )
13813 });
13814 })
13815 }
13816
13817 pub fn move_to_start_of_next_excerpt(
13818 &mut self,
13819 _: &MoveToStartOfNextExcerpt,
13820 window: &mut Window,
13821 cx: &mut Context<Self>,
13822 ) {
13823 if matches!(self.mode, EditorMode::SingleLine) {
13824 cx.propagate();
13825 return;
13826 }
13827
13828 self.change_selections(Default::default(), window, cx, |s| {
13829 s.move_with(|map, selection| {
13830 selection.collapse_to(
13831 movement::start_of_excerpt(
13832 map,
13833 selection.head(),
13834 workspace::searchable::Direction::Next,
13835 ),
13836 SelectionGoal::None,
13837 )
13838 });
13839 })
13840 }
13841
13842 pub fn move_to_end_of_excerpt(
13843 &mut self,
13844 _: &MoveToEndOfExcerpt,
13845 window: &mut Window,
13846 cx: &mut Context<Self>,
13847 ) {
13848 if matches!(self.mode, EditorMode::SingleLine) {
13849 cx.propagate();
13850 return;
13851 }
13852 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13853 self.change_selections(Default::default(), window, cx, |s| {
13854 s.move_with(|map, selection| {
13855 selection.collapse_to(
13856 movement::end_of_excerpt(
13857 map,
13858 selection.head(),
13859 workspace::searchable::Direction::Next,
13860 ),
13861 SelectionGoal::None,
13862 )
13863 });
13864 })
13865 }
13866
13867 pub fn move_to_end_of_previous_excerpt(
13868 &mut self,
13869 _: &MoveToEndOfPreviousExcerpt,
13870 window: &mut Window,
13871 cx: &mut Context<Self>,
13872 ) {
13873 if matches!(self.mode, EditorMode::SingleLine) {
13874 cx.propagate();
13875 return;
13876 }
13877 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13878 self.change_selections(Default::default(), window, cx, |s| {
13879 s.move_with(|map, selection| {
13880 selection.collapse_to(
13881 movement::end_of_excerpt(
13882 map,
13883 selection.head(),
13884 workspace::searchable::Direction::Prev,
13885 ),
13886 SelectionGoal::None,
13887 )
13888 });
13889 })
13890 }
13891
13892 pub fn select_to_start_of_excerpt(
13893 &mut self,
13894 _: &SelectToStartOfExcerpt,
13895 window: &mut Window,
13896 cx: &mut Context<Self>,
13897 ) {
13898 if matches!(self.mode, EditorMode::SingleLine) {
13899 cx.propagate();
13900 return;
13901 }
13902 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13903 self.change_selections(Default::default(), window, cx, |s| {
13904 s.move_heads_with(|map, head, _| {
13905 (
13906 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13907 SelectionGoal::None,
13908 )
13909 });
13910 })
13911 }
13912
13913 pub fn select_to_start_of_next_excerpt(
13914 &mut self,
13915 _: &SelectToStartOfNextExcerpt,
13916 window: &mut Window,
13917 cx: &mut Context<Self>,
13918 ) {
13919 if matches!(self.mode, EditorMode::SingleLine) {
13920 cx.propagate();
13921 return;
13922 }
13923 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13924 self.change_selections(Default::default(), window, cx, |s| {
13925 s.move_heads_with(|map, head, _| {
13926 (
13927 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13928 SelectionGoal::None,
13929 )
13930 });
13931 })
13932 }
13933
13934 pub fn select_to_end_of_excerpt(
13935 &mut self,
13936 _: &SelectToEndOfExcerpt,
13937 window: &mut Window,
13938 cx: &mut Context<Self>,
13939 ) {
13940 if matches!(self.mode, EditorMode::SingleLine) {
13941 cx.propagate();
13942 return;
13943 }
13944 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13945 self.change_selections(Default::default(), window, cx, |s| {
13946 s.move_heads_with(|map, head, _| {
13947 (
13948 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13949 SelectionGoal::None,
13950 )
13951 });
13952 })
13953 }
13954
13955 pub fn select_to_end_of_previous_excerpt(
13956 &mut self,
13957 _: &SelectToEndOfPreviousExcerpt,
13958 window: &mut Window,
13959 cx: &mut Context<Self>,
13960 ) {
13961 if matches!(self.mode, EditorMode::SingleLine) {
13962 cx.propagate();
13963 return;
13964 }
13965 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13966 self.change_selections(Default::default(), window, cx, |s| {
13967 s.move_heads_with(|map, head, _| {
13968 (
13969 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13970 SelectionGoal::None,
13971 )
13972 });
13973 })
13974 }
13975
13976 pub fn move_to_beginning(
13977 &mut self,
13978 _: &MoveToBeginning,
13979 window: &mut Window,
13980 cx: &mut Context<Self>,
13981 ) {
13982 if matches!(self.mode, EditorMode::SingleLine) {
13983 cx.propagate();
13984 return;
13985 }
13986 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13987 self.change_selections(Default::default(), window, cx, |s| {
13988 s.select_ranges(vec![0..0]);
13989 });
13990 }
13991
13992 pub fn select_to_beginning(
13993 &mut self,
13994 _: &SelectToBeginning,
13995 window: &mut Window,
13996 cx: &mut Context<Self>,
13997 ) {
13998 let mut selection = self.selections.last::<Point>(cx);
13999 selection.set_head(Point::zero(), SelectionGoal::None);
14000 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14001 self.change_selections(Default::default(), window, cx, |s| {
14002 s.select(vec![selection]);
14003 });
14004 }
14005
14006 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14007 if matches!(self.mode, EditorMode::SingleLine) {
14008 cx.propagate();
14009 return;
14010 }
14011 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14012 let cursor = self.buffer.read(cx).read(cx).len();
14013 self.change_selections(Default::default(), window, cx, |s| {
14014 s.select_ranges(vec![cursor..cursor])
14015 });
14016 }
14017
14018 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14019 self.nav_history = nav_history;
14020 }
14021
14022 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14023 self.nav_history.as_ref()
14024 }
14025
14026 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14027 self.push_to_nav_history(
14028 self.selections.newest_anchor().head(),
14029 None,
14030 false,
14031 true,
14032 cx,
14033 );
14034 }
14035
14036 fn push_to_nav_history(
14037 &mut self,
14038 cursor_anchor: Anchor,
14039 new_position: Option<Point>,
14040 is_deactivate: bool,
14041 always: bool,
14042 cx: &mut Context<Self>,
14043 ) {
14044 if let Some(nav_history) = self.nav_history.as_mut() {
14045 let buffer = self.buffer.read(cx).read(cx);
14046 let cursor_position = cursor_anchor.to_point(&buffer);
14047 let scroll_state = self.scroll_manager.anchor();
14048 let scroll_top_row = scroll_state.top_row(&buffer);
14049 drop(buffer);
14050
14051 if let Some(new_position) = new_position {
14052 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14053 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14054 return;
14055 }
14056 }
14057
14058 nav_history.push(
14059 Some(NavigationData {
14060 cursor_anchor,
14061 cursor_position,
14062 scroll_anchor: scroll_state,
14063 scroll_top_row,
14064 }),
14065 cx,
14066 );
14067 cx.emit(EditorEvent::PushedToNavHistory {
14068 anchor: cursor_anchor,
14069 is_deactivate,
14070 })
14071 }
14072 }
14073
14074 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14075 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14076 let buffer = self.buffer.read(cx).snapshot(cx);
14077 let mut selection = self.selections.first::<usize>(cx);
14078 selection.set_head(buffer.len(), SelectionGoal::None);
14079 self.change_selections(Default::default(), window, cx, |s| {
14080 s.select(vec![selection]);
14081 });
14082 }
14083
14084 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14085 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14086 let end = self.buffer.read(cx).read(cx).len();
14087 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14088 s.select_ranges(vec![0..end]);
14089 });
14090 }
14091
14092 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14093 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14094 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14095 let mut selections = self.selections.all::<Point>(cx);
14096 let max_point = display_map.buffer_snapshot.max_point();
14097 for selection in &mut selections {
14098 let rows = selection.spanned_rows(true, &display_map);
14099 selection.start = Point::new(rows.start.0, 0);
14100 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14101 selection.reversed = false;
14102 }
14103 self.change_selections(Default::default(), window, cx, |s| {
14104 s.select(selections);
14105 });
14106 }
14107
14108 pub fn split_selection_into_lines(
14109 &mut self,
14110 action: &SplitSelectionIntoLines,
14111 window: &mut Window,
14112 cx: &mut Context<Self>,
14113 ) {
14114 let selections = self
14115 .selections
14116 .all::<Point>(cx)
14117 .into_iter()
14118 .map(|selection| selection.start..selection.end)
14119 .collect::<Vec<_>>();
14120 self.unfold_ranges(&selections, true, true, cx);
14121
14122 let mut new_selection_ranges = Vec::new();
14123 {
14124 let buffer = self.buffer.read(cx).read(cx);
14125 for selection in selections {
14126 for row in selection.start.row..selection.end.row {
14127 let line_start = Point::new(row, 0);
14128 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14129
14130 if action.keep_selections {
14131 // Keep the selection range for each line
14132 let selection_start = if row == selection.start.row {
14133 selection.start
14134 } else {
14135 line_start
14136 };
14137 new_selection_ranges.push(selection_start..line_end);
14138 } else {
14139 // Collapse to cursor at end of line
14140 new_selection_ranges.push(line_end..line_end);
14141 }
14142 }
14143
14144 let is_multiline_selection = selection.start.row != selection.end.row;
14145 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14146 // so this action feels more ergonomic when paired with other selection operations
14147 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14148 if !should_skip_last {
14149 if action.keep_selections {
14150 if is_multiline_selection {
14151 let line_start = Point::new(selection.end.row, 0);
14152 new_selection_ranges.push(line_start..selection.end);
14153 } else {
14154 new_selection_ranges.push(selection.start..selection.end);
14155 }
14156 } else {
14157 new_selection_ranges.push(selection.end..selection.end);
14158 }
14159 }
14160 }
14161 }
14162 self.change_selections(Default::default(), window, cx, |s| {
14163 s.select_ranges(new_selection_ranges);
14164 });
14165 }
14166
14167 pub fn add_selection_above(
14168 &mut self,
14169 _: &AddSelectionAbove,
14170 window: &mut Window,
14171 cx: &mut Context<Self>,
14172 ) {
14173 self.add_selection(true, window, cx);
14174 }
14175
14176 pub fn add_selection_below(
14177 &mut self,
14178 _: &AddSelectionBelow,
14179 window: &mut Window,
14180 cx: &mut Context<Self>,
14181 ) {
14182 self.add_selection(false, window, cx);
14183 }
14184
14185 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14186 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14187
14188 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14189 let all_selections = self.selections.all::<Point>(cx);
14190 let text_layout_details = self.text_layout_details(window);
14191
14192 let (mut columnar_selections, new_selections_to_columnarize) = {
14193 if let Some(state) = self.add_selections_state.as_ref() {
14194 let columnar_selection_ids: HashSet<_> = state
14195 .groups
14196 .iter()
14197 .flat_map(|group| group.stack.iter())
14198 .copied()
14199 .collect();
14200
14201 all_selections
14202 .into_iter()
14203 .partition(|s| columnar_selection_ids.contains(&s.id))
14204 } else {
14205 (Vec::new(), all_selections)
14206 }
14207 };
14208
14209 let mut state = self
14210 .add_selections_state
14211 .take()
14212 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14213
14214 for selection in new_selections_to_columnarize {
14215 let range = selection.display_range(&display_map).sorted();
14216 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14217 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14218 let positions = start_x.min(end_x)..start_x.max(end_x);
14219 let mut stack = Vec::new();
14220 for row in range.start.row().0..=range.end.row().0 {
14221 if let Some(selection) = self.selections.build_columnar_selection(
14222 &display_map,
14223 DisplayRow(row),
14224 &positions,
14225 selection.reversed,
14226 &text_layout_details,
14227 ) {
14228 stack.push(selection.id);
14229 columnar_selections.push(selection);
14230 }
14231 }
14232 if !stack.is_empty() {
14233 if above {
14234 stack.reverse();
14235 }
14236 state.groups.push(AddSelectionsGroup { above, stack });
14237 }
14238 }
14239
14240 let mut final_selections = Vec::new();
14241 let end_row = if above {
14242 DisplayRow(0)
14243 } else {
14244 display_map.max_point().row()
14245 };
14246
14247 let mut last_added_item_per_group = HashMap::default();
14248 for group in state.groups.iter_mut() {
14249 if let Some(last_id) = group.stack.last() {
14250 last_added_item_per_group.insert(*last_id, group);
14251 }
14252 }
14253
14254 for selection in columnar_selections {
14255 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14256 if above == group.above {
14257 let range = selection.display_range(&display_map).sorted();
14258 debug_assert_eq!(range.start.row(), range.end.row());
14259 let mut row = range.start.row();
14260 let positions =
14261 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14262 Pixels::from(start)..Pixels::from(end)
14263 } else {
14264 let start_x =
14265 display_map.x_for_display_point(range.start, &text_layout_details);
14266 let end_x =
14267 display_map.x_for_display_point(range.end, &text_layout_details);
14268 start_x.min(end_x)..start_x.max(end_x)
14269 };
14270
14271 let mut maybe_new_selection = None;
14272 while row != end_row {
14273 if above {
14274 row.0 -= 1;
14275 } else {
14276 row.0 += 1;
14277 }
14278 if let Some(new_selection) = self.selections.build_columnar_selection(
14279 &display_map,
14280 row,
14281 &positions,
14282 selection.reversed,
14283 &text_layout_details,
14284 ) {
14285 maybe_new_selection = Some(new_selection);
14286 break;
14287 }
14288 }
14289
14290 if let Some(new_selection) = maybe_new_selection {
14291 group.stack.push(new_selection.id);
14292 if above {
14293 final_selections.push(new_selection);
14294 final_selections.push(selection);
14295 } else {
14296 final_selections.push(selection);
14297 final_selections.push(new_selection);
14298 }
14299 } else {
14300 final_selections.push(selection);
14301 }
14302 } else {
14303 group.stack.pop();
14304 }
14305 } else {
14306 final_selections.push(selection);
14307 }
14308 }
14309
14310 self.change_selections(Default::default(), window, cx, |s| {
14311 s.select(final_selections);
14312 });
14313
14314 let final_selection_ids: HashSet<_> = self
14315 .selections
14316 .all::<Point>(cx)
14317 .iter()
14318 .map(|s| s.id)
14319 .collect();
14320 state.groups.retain_mut(|group| {
14321 // selections might get merged above so we remove invalid items from stacks
14322 group.stack.retain(|id| final_selection_ids.contains(id));
14323
14324 // single selection in stack can be treated as initial state
14325 group.stack.len() > 1
14326 });
14327
14328 if !state.groups.is_empty() {
14329 self.add_selections_state = Some(state);
14330 }
14331 }
14332
14333 fn select_match_ranges(
14334 &mut self,
14335 range: Range<usize>,
14336 reversed: bool,
14337 replace_newest: bool,
14338 auto_scroll: Option<Autoscroll>,
14339 window: &mut Window,
14340 cx: &mut Context<Editor>,
14341 ) {
14342 self.unfold_ranges(
14343 std::slice::from_ref(&range),
14344 false,
14345 auto_scroll.is_some(),
14346 cx,
14347 );
14348 let effects = if let Some(scroll) = auto_scroll {
14349 SelectionEffects::scroll(scroll)
14350 } else {
14351 SelectionEffects::no_scroll()
14352 };
14353 self.change_selections(effects, window, cx, |s| {
14354 if replace_newest {
14355 s.delete(s.newest_anchor().id);
14356 }
14357 if reversed {
14358 s.insert_range(range.end..range.start);
14359 } else {
14360 s.insert_range(range);
14361 }
14362 });
14363 }
14364
14365 pub fn select_next_match_internal(
14366 &mut self,
14367 display_map: &DisplaySnapshot,
14368 replace_newest: bool,
14369 autoscroll: Option<Autoscroll>,
14370 window: &mut Window,
14371 cx: &mut Context<Self>,
14372 ) -> Result<()> {
14373 let buffer = &display_map.buffer_snapshot;
14374 let mut selections = self.selections.all::<usize>(cx);
14375 if let Some(mut select_next_state) = self.select_next_state.take() {
14376 let query = &select_next_state.query;
14377 if !select_next_state.done {
14378 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14379 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14380 let mut next_selected_range = None;
14381
14382 let bytes_after_last_selection =
14383 buffer.bytes_in_range(last_selection.end..buffer.len());
14384 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14385 let query_matches = query
14386 .stream_find_iter(bytes_after_last_selection)
14387 .map(|result| (last_selection.end, result))
14388 .chain(
14389 query
14390 .stream_find_iter(bytes_before_first_selection)
14391 .map(|result| (0, result)),
14392 );
14393
14394 for (start_offset, query_match) in query_matches {
14395 let query_match = query_match.unwrap(); // can only fail due to I/O
14396 let offset_range =
14397 start_offset + query_match.start()..start_offset + query_match.end();
14398
14399 if !select_next_state.wordwise
14400 || (!buffer.is_inside_word(offset_range.start, None)
14401 && !buffer.is_inside_word(offset_range.end, None))
14402 {
14403 // TODO: This is n^2, because we might check all the selections
14404 if !selections
14405 .iter()
14406 .any(|selection| selection.range().overlaps(&offset_range))
14407 {
14408 next_selected_range = Some(offset_range);
14409 break;
14410 }
14411 }
14412 }
14413
14414 if let Some(next_selected_range) = next_selected_range {
14415 self.select_match_ranges(
14416 next_selected_range,
14417 last_selection.reversed,
14418 replace_newest,
14419 autoscroll,
14420 window,
14421 cx,
14422 );
14423 } else {
14424 select_next_state.done = true;
14425 }
14426 }
14427
14428 self.select_next_state = Some(select_next_state);
14429 } else {
14430 let mut only_carets = true;
14431 let mut same_text_selected = true;
14432 let mut selected_text = None;
14433
14434 let mut selections_iter = selections.iter().peekable();
14435 while let Some(selection) = selections_iter.next() {
14436 if selection.start != selection.end {
14437 only_carets = false;
14438 }
14439
14440 if same_text_selected {
14441 if selected_text.is_none() {
14442 selected_text =
14443 Some(buffer.text_for_range(selection.range()).collect::<String>());
14444 }
14445
14446 if let Some(next_selection) = selections_iter.peek() {
14447 if next_selection.range().len() == selection.range().len() {
14448 let next_selected_text = buffer
14449 .text_for_range(next_selection.range())
14450 .collect::<String>();
14451 if Some(next_selected_text) != selected_text {
14452 same_text_selected = false;
14453 selected_text = None;
14454 }
14455 } else {
14456 same_text_selected = false;
14457 selected_text = None;
14458 }
14459 }
14460 }
14461 }
14462
14463 if only_carets {
14464 for selection in &mut selections {
14465 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14466 selection.start = word_range.start;
14467 selection.end = word_range.end;
14468 selection.goal = SelectionGoal::None;
14469 selection.reversed = false;
14470 self.select_match_ranges(
14471 selection.start..selection.end,
14472 selection.reversed,
14473 replace_newest,
14474 autoscroll,
14475 window,
14476 cx,
14477 );
14478 }
14479
14480 if selections.len() == 1 {
14481 let selection = selections
14482 .last()
14483 .expect("ensured that there's only one selection");
14484 let query = buffer
14485 .text_for_range(selection.start..selection.end)
14486 .collect::<String>();
14487 let is_empty = query.is_empty();
14488 let select_state = SelectNextState {
14489 query: AhoCorasick::new(&[query])?,
14490 wordwise: true,
14491 done: is_empty,
14492 };
14493 self.select_next_state = Some(select_state);
14494 } else {
14495 self.select_next_state = None;
14496 }
14497 } else if let Some(selected_text) = selected_text {
14498 self.select_next_state = Some(SelectNextState {
14499 query: AhoCorasick::new(&[selected_text])?,
14500 wordwise: false,
14501 done: false,
14502 });
14503 self.select_next_match_internal(
14504 display_map,
14505 replace_newest,
14506 autoscroll,
14507 window,
14508 cx,
14509 )?;
14510 }
14511 }
14512 Ok(())
14513 }
14514
14515 pub fn select_all_matches(
14516 &mut self,
14517 _action: &SelectAllMatches,
14518 window: &mut Window,
14519 cx: &mut Context<Self>,
14520 ) -> Result<()> {
14521 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14522
14523 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14524
14525 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14526 let Some(select_next_state) = self.select_next_state.as_mut() else {
14527 return Ok(());
14528 };
14529 if select_next_state.done {
14530 return Ok(());
14531 }
14532
14533 let mut new_selections = Vec::new();
14534
14535 let reversed = self.selections.oldest::<usize>(cx).reversed;
14536 let buffer = &display_map.buffer_snapshot;
14537 let query_matches = select_next_state
14538 .query
14539 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14540
14541 for query_match in query_matches.into_iter() {
14542 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14543 let offset_range = if reversed {
14544 query_match.end()..query_match.start()
14545 } else {
14546 query_match.start()..query_match.end()
14547 };
14548
14549 if !select_next_state.wordwise
14550 || (!buffer.is_inside_word(offset_range.start, None)
14551 && !buffer.is_inside_word(offset_range.end, None))
14552 {
14553 new_selections.push(offset_range.start..offset_range.end);
14554 }
14555 }
14556
14557 select_next_state.done = true;
14558
14559 if new_selections.is_empty() {
14560 log::error!("bug: new_selections is empty in select_all_matches");
14561 return Ok(());
14562 }
14563
14564 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14565 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14566 selections.select_ranges(new_selections)
14567 });
14568
14569 Ok(())
14570 }
14571
14572 pub fn select_next(
14573 &mut self,
14574 action: &SelectNext,
14575 window: &mut Window,
14576 cx: &mut Context<Self>,
14577 ) -> Result<()> {
14578 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14579 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14580 self.select_next_match_internal(
14581 &display_map,
14582 action.replace_newest,
14583 Some(Autoscroll::newest()),
14584 window,
14585 cx,
14586 )?;
14587 Ok(())
14588 }
14589
14590 pub fn select_previous(
14591 &mut self,
14592 action: &SelectPrevious,
14593 window: &mut Window,
14594 cx: &mut Context<Self>,
14595 ) -> Result<()> {
14596 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14597 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14598 let buffer = &display_map.buffer_snapshot;
14599 let mut selections = self.selections.all::<usize>(cx);
14600 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14601 let query = &select_prev_state.query;
14602 if !select_prev_state.done {
14603 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14604 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14605 let mut next_selected_range = None;
14606 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14607 let bytes_before_last_selection =
14608 buffer.reversed_bytes_in_range(0..last_selection.start);
14609 let bytes_after_first_selection =
14610 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14611 let query_matches = query
14612 .stream_find_iter(bytes_before_last_selection)
14613 .map(|result| (last_selection.start, result))
14614 .chain(
14615 query
14616 .stream_find_iter(bytes_after_first_selection)
14617 .map(|result| (buffer.len(), result)),
14618 );
14619 for (end_offset, query_match) in query_matches {
14620 let query_match = query_match.unwrap(); // can only fail due to I/O
14621 let offset_range =
14622 end_offset - query_match.end()..end_offset - query_match.start();
14623
14624 if !select_prev_state.wordwise
14625 || (!buffer.is_inside_word(offset_range.start, None)
14626 && !buffer.is_inside_word(offset_range.end, None))
14627 {
14628 next_selected_range = Some(offset_range);
14629 break;
14630 }
14631 }
14632
14633 if let Some(next_selected_range) = next_selected_range {
14634 self.select_match_ranges(
14635 next_selected_range,
14636 last_selection.reversed,
14637 action.replace_newest,
14638 Some(Autoscroll::newest()),
14639 window,
14640 cx,
14641 );
14642 } else {
14643 select_prev_state.done = true;
14644 }
14645 }
14646
14647 self.select_prev_state = Some(select_prev_state);
14648 } else {
14649 let mut only_carets = true;
14650 let mut same_text_selected = true;
14651 let mut selected_text = None;
14652
14653 let mut selections_iter = selections.iter().peekable();
14654 while let Some(selection) = selections_iter.next() {
14655 if selection.start != selection.end {
14656 only_carets = false;
14657 }
14658
14659 if same_text_selected {
14660 if selected_text.is_none() {
14661 selected_text =
14662 Some(buffer.text_for_range(selection.range()).collect::<String>());
14663 }
14664
14665 if let Some(next_selection) = selections_iter.peek() {
14666 if next_selection.range().len() == selection.range().len() {
14667 let next_selected_text = buffer
14668 .text_for_range(next_selection.range())
14669 .collect::<String>();
14670 if Some(next_selected_text) != selected_text {
14671 same_text_selected = false;
14672 selected_text = None;
14673 }
14674 } else {
14675 same_text_selected = false;
14676 selected_text = None;
14677 }
14678 }
14679 }
14680 }
14681
14682 if only_carets {
14683 for selection in &mut selections {
14684 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14685 selection.start = word_range.start;
14686 selection.end = word_range.end;
14687 selection.goal = SelectionGoal::None;
14688 selection.reversed = false;
14689 self.select_match_ranges(
14690 selection.start..selection.end,
14691 selection.reversed,
14692 action.replace_newest,
14693 Some(Autoscroll::newest()),
14694 window,
14695 cx,
14696 );
14697 }
14698 if selections.len() == 1 {
14699 let selection = selections
14700 .last()
14701 .expect("ensured that there's only one selection");
14702 let query = buffer
14703 .text_for_range(selection.start..selection.end)
14704 .collect::<String>();
14705 let is_empty = query.is_empty();
14706 let select_state = SelectNextState {
14707 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14708 wordwise: true,
14709 done: is_empty,
14710 };
14711 self.select_prev_state = Some(select_state);
14712 } else {
14713 self.select_prev_state = None;
14714 }
14715 } else if let Some(selected_text) = selected_text {
14716 self.select_prev_state = Some(SelectNextState {
14717 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14718 wordwise: false,
14719 done: false,
14720 });
14721 self.select_previous(action, window, cx)?;
14722 }
14723 }
14724 Ok(())
14725 }
14726
14727 pub fn find_next_match(
14728 &mut self,
14729 _: &FindNextMatch,
14730 window: &mut Window,
14731 cx: &mut Context<Self>,
14732 ) -> Result<()> {
14733 let selections = self.selections.disjoint_anchors_arc();
14734 match selections.first() {
14735 Some(first) if selections.len() >= 2 => {
14736 self.change_selections(Default::default(), window, cx, |s| {
14737 s.select_ranges([first.range()]);
14738 });
14739 }
14740 _ => self.select_next(
14741 &SelectNext {
14742 replace_newest: true,
14743 },
14744 window,
14745 cx,
14746 )?,
14747 }
14748 Ok(())
14749 }
14750
14751 pub fn find_previous_match(
14752 &mut self,
14753 _: &FindPreviousMatch,
14754 window: &mut Window,
14755 cx: &mut Context<Self>,
14756 ) -> Result<()> {
14757 let selections = self.selections.disjoint_anchors_arc();
14758 match selections.last() {
14759 Some(last) if selections.len() >= 2 => {
14760 self.change_selections(Default::default(), window, cx, |s| {
14761 s.select_ranges([last.range()]);
14762 });
14763 }
14764 _ => self.select_previous(
14765 &SelectPrevious {
14766 replace_newest: true,
14767 },
14768 window,
14769 cx,
14770 )?,
14771 }
14772 Ok(())
14773 }
14774
14775 pub fn toggle_comments(
14776 &mut self,
14777 action: &ToggleComments,
14778 window: &mut Window,
14779 cx: &mut Context<Self>,
14780 ) {
14781 if self.read_only(cx) {
14782 return;
14783 }
14784 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14785 let text_layout_details = &self.text_layout_details(window);
14786 self.transact(window, cx, |this, window, cx| {
14787 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14788 let mut edits = Vec::new();
14789 let mut selection_edit_ranges = Vec::new();
14790 let mut last_toggled_row = None;
14791 let snapshot = this.buffer.read(cx).read(cx);
14792 let empty_str: Arc<str> = Arc::default();
14793 let mut suffixes_inserted = Vec::new();
14794 let ignore_indent = action.ignore_indent;
14795
14796 fn comment_prefix_range(
14797 snapshot: &MultiBufferSnapshot,
14798 row: MultiBufferRow,
14799 comment_prefix: &str,
14800 comment_prefix_whitespace: &str,
14801 ignore_indent: bool,
14802 ) -> Range<Point> {
14803 let indent_size = if ignore_indent {
14804 0
14805 } else {
14806 snapshot.indent_size_for_line(row).len
14807 };
14808
14809 let start = Point::new(row.0, indent_size);
14810
14811 let mut line_bytes = snapshot
14812 .bytes_in_range(start..snapshot.max_point())
14813 .flatten()
14814 .copied();
14815
14816 // If this line currently begins with the line comment prefix, then record
14817 // the range containing the prefix.
14818 if line_bytes
14819 .by_ref()
14820 .take(comment_prefix.len())
14821 .eq(comment_prefix.bytes())
14822 {
14823 // Include any whitespace that matches the comment prefix.
14824 let matching_whitespace_len = line_bytes
14825 .zip(comment_prefix_whitespace.bytes())
14826 .take_while(|(a, b)| a == b)
14827 .count() as u32;
14828 let end = Point::new(
14829 start.row,
14830 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14831 );
14832 start..end
14833 } else {
14834 start..start
14835 }
14836 }
14837
14838 fn comment_suffix_range(
14839 snapshot: &MultiBufferSnapshot,
14840 row: MultiBufferRow,
14841 comment_suffix: &str,
14842 comment_suffix_has_leading_space: bool,
14843 ) -> Range<Point> {
14844 let end = Point::new(row.0, snapshot.line_len(row));
14845 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14846
14847 let mut line_end_bytes = snapshot
14848 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14849 .flatten()
14850 .copied();
14851
14852 let leading_space_len = if suffix_start_column > 0
14853 && line_end_bytes.next() == Some(b' ')
14854 && comment_suffix_has_leading_space
14855 {
14856 1
14857 } else {
14858 0
14859 };
14860
14861 // If this line currently begins with the line comment prefix, then record
14862 // the range containing the prefix.
14863 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14864 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14865 start..end
14866 } else {
14867 end..end
14868 }
14869 }
14870
14871 // TODO: Handle selections that cross excerpts
14872 for selection in &mut selections {
14873 let start_column = snapshot
14874 .indent_size_for_line(MultiBufferRow(selection.start.row))
14875 .len;
14876 let language = if let Some(language) =
14877 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14878 {
14879 language
14880 } else {
14881 continue;
14882 };
14883
14884 selection_edit_ranges.clear();
14885
14886 // If multiple selections contain a given row, avoid processing that
14887 // row more than once.
14888 let mut start_row = MultiBufferRow(selection.start.row);
14889 if last_toggled_row == Some(start_row) {
14890 start_row = start_row.next_row();
14891 }
14892 let end_row =
14893 if selection.end.row > selection.start.row && selection.end.column == 0 {
14894 MultiBufferRow(selection.end.row - 1)
14895 } else {
14896 MultiBufferRow(selection.end.row)
14897 };
14898 last_toggled_row = Some(end_row);
14899
14900 if start_row > end_row {
14901 continue;
14902 }
14903
14904 // If the language has line comments, toggle those.
14905 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14906
14907 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14908 if ignore_indent {
14909 full_comment_prefixes = full_comment_prefixes
14910 .into_iter()
14911 .map(|s| Arc::from(s.trim_end()))
14912 .collect();
14913 }
14914
14915 if !full_comment_prefixes.is_empty() {
14916 let first_prefix = full_comment_prefixes
14917 .first()
14918 .expect("prefixes is non-empty");
14919 let prefix_trimmed_lengths = full_comment_prefixes
14920 .iter()
14921 .map(|p| p.trim_end_matches(' ').len())
14922 .collect::<SmallVec<[usize; 4]>>();
14923
14924 let mut all_selection_lines_are_comments = true;
14925
14926 for row in start_row.0..=end_row.0 {
14927 let row = MultiBufferRow(row);
14928 if start_row < end_row && snapshot.is_line_blank(row) {
14929 continue;
14930 }
14931
14932 let prefix_range = full_comment_prefixes
14933 .iter()
14934 .zip(prefix_trimmed_lengths.iter().copied())
14935 .map(|(prefix, trimmed_prefix_len)| {
14936 comment_prefix_range(
14937 snapshot.deref(),
14938 row,
14939 &prefix[..trimmed_prefix_len],
14940 &prefix[trimmed_prefix_len..],
14941 ignore_indent,
14942 )
14943 })
14944 .max_by_key(|range| range.end.column - range.start.column)
14945 .expect("prefixes is non-empty");
14946
14947 if prefix_range.is_empty() {
14948 all_selection_lines_are_comments = false;
14949 }
14950
14951 selection_edit_ranges.push(prefix_range);
14952 }
14953
14954 if all_selection_lines_are_comments {
14955 edits.extend(
14956 selection_edit_ranges
14957 .iter()
14958 .cloned()
14959 .map(|range| (range, empty_str.clone())),
14960 );
14961 } else {
14962 let min_column = selection_edit_ranges
14963 .iter()
14964 .map(|range| range.start.column)
14965 .min()
14966 .unwrap_or(0);
14967 edits.extend(selection_edit_ranges.iter().map(|range| {
14968 let position = Point::new(range.start.row, min_column);
14969 (position..position, first_prefix.clone())
14970 }));
14971 }
14972 } else if let Some(BlockCommentConfig {
14973 start: full_comment_prefix,
14974 end: comment_suffix,
14975 ..
14976 }) = language.block_comment()
14977 {
14978 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14979 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14980 let prefix_range = comment_prefix_range(
14981 snapshot.deref(),
14982 start_row,
14983 comment_prefix,
14984 comment_prefix_whitespace,
14985 ignore_indent,
14986 );
14987 let suffix_range = comment_suffix_range(
14988 snapshot.deref(),
14989 end_row,
14990 comment_suffix.trim_start_matches(' '),
14991 comment_suffix.starts_with(' '),
14992 );
14993
14994 if prefix_range.is_empty() || suffix_range.is_empty() {
14995 edits.push((
14996 prefix_range.start..prefix_range.start,
14997 full_comment_prefix.clone(),
14998 ));
14999 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15000 suffixes_inserted.push((end_row, comment_suffix.len()));
15001 } else {
15002 edits.push((prefix_range, empty_str.clone()));
15003 edits.push((suffix_range, empty_str.clone()));
15004 }
15005 } else {
15006 continue;
15007 }
15008 }
15009
15010 drop(snapshot);
15011 this.buffer.update(cx, |buffer, cx| {
15012 buffer.edit(edits, None, cx);
15013 });
15014
15015 // Adjust selections so that they end before any comment suffixes that
15016 // were inserted.
15017 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15018 let mut selections = this.selections.all::<Point>(cx);
15019 let snapshot = this.buffer.read(cx).read(cx);
15020 for selection in &mut selections {
15021 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15022 match row.cmp(&MultiBufferRow(selection.end.row)) {
15023 Ordering::Less => {
15024 suffixes_inserted.next();
15025 continue;
15026 }
15027 Ordering::Greater => break,
15028 Ordering::Equal => {
15029 if selection.end.column == snapshot.line_len(row) {
15030 if selection.is_empty() {
15031 selection.start.column -= suffix_len as u32;
15032 }
15033 selection.end.column -= suffix_len as u32;
15034 }
15035 break;
15036 }
15037 }
15038 }
15039 }
15040
15041 drop(snapshot);
15042 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15043
15044 let selections = this.selections.all::<Point>(cx);
15045 let selections_on_single_row = selections.windows(2).all(|selections| {
15046 selections[0].start.row == selections[1].start.row
15047 && selections[0].end.row == selections[1].end.row
15048 && selections[0].start.row == selections[0].end.row
15049 });
15050 let selections_selecting = selections
15051 .iter()
15052 .any(|selection| selection.start != selection.end);
15053 let advance_downwards = action.advance_downwards
15054 && selections_on_single_row
15055 && !selections_selecting
15056 && !matches!(this.mode, EditorMode::SingleLine);
15057
15058 if advance_downwards {
15059 let snapshot = this.buffer.read(cx).snapshot(cx);
15060
15061 this.change_selections(Default::default(), window, cx, |s| {
15062 s.move_cursors_with(|display_snapshot, display_point, _| {
15063 let mut point = display_point.to_point(display_snapshot);
15064 point.row += 1;
15065 point = snapshot.clip_point(point, Bias::Left);
15066 let display_point = point.to_display_point(display_snapshot);
15067 let goal = SelectionGoal::HorizontalPosition(
15068 display_snapshot
15069 .x_for_display_point(display_point, text_layout_details)
15070 .into(),
15071 );
15072 (display_point, goal)
15073 })
15074 });
15075 }
15076 });
15077 }
15078
15079 pub fn select_enclosing_symbol(
15080 &mut self,
15081 _: &SelectEnclosingSymbol,
15082 window: &mut Window,
15083 cx: &mut Context<Self>,
15084 ) {
15085 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15086
15087 let buffer = self.buffer.read(cx).snapshot(cx);
15088 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
15089
15090 fn update_selection(
15091 selection: &Selection<usize>,
15092 buffer_snap: &MultiBufferSnapshot,
15093 ) -> Option<Selection<usize>> {
15094 let cursor = selection.head();
15095 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15096 for symbol in symbols.iter().rev() {
15097 let start = symbol.range.start.to_offset(buffer_snap);
15098 let end = symbol.range.end.to_offset(buffer_snap);
15099 let new_range = start..end;
15100 if start < selection.start || end > selection.end {
15101 return Some(Selection {
15102 id: selection.id,
15103 start: new_range.start,
15104 end: new_range.end,
15105 goal: SelectionGoal::None,
15106 reversed: selection.reversed,
15107 });
15108 }
15109 }
15110 None
15111 }
15112
15113 let mut selected_larger_symbol = false;
15114 let new_selections = old_selections
15115 .iter()
15116 .map(|selection| match update_selection(selection, &buffer) {
15117 Some(new_selection) => {
15118 if new_selection.range() != selection.range() {
15119 selected_larger_symbol = true;
15120 }
15121 new_selection
15122 }
15123 None => selection.clone(),
15124 })
15125 .collect::<Vec<_>>();
15126
15127 if selected_larger_symbol {
15128 self.change_selections(Default::default(), window, cx, |s| {
15129 s.select(new_selections);
15130 });
15131 }
15132 }
15133
15134 pub fn select_larger_syntax_node(
15135 &mut self,
15136 _: &SelectLargerSyntaxNode,
15137 window: &mut Window,
15138 cx: &mut Context<Self>,
15139 ) {
15140 let Some(visible_row_count) = self.visible_row_count() else {
15141 return;
15142 };
15143 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15144 if old_selections.is_empty() {
15145 return;
15146 }
15147
15148 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15149
15150 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15151 let buffer = self.buffer.read(cx).snapshot(cx);
15152
15153 let mut selected_larger_node = false;
15154 let mut new_selections = old_selections
15155 .iter()
15156 .map(|selection| {
15157 let old_range = selection.start..selection.end;
15158
15159 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15160 // manually select word at selection
15161 if ["string_content", "inline"].contains(&node.kind()) {
15162 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15163 // ignore if word is already selected
15164 if !word_range.is_empty() && old_range != word_range {
15165 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15166 // only select word if start and end point belongs to same word
15167 if word_range == last_word_range {
15168 selected_larger_node = true;
15169 return Selection {
15170 id: selection.id,
15171 start: word_range.start,
15172 end: word_range.end,
15173 goal: SelectionGoal::None,
15174 reversed: selection.reversed,
15175 };
15176 }
15177 }
15178 }
15179 }
15180
15181 let mut new_range = old_range.clone();
15182 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15183 new_range = range;
15184 if !node.is_named() {
15185 continue;
15186 }
15187 if !display_map.intersects_fold(new_range.start)
15188 && !display_map.intersects_fold(new_range.end)
15189 {
15190 break;
15191 }
15192 }
15193
15194 selected_larger_node |= new_range != old_range;
15195 Selection {
15196 id: selection.id,
15197 start: new_range.start,
15198 end: new_range.end,
15199 goal: SelectionGoal::None,
15200 reversed: selection.reversed,
15201 }
15202 })
15203 .collect::<Vec<_>>();
15204
15205 if !selected_larger_node {
15206 return; // don't put this call in the history
15207 }
15208
15209 // scroll based on transformation done to the last selection created by the user
15210 let (last_old, last_new) = old_selections
15211 .last()
15212 .zip(new_selections.last().cloned())
15213 .expect("old_selections isn't empty");
15214
15215 // revert selection
15216 let is_selection_reversed = {
15217 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15218 new_selections.last_mut().expect("checked above").reversed =
15219 should_newest_selection_be_reversed;
15220 should_newest_selection_be_reversed
15221 };
15222
15223 if selected_larger_node {
15224 self.select_syntax_node_history.disable_clearing = true;
15225 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15226 s.select(new_selections.clone());
15227 });
15228 self.select_syntax_node_history.disable_clearing = false;
15229 }
15230
15231 let start_row = last_new.start.to_display_point(&display_map).row().0;
15232 let end_row = last_new.end.to_display_point(&display_map).row().0;
15233 let selection_height = end_row - start_row + 1;
15234 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15235
15236 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15237 let scroll_behavior = if fits_on_the_screen {
15238 self.request_autoscroll(Autoscroll::fit(), cx);
15239 SelectSyntaxNodeScrollBehavior::FitSelection
15240 } else if is_selection_reversed {
15241 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15242 SelectSyntaxNodeScrollBehavior::CursorTop
15243 } else {
15244 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15245 SelectSyntaxNodeScrollBehavior::CursorBottom
15246 };
15247
15248 self.select_syntax_node_history.push((
15249 old_selections,
15250 scroll_behavior,
15251 is_selection_reversed,
15252 ));
15253 }
15254
15255 pub fn select_smaller_syntax_node(
15256 &mut self,
15257 _: &SelectSmallerSyntaxNode,
15258 window: &mut Window,
15259 cx: &mut Context<Self>,
15260 ) {
15261 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15262
15263 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15264 self.select_syntax_node_history.pop()
15265 {
15266 if let Some(selection) = selections.last_mut() {
15267 selection.reversed = is_selection_reversed;
15268 }
15269
15270 self.select_syntax_node_history.disable_clearing = true;
15271 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15272 s.select(selections.to_vec());
15273 });
15274 self.select_syntax_node_history.disable_clearing = false;
15275
15276 match scroll_behavior {
15277 SelectSyntaxNodeScrollBehavior::CursorTop => {
15278 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15279 }
15280 SelectSyntaxNodeScrollBehavior::FitSelection => {
15281 self.request_autoscroll(Autoscroll::fit(), cx);
15282 }
15283 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15284 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15285 }
15286 }
15287 }
15288 }
15289
15290 pub fn unwrap_syntax_node(
15291 &mut self,
15292 _: &UnwrapSyntaxNode,
15293 window: &mut Window,
15294 cx: &mut Context<Self>,
15295 ) {
15296 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15297
15298 let buffer = self.buffer.read(cx).snapshot(cx);
15299 let selections = self
15300 .selections
15301 .all::<usize>(cx)
15302 .into_iter()
15303 // subtracting the offset requires sorting
15304 .sorted_by_key(|i| i.start);
15305
15306 let full_edits = selections
15307 .into_iter()
15308 .filter_map(|selection| {
15309 let child = if selection.is_empty()
15310 && let Some((_, ancestor_range)) =
15311 buffer.syntax_ancestor(selection.start..selection.end)
15312 {
15313 ancestor_range
15314 } else {
15315 selection.range()
15316 };
15317
15318 let mut parent = child.clone();
15319 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15320 parent = ancestor_range;
15321 if parent.start < child.start || parent.end > child.end {
15322 break;
15323 }
15324 }
15325
15326 if parent == child {
15327 return None;
15328 }
15329 let text = buffer.text_for_range(child).collect::<String>();
15330 Some((selection.id, parent, text))
15331 })
15332 .collect::<Vec<_>>();
15333 if full_edits.is_empty() {
15334 return;
15335 }
15336
15337 self.transact(window, cx, |this, window, cx| {
15338 this.buffer.update(cx, |buffer, cx| {
15339 buffer.edit(
15340 full_edits
15341 .iter()
15342 .map(|(_, p, t)| (p.clone(), t.clone()))
15343 .collect::<Vec<_>>(),
15344 None,
15345 cx,
15346 );
15347 });
15348 this.change_selections(Default::default(), window, cx, |s| {
15349 let mut offset = 0;
15350 let mut selections = vec![];
15351 for (id, parent, text) in full_edits {
15352 let start = parent.start - offset;
15353 offset += parent.len() - text.len();
15354 selections.push(Selection {
15355 id,
15356 start,
15357 end: start + text.len(),
15358 reversed: false,
15359 goal: Default::default(),
15360 });
15361 }
15362 s.select(selections);
15363 });
15364 });
15365 }
15366
15367 pub fn select_next_syntax_node(
15368 &mut self,
15369 _: &SelectNextSyntaxNode,
15370 window: &mut Window,
15371 cx: &mut Context<Self>,
15372 ) {
15373 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15374 if old_selections.is_empty() {
15375 return;
15376 }
15377
15378 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15379
15380 let buffer = self.buffer.read(cx).snapshot(cx);
15381 let mut selected_sibling = false;
15382
15383 let new_selections = old_selections
15384 .iter()
15385 .map(|selection| {
15386 let old_range = selection.start..selection.end;
15387
15388 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15389 let new_range = node.byte_range();
15390 selected_sibling = true;
15391 Selection {
15392 id: selection.id,
15393 start: new_range.start,
15394 end: new_range.end,
15395 goal: SelectionGoal::None,
15396 reversed: selection.reversed,
15397 }
15398 } else {
15399 selection.clone()
15400 }
15401 })
15402 .collect::<Vec<_>>();
15403
15404 if selected_sibling {
15405 self.change_selections(
15406 SelectionEffects::scroll(Autoscroll::fit()),
15407 window,
15408 cx,
15409 |s| {
15410 s.select(new_selections);
15411 },
15412 );
15413 }
15414 }
15415
15416 pub fn select_prev_syntax_node(
15417 &mut self,
15418 _: &SelectPreviousSyntaxNode,
15419 window: &mut Window,
15420 cx: &mut Context<Self>,
15421 ) {
15422 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15423 if old_selections.is_empty() {
15424 return;
15425 }
15426
15427 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15428
15429 let buffer = self.buffer.read(cx).snapshot(cx);
15430 let mut selected_sibling = false;
15431
15432 let new_selections = old_selections
15433 .iter()
15434 .map(|selection| {
15435 let old_range = selection.start..selection.end;
15436
15437 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15438 let new_range = node.byte_range();
15439 selected_sibling = true;
15440 Selection {
15441 id: selection.id,
15442 start: new_range.start,
15443 end: new_range.end,
15444 goal: SelectionGoal::None,
15445 reversed: selection.reversed,
15446 }
15447 } else {
15448 selection.clone()
15449 }
15450 })
15451 .collect::<Vec<_>>();
15452
15453 if selected_sibling {
15454 self.change_selections(
15455 SelectionEffects::scroll(Autoscroll::fit()),
15456 window,
15457 cx,
15458 |s| {
15459 s.select(new_selections);
15460 },
15461 );
15462 }
15463 }
15464
15465 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15466 if !EditorSettings::get_global(cx).gutter.runnables {
15467 self.clear_tasks();
15468 return Task::ready(());
15469 }
15470 let project = self.project().map(Entity::downgrade);
15471 let task_sources = self.lsp_task_sources(cx);
15472 let multi_buffer = self.buffer.downgrade();
15473 cx.spawn_in(window, async move |editor, cx| {
15474 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15475 let Some(project) = project.and_then(|p| p.upgrade()) else {
15476 return;
15477 };
15478 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15479 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15480 }) else {
15481 return;
15482 };
15483
15484 let hide_runnables = project
15485 .update(cx, |project, _| project.is_via_collab())
15486 .unwrap_or(true);
15487 if hide_runnables {
15488 return;
15489 }
15490 let new_rows =
15491 cx.background_spawn({
15492 let snapshot = display_snapshot.clone();
15493 async move {
15494 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15495 }
15496 })
15497 .await;
15498 let Ok(lsp_tasks) =
15499 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15500 else {
15501 return;
15502 };
15503 let lsp_tasks = lsp_tasks.await;
15504
15505 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15506 lsp_tasks
15507 .into_iter()
15508 .flat_map(|(kind, tasks)| {
15509 tasks.into_iter().filter_map(move |(location, task)| {
15510 Some((kind.clone(), location?, task))
15511 })
15512 })
15513 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15514 let buffer = location.target.buffer;
15515 let buffer_snapshot = buffer.read(cx).snapshot();
15516 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15517 |(excerpt_id, snapshot, _)| {
15518 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15519 display_snapshot
15520 .buffer_snapshot
15521 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15522 } else {
15523 None
15524 }
15525 },
15526 );
15527 if let Some(offset) = offset {
15528 let task_buffer_range =
15529 location.target.range.to_point(&buffer_snapshot);
15530 let context_buffer_range =
15531 task_buffer_range.to_offset(&buffer_snapshot);
15532 let context_range = BufferOffset(context_buffer_range.start)
15533 ..BufferOffset(context_buffer_range.end);
15534
15535 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15536 .or_insert_with(|| RunnableTasks {
15537 templates: Vec::new(),
15538 offset,
15539 column: task_buffer_range.start.column,
15540 extra_variables: HashMap::default(),
15541 context_range,
15542 })
15543 .templates
15544 .push((kind, task.original_task().clone()));
15545 }
15546
15547 acc
15548 })
15549 }) else {
15550 return;
15551 };
15552
15553 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15554 buffer.language_settings(cx).tasks.prefer_lsp
15555 }) else {
15556 return;
15557 };
15558
15559 let rows = Self::runnable_rows(
15560 project,
15561 display_snapshot,
15562 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15563 new_rows,
15564 cx.clone(),
15565 )
15566 .await;
15567 editor
15568 .update(cx, |editor, _| {
15569 editor.clear_tasks();
15570 for (key, mut value) in rows {
15571 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15572 value.templates.extend(lsp_tasks.templates);
15573 }
15574
15575 editor.insert_tasks(key, value);
15576 }
15577 for (key, value) in lsp_tasks_by_rows {
15578 editor.insert_tasks(key, value);
15579 }
15580 })
15581 .ok();
15582 })
15583 }
15584 fn fetch_runnable_ranges(
15585 snapshot: &DisplaySnapshot,
15586 range: Range<Anchor>,
15587 ) -> Vec<language::RunnableRange> {
15588 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15589 }
15590
15591 fn runnable_rows(
15592 project: Entity<Project>,
15593 snapshot: DisplaySnapshot,
15594 prefer_lsp: bool,
15595 runnable_ranges: Vec<RunnableRange>,
15596 cx: AsyncWindowContext,
15597 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15598 cx.spawn(async move |cx| {
15599 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15600 for mut runnable in runnable_ranges {
15601 let Some(tasks) = cx
15602 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15603 .ok()
15604 else {
15605 continue;
15606 };
15607 let mut tasks = tasks.await;
15608
15609 if prefer_lsp {
15610 tasks.retain(|(task_kind, _)| {
15611 !matches!(task_kind, TaskSourceKind::Language { .. })
15612 });
15613 }
15614 if tasks.is_empty() {
15615 continue;
15616 }
15617
15618 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15619 let Some(row) = snapshot
15620 .buffer_snapshot
15621 .buffer_line_for_row(MultiBufferRow(point.row))
15622 .map(|(_, range)| range.start.row)
15623 else {
15624 continue;
15625 };
15626
15627 let context_range =
15628 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15629 runnable_rows.push((
15630 (runnable.buffer_id, row),
15631 RunnableTasks {
15632 templates: tasks,
15633 offset: snapshot
15634 .buffer_snapshot
15635 .anchor_before(runnable.run_range.start),
15636 context_range,
15637 column: point.column,
15638 extra_variables: runnable.extra_captures,
15639 },
15640 ));
15641 }
15642 runnable_rows
15643 })
15644 }
15645
15646 fn templates_with_tags(
15647 project: &Entity<Project>,
15648 runnable: &mut Runnable,
15649 cx: &mut App,
15650 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15651 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15652 let (worktree_id, file) = project
15653 .buffer_for_id(runnable.buffer, cx)
15654 .and_then(|buffer| buffer.read(cx).file())
15655 .map(|file| (file.worktree_id(cx), file.clone()))
15656 .unzip();
15657
15658 (
15659 project.task_store().read(cx).task_inventory().cloned(),
15660 worktree_id,
15661 file,
15662 )
15663 });
15664
15665 let tags = mem::take(&mut runnable.tags);
15666 let language = runnable.language.clone();
15667 cx.spawn(async move |cx| {
15668 let mut templates_with_tags = Vec::new();
15669 if let Some(inventory) = inventory {
15670 for RunnableTag(tag) in tags {
15671 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15672 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15673 }) else {
15674 return templates_with_tags;
15675 };
15676 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15677 move |(_, template)| {
15678 template.tags.iter().any(|source_tag| source_tag == &tag)
15679 },
15680 ));
15681 }
15682 }
15683 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15684
15685 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15686 // Strongest source wins; if we have worktree tag binding, prefer that to
15687 // global and language bindings;
15688 // if we have a global binding, prefer that to language binding.
15689 let first_mismatch = templates_with_tags
15690 .iter()
15691 .position(|(tag_source, _)| tag_source != leading_tag_source);
15692 if let Some(index) = first_mismatch {
15693 templates_with_tags.truncate(index);
15694 }
15695 }
15696
15697 templates_with_tags
15698 })
15699 }
15700
15701 pub fn move_to_enclosing_bracket(
15702 &mut self,
15703 _: &MoveToEnclosingBracket,
15704 window: &mut Window,
15705 cx: &mut Context<Self>,
15706 ) {
15707 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15708 self.change_selections(Default::default(), window, cx, |s| {
15709 s.move_offsets_with(|snapshot, selection| {
15710 let Some(enclosing_bracket_ranges) =
15711 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15712 else {
15713 return;
15714 };
15715
15716 let mut best_length = usize::MAX;
15717 let mut best_inside = false;
15718 let mut best_in_bracket_range = false;
15719 let mut best_destination = None;
15720 for (open, close) in enclosing_bracket_ranges {
15721 let close = close.to_inclusive();
15722 let length = close.end() - open.start;
15723 let inside = selection.start >= open.end && selection.end <= *close.start();
15724 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15725 || close.contains(&selection.head());
15726
15727 // If best is next to a bracket and current isn't, skip
15728 if !in_bracket_range && best_in_bracket_range {
15729 continue;
15730 }
15731
15732 // Prefer smaller lengths unless best is inside and current isn't
15733 if length > best_length && (best_inside || !inside) {
15734 continue;
15735 }
15736
15737 best_length = length;
15738 best_inside = inside;
15739 best_in_bracket_range = in_bracket_range;
15740 best_destination = Some(
15741 if close.contains(&selection.start) && close.contains(&selection.end) {
15742 if inside { open.end } else { open.start }
15743 } else if inside {
15744 *close.start()
15745 } else {
15746 *close.end()
15747 },
15748 );
15749 }
15750
15751 if let Some(destination) = best_destination {
15752 selection.collapse_to(destination, SelectionGoal::None);
15753 }
15754 })
15755 });
15756 }
15757
15758 pub fn undo_selection(
15759 &mut self,
15760 _: &UndoSelection,
15761 window: &mut Window,
15762 cx: &mut Context<Self>,
15763 ) {
15764 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15765 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15766 self.selection_history.mode = SelectionHistoryMode::Undoing;
15767 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15768 this.end_selection(window, cx);
15769 this.change_selections(
15770 SelectionEffects::scroll(Autoscroll::newest()),
15771 window,
15772 cx,
15773 |s| s.select_anchors(entry.selections.to_vec()),
15774 );
15775 });
15776 self.selection_history.mode = SelectionHistoryMode::Normal;
15777
15778 self.select_next_state = entry.select_next_state;
15779 self.select_prev_state = entry.select_prev_state;
15780 self.add_selections_state = entry.add_selections_state;
15781 }
15782 }
15783
15784 pub fn redo_selection(
15785 &mut self,
15786 _: &RedoSelection,
15787 window: &mut Window,
15788 cx: &mut Context<Self>,
15789 ) {
15790 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15791 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15792 self.selection_history.mode = SelectionHistoryMode::Redoing;
15793 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15794 this.end_selection(window, cx);
15795 this.change_selections(
15796 SelectionEffects::scroll(Autoscroll::newest()),
15797 window,
15798 cx,
15799 |s| s.select_anchors(entry.selections.to_vec()),
15800 );
15801 });
15802 self.selection_history.mode = SelectionHistoryMode::Normal;
15803
15804 self.select_next_state = entry.select_next_state;
15805 self.select_prev_state = entry.select_prev_state;
15806 self.add_selections_state = entry.add_selections_state;
15807 }
15808 }
15809
15810 pub fn expand_excerpts(
15811 &mut self,
15812 action: &ExpandExcerpts,
15813 _: &mut Window,
15814 cx: &mut Context<Self>,
15815 ) {
15816 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15817 }
15818
15819 pub fn expand_excerpts_down(
15820 &mut self,
15821 action: &ExpandExcerptsDown,
15822 _: &mut Window,
15823 cx: &mut Context<Self>,
15824 ) {
15825 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15826 }
15827
15828 pub fn expand_excerpts_up(
15829 &mut self,
15830 action: &ExpandExcerptsUp,
15831 _: &mut Window,
15832 cx: &mut Context<Self>,
15833 ) {
15834 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15835 }
15836
15837 pub fn expand_excerpts_for_direction(
15838 &mut self,
15839 lines: u32,
15840 direction: ExpandExcerptDirection,
15841
15842 cx: &mut Context<Self>,
15843 ) {
15844 let selections = self.selections.disjoint_anchors_arc();
15845
15846 let lines = if lines == 0 {
15847 EditorSettings::get_global(cx).expand_excerpt_lines
15848 } else {
15849 lines
15850 };
15851
15852 self.buffer.update(cx, |buffer, cx| {
15853 let snapshot = buffer.snapshot(cx);
15854 let mut excerpt_ids = selections
15855 .iter()
15856 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15857 .collect::<Vec<_>>();
15858 excerpt_ids.sort();
15859 excerpt_ids.dedup();
15860 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15861 })
15862 }
15863
15864 pub fn expand_excerpt(
15865 &mut self,
15866 excerpt: ExcerptId,
15867 direction: ExpandExcerptDirection,
15868 window: &mut Window,
15869 cx: &mut Context<Self>,
15870 ) {
15871 let current_scroll_position = self.scroll_position(cx);
15872 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15873 let mut should_scroll_up = false;
15874
15875 if direction == ExpandExcerptDirection::Down {
15876 let multi_buffer = self.buffer.read(cx);
15877 let snapshot = multi_buffer.snapshot(cx);
15878 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15879 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15880 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15881 {
15882 let buffer_snapshot = buffer.read(cx).snapshot();
15883 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15884 let last_row = buffer_snapshot.max_point().row;
15885 let lines_below = last_row.saturating_sub(excerpt_end_row);
15886 should_scroll_up = lines_below >= lines_to_expand;
15887 }
15888 }
15889
15890 self.buffer.update(cx, |buffer, cx| {
15891 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15892 });
15893
15894 if should_scroll_up {
15895 let new_scroll_position =
15896 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15897 self.set_scroll_position(new_scroll_position, window, cx);
15898 }
15899 }
15900
15901 pub fn go_to_singleton_buffer_point(
15902 &mut self,
15903 point: Point,
15904 window: &mut Window,
15905 cx: &mut Context<Self>,
15906 ) {
15907 self.go_to_singleton_buffer_range(point..point, window, cx);
15908 }
15909
15910 pub fn go_to_singleton_buffer_range(
15911 &mut self,
15912 range: Range<Point>,
15913 window: &mut Window,
15914 cx: &mut Context<Self>,
15915 ) {
15916 let multibuffer = self.buffer().read(cx);
15917 let Some(buffer) = multibuffer.as_singleton() else {
15918 return;
15919 };
15920 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15921 return;
15922 };
15923 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15924 return;
15925 };
15926 self.change_selections(
15927 SelectionEffects::default().nav_history(true),
15928 window,
15929 cx,
15930 |s| s.select_anchor_ranges([start..end]),
15931 );
15932 }
15933
15934 pub fn go_to_diagnostic(
15935 &mut self,
15936 action: &GoToDiagnostic,
15937 window: &mut Window,
15938 cx: &mut Context<Self>,
15939 ) {
15940 if !self.diagnostics_enabled() {
15941 return;
15942 }
15943 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15944 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15945 }
15946
15947 pub fn go_to_prev_diagnostic(
15948 &mut self,
15949 action: &GoToPreviousDiagnostic,
15950 window: &mut Window,
15951 cx: &mut Context<Self>,
15952 ) {
15953 if !self.diagnostics_enabled() {
15954 return;
15955 }
15956 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15957 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15958 }
15959
15960 pub fn go_to_diagnostic_impl(
15961 &mut self,
15962 direction: Direction,
15963 severity: GoToDiagnosticSeverityFilter,
15964 window: &mut Window,
15965 cx: &mut Context<Self>,
15966 ) {
15967 let buffer = self.buffer.read(cx).snapshot(cx);
15968 let selection = self.selections.newest::<usize>(cx);
15969
15970 let mut active_group_id = None;
15971 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15972 && active_group.active_range.start.to_offset(&buffer) == selection.start
15973 {
15974 active_group_id = Some(active_group.group_id);
15975 }
15976
15977 fn filtered<'a>(
15978 snapshot: EditorSnapshot,
15979 severity: GoToDiagnosticSeverityFilter,
15980 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
15981 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
15982 diagnostics
15983 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15984 .filter(|entry| entry.range.start != entry.range.end)
15985 .filter(|entry| !entry.diagnostic.is_unnecessary)
15986 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15987 }
15988
15989 let snapshot = self.snapshot(window, cx);
15990 let before = filtered(
15991 snapshot.clone(),
15992 severity,
15993 buffer
15994 .diagnostics_in_range(0..selection.start)
15995 .filter(|entry| entry.range.start <= selection.start),
15996 );
15997 let after = filtered(
15998 snapshot,
15999 severity,
16000 buffer
16001 .diagnostics_in_range(selection.start..buffer.len())
16002 .filter(|entry| entry.range.start >= selection.start),
16003 );
16004
16005 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16006 if direction == Direction::Prev {
16007 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16008 {
16009 for diagnostic in prev_diagnostics.into_iter().rev() {
16010 if diagnostic.range.start != selection.start
16011 || active_group_id
16012 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16013 {
16014 found = Some(diagnostic);
16015 break 'outer;
16016 }
16017 }
16018 }
16019 } else {
16020 for diagnostic in after.chain(before) {
16021 if diagnostic.range.start != selection.start
16022 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16023 {
16024 found = Some(diagnostic);
16025 break;
16026 }
16027 }
16028 }
16029 let Some(next_diagnostic) = found else {
16030 return;
16031 };
16032
16033 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16034 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16035 return;
16036 };
16037 self.change_selections(Default::default(), window, cx, |s| {
16038 s.select_ranges(vec![
16039 next_diagnostic.range.start..next_diagnostic.range.start,
16040 ])
16041 });
16042 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16043 self.refresh_edit_prediction(false, true, window, cx);
16044 }
16045
16046 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16047 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16048 let snapshot = self.snapshot(window, cx);
16049 let selection = self.selections.newest::<Point>(cx);
16050 self.go_to_hunk_before_or_after_position(
16051 &snapshot,
16052 selection.head(),
16053 Direction::Next,
16054 window,
16055 cx,
16056 );
16057 }
16058
16059 pub fn go_to_hunk_before_or_after_position(
16060 &mut self,
16061 snapshot: &EditorSnapshot,
16062 position: Point,
16063 direction: Direction,
16064 window: &mut Window,
16065 cx: &mut Context<Editor>,
16066 ) {
16067 let row = if direction == Direction::Next {
16068 self.hunk_after_position(snapshot, position)
16069 .map(|hunk| hunk.row_range.start)
16070 } else {
16071 self.hunk_before_position(snapshot, position)
16072 };
16073
16074 if let Some(row) = row {
16075 let destination = Point::new(row.0, 0);
16076 let autoscroll = Autoscroll::center();
16077
16078 self.unfold_ranges(&[destination..destination], false, false, cx);
16079 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16080 s.select_ranges([destination..destination]);
16081 });
16082 }
16083 }
16084
16085 fn hunk_after_position(
16086 &mut self,
16087 snapshot: &EditorSnapshot,
16088 position: Point,
16089 ) -> Option<MultiBufferDiffHunk> {
16090 snapshot
16091 .buffer_snapshot
16092 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16093 .find(|hunk| hunk.row_range.start.0 > position.row)
16094 .or_else(|| {
16095 snapshot
16096 .buffer_snapshot
16097 .diff_hunks_in_range(Point::zero()..position)
16098 .find(|hunk| hunk.row_range.end.0 < position.row)
16099 })
16100 }
16101
16102 fn go_to_prev_hunk(
16103 &mut self,
16104 _: &GoToPreviousHunk,
16105 window: &mut Window,
16106 cx: &mut Context<Self>,
16107 ) {
16108 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16109 let snapshot = self.snapshot(window, cx);
16110 let selection = self.selections.newest::<Point>(cx);
16111 self.go_to_hunk_before_or_after_position(
16112 &snapshot,
16113 selection.head(),
16114 Direction::Prev,
16115 window,
16116 cx,
16117 );
16118 }
16119
16120 fn hunk_before_position(
16121 &mut self,
16122 snapshot: &EditorSnapshot,
16123 position: Point,
16124 ) -> Option<MultiBufferRow> {
16125 snapshot
16126 .buffer_snapshot
16127 .diff_hunk_before(position)
16128 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
16129 }
16130
16131 fn go_to_next_change(
16132 &mut self,
16133 _: &GoToNextChange,
16134 window: &mut Window,
16135 cx: &mut Context<Self>,
16136 ) {
16137 if let Some(selections) = self
16138 .change_list
16139 .next_change(1, Direction::Next)
16140 .map(|s| s.to_vec())
16141 {
16142 self.change_selections(Default::default(), window, cx, |s| {
16143 let map = s.display_map();
16144 s.select_display_ranges(selections.iter().map(|a| {
16145 let point = a.to_display_point(&map);
16146 point..point
16147 }))
16148 })
16149 }
16150 }
16151
16152 fn go_to_previous_change(
16153 &mut self,
16154 _: &GoToPreviousChange,
16155 window: &mut Window,
16156 cx: &mut Context<Self>,
16157 ) {
16158 if let Some(selections) = self
16159 .change_list
16160 .next_change(1, Direction::Prev)
16161 .map(|s| s.to_vec())
16162 {
16163 self.change_selections(Default::default(), window, cx, |s| {
16164 let map = s.display_map();
16165 s.select_display_ranges(selections.iter().map(|a| {
16166 let point = a.to_display_point(&map);
16167 point..point
16168 }))
16169 })
16170 }
16171 }
16172
16173 pub fn go_to_next_document_highlight(
16174 &mut self,
16175 _: &GoToNextDocumentHighlight,
16176 window: &mut Window,
16177 cx: &mut Context<Self>,
16178 ) {
16179 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16180 }
16181
16182 pub fn go_to_prev_document_highlight(
16183 &mut self,
16184 _: &GoToPreviousDocumentHighlight,
16185 window: &mut Window,
16186 cx: &mut Context<Self>,
16187 ) {
16188 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16189 }
16190
16191 pub fn go_to_document_highlight_before_or_after_position(
16192 &mut self,
16193 direction: Direction,
16194 window: &mut Window,
16195 cx: &mut Context<Editor>,
16196 ) {
16197 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16198 let snapshot = self.snapshot(window, cx);
16199 let buffer = &snapshot.buffer_snapshot;
16200 let position = self.selections.newest::<Point>(cx).head();
16201 let anchor_position = buffer.anchor_after(position);
16202
16203 // Get all document highlights (both read and write)
16204 let mut all_highlights = Vec::new();
16205
16206 if let Some((_, read_highlights)) = self
16207 .background_highlights
16208 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16209 {
16210 all_highlights.extend(read_highlights.iter());
16211 }
16212
16213 if let Some((_, write_highlights)) = self
16214 .background_highlights
16215 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16216 {
16217 all_highlights.extend(write_highlights.iter());
16218 }
16219
16220 if all_highlights.is_empty() {
16221 return;
16222 }
16223
16224 // Sort highlights by position
16225 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16226
16227 let target_highlight = match direction {
16228 Direction::Next => {
16229 // Find the first highlight after the current position
16230 all_highlights
16231 .iter()
16232 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16233 }
16234 Direction::Prev => {
16235 // Find the last highlight before the current position
16236 all_highlights
16237 .iter()
16238 .rev()
16239 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16240 }
16241 };
16242
16243 if let Some(highlight) = target_highlight {
16244 let destination = highlight.start.to_point(buffer);
16245 let autoscroll = Autoscroll::center();
16246
16247 self.unfold_ranges(&[destination..destination], false, false, cx);
16248 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16249 s.select_ranges([destination..destination]);
16250 });
16251 }
16252 }
16253
16254 fn go_to_line<T: 'static>(
16255 &mut self,
16256 position: Anchor,
16257 highlight_color: Option<Hsla>,
16258 window: &mut Window,
16259 cx: &mut Context<Self>,
16260 ) {
16261 let snapshot = self.snapshot(window, cx).display_snapshot;
16262 let position = position.to_point(&snapshot.buffer_snapshot);
16263 let start = snapshot
16264 .buffer_snapshot
16265 .clip_point(Point::new(position.row, 0), Bias::Left);
16266 let end = start + Point::new(1, 0);
16267 let start = snapshot.buffer_snapshot.anchor_before(start);
16268 let end = snapshot.buffer_snapshot.anchor_before(end);
16269
16270 self.highlight_rows::<T>(
16271 start..end,
16272 highlight_color
16273 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16274 Default::default(),
16275 cx,
16276 );
16277
16278 if self.buffer.read(cx).is_singleton() {
16279 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16280 }
16281 }
16282
16283 pub fn go_to_definition(
16284 &mut self,
16285 _: &GoToDefinition,
16286 window: &mut Window,
16287 cx: &mut Context<Self>,
16288 ) -> Task<Result<Navigated>> {
16289 let definition =
16290 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16291 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16292 cx.spawn_in(window, async move |editor, cx| {
16293 if definition.await? == Navigated::Yes {
16294 return Ok(Navigated::Yes);
16295 }
16296 match fallback_strategy {
16297 GoToDefinitionFallback::None => Ok(Navigated::No),
16298 GoToDefinitionFallback::FindAllReferences => {
16299 match editor.update_in(cx, |editor, window, cx| {
16300 editor.find_all_references(&FindAllReferences, window, cx)
16301 })? {
16302 Some(references) => references.await,
16303 None => Ok(Navigated::No),
16304 }
16305 }
16306 }
16307 })
16308 }
16309
16310 pub fn go_to_declaration(
16311 &mut self,
16312 _: &GoToDeclaration,
16313 window: &mut Window,
16314 cx: &mut Context<Self>,
16315 ) -> Task<Result<Navigated>> {
16316 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16317 }
16318
16319 pub fn go_to_declaration_split(
16320 &mut self,
16321 _: &GoToDeclaration,
16322 window: &mut Window,
16323 cx: &mut Context<Self>,
16324 ) -> Task<Result<Navigated>> {
16325 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16326 }
16327
16328 pub fn go_to_implementation(
16329 &mut self,
16330 _: &GoToImplementation,
16331 window: &mut Window,
16332 cx: &mut Context<Self>,
16333 ) -> Task<Result<Navigated>> {
16334 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16335 }
16336
16337 pub fn go_to_implementation_split(
16338 &mut self,
16339 _: &GoToImplementationSplit,
16340 window: &mut Window,
16341 cx: &mut Context<Self>,
16342 ) -> Task<Result<Navigated>> {
16343 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16344 }
16345
16346 pub fn go_to_type_definition(
16347 &mut self,
16348 _: &GoToTypeDefinition,
16349 window: &mut Window,
16350 cx: &mut Context<Self>,
16351 ) -> Task<Result<Navigated>> {
16352 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16353 }
16354
16355 pub fn go_to_definition_split(
16356 &mut self,
16357 _: &GoToDefinitionSplit,
16358 window: &mut Window,
16359 cx: &mut Context<Self>,
16360 ) -> Task<Result<Navigated>> {
16361 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16362 }
16363
16364 pub fn go_to_type_definition_split(
16365 &mut self,
16366 _: &GoToTypeDefinitionSplit,
16367 window: &mut Window,
16368 cx: &mut Context<Self>,
16369 ) -> Task<Result<Navigated>> {
16370 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16371 }
16372
16373 fn go_to_definition_of_kind(
16374 &mut self,
16375 kind: GotoDefinitionKind,
16376 split: bool,
16377 window: &mut Window,
16378 cx: &mut Context<Self>,
16379 ) -> Task<Result<Navigated>> {
16380 let Some(provider) = self.semantics_provider.clone() else {
16381 return Task::ready(Ok(Navigated::No));
16382 };
16383 let head = self.selections.newest::<usize>(cx).head();
16384 let buffer = self.buffer.read(cx);
16385 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16386 return Task::ready(Ok(Navigated::No));
16387 };
16388 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16389 return Task::ready(Ok(Navigated::No));
16390 };
16391
16392 cx.spawn_in(window, async move |editor, cx| {
16393 let Some(definitions) = definitions.await? else {
16394 return Ok(Navigated::No);
16395 };
16396 let navigated = editor
16397 .update_in(cx, |editor, window, cx| {
16398 editor.navigate_to_hover_links(
16399 Some(kind),
16400 definitions
16401 .into_iter()
16402 .filter(|location| {
16403 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16404 })
16405 .map(HoverLink::Text)
16406 .collect::<Vec<_>>(),
16407 split,
16408 window,
16409 cx,
16410 )
16411 })?
16412 .await?;
16413 anyhow::Ok(navigated)
16414 })
16415 }
16416
16417 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16418 let selection = self.selections.newest_anchor();
16419 let head = selection.head();
16420 let tail = selection.tail();
16421
16422 let Some((buffer, start_position)) =
16423 self.buffer.read(cx).text_anchor_for_position(head, cx)
16424 else {
16425 return;
16426 };
16427
16428 let end_position = if head != tail {
16429 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16430 return;
16431 };
16432 Some(pos)
16433 } else {
16434 None
16435 };
16436
16437 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16438 let url = if let Some(end_pos) = end_position {
16439 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16440 } else {
16441 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16442 };
16443
16444 if let Some(url) = url {
16445 cx.update(|window, cx| {
16446 if parse_zed_link(&url, cx).is_some() {
16447 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16448 } else {
16449 cx.open_url(&url);
16450 }
16451 })?;
16452 }
16453
16454 anyhow::Ok(())
16455 });
16456
16457 url_finder.detach();
16458 }
16459
16460 pub fn open_selected_filename(
16461 &mut self,
16462 _: &OpenSelectedFilename,
16463 window: &mut Window,
16464 cx: &mut Context<Self>,
16465 ) {
16466 let Some(workspace) = self.workspace() else {
16467 return;
16468 };
16469
16470 let position = self.selections.newest_anchor().head();
16471
16472 let Some((buffer, buffer_position)) =
16473 self.buffer.read(cx).text_anchor_for_position(position, cx)
16474 else {
16475 return;
16476 };
16477
16478 let project = self.project.clone();
16479
16480 cx.spawn_in(window, async move |_, cx| {
16481 let result = find_file(&buffer, project, buffer_position, cx).await;
16482
16483 if let Some((_, path)) = result {
16484 workspace
16485 .update_in(cx, |workspace, window, cx| {
16486 workspace.open_resolved_path(path, window, cx)
16487 })?
16488 .await?;
16489 }
16490 anyhow::Ok(())
16491 })
16492 .detach();
16493 }
16494
16495 pub(crate) fn navigate_to_hover_links(
16496 &mut self,
16497 kind: Option<GotoDefinitionKind>,
16498 definitions: Vec<HoverLink>,
16499 split: bool,
16500 window: &mut Window,
16501 cx: &mut Context<Editor>,
16502 ) -> Task<Result<Navigated>> {
16503 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16504 let mut first_url_or_file = None;
16505 let definitions: Vec<_> = definitions
16506 .into_iter()
16507 .filter_map(|def| match def {
16508 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16509 HoverLink::InlayHint(lsp_location, server_id) => {
16510 let computation =
16511 self.compute_target_location(lsp_location, server_id, window, cx);
16512 Some(cx.background_spawn(computation))
16513 }
16514 HoverLink::Url(url) => {
16515 first_url_or_file = Some(Either::Left(url));
16516 None
16517 }
16518 HoverLink::File(path) => {
16519 first_url_or_file = Some(Either::Right(path));
16520 None
16521 }
16522 })
16523 .collect();
16524
16525 let workspace = self.workspace();
16526
16527 cx.spawn_in(window, async move |editor, cx| {
16528 let locations: Vec<Location> = future::join_all(definitions)
16529 .await
16530 .into_iter()
16531 .filter_map(|location| location.transpose())
16532 .collect::<Result<_>>()
16533 .context("location tasks")?;
16534 let mut locations = cx.update(|_, cx| {
16535 locations
16536 .into_iter()
16537 .map(|location| {
16538 let buffer = location.buffer.read(cx);
16539 (location.buffer, location.range.to_point(buffer))
16540 })
16541 .into_group_map()
16542 })?;
16543 let mut num_locations = 0;
16544 for ranges in locations.values_mut() {
16545 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16546 ranges.dedup();
16547 num_locations += ranges.len();
16548 }
16549
16550 if num_locations > 1 {
16551 let Some(workspace) = workspace else {
16552 return Ok(Navigated::No);
16553 };
16554
16555 let tab_kind = match kind {
16556 Some(GotoDefinitionKind::Implementation) => "Implementations",
16557 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16558 Some(GotoDefinitionKind::Declaration) => "Declarations",
16559 Some(GotoDefinitionKind::Type) => "Types",
16560 };
16561 let title = editor
16562 .update_in(cx, |_, _, cx| {
16563 let target = locations
16564 .iter()
16565 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16566 .map(|(buffer, location)| {
16567 buffer
16568 .read(cx)
16569 .text_for_range(location.clone())
16570 .collect::<String>()
16571 })
16572 .filter(|text| !text.contains('\n'))
16573 .unique()
16574 .take(3)
16575 .join(", ");
16576 if target.is_empty() {
16577 tab_kind.to_owned()
16578 } else {
16579 format!("{tab_kind} for {target}")
16580 }
16581 })
16582 .context("buffer title")?;
16583
16584 let opened = workspace
16585 .update_in(cx, |workspace, window, cx| {
16586 Self::open_locations_in_multibuffer(
16587 workspace,
16588 locations,
16589 title,
16590 split,
16591 MultibufferSelectionMode::First,
16592 window,
16593 cx,
16594 )
16595 })
16596 .is_ok();
16597
16598 anyhow::Ok(Navigated::from_bool(opened))
16599 } else if num_locations == 0 {
16600 // If there is one url or file, open it directly
16601 match first_url_or_file {
16602 Some(Either::Left(url)) => {
16603 cx.update(|_, cx| cx.open_url(&url))?;
16604 Ok(Navigated::Yes)
16605 }
16606 Some(Either::Right(path)) => {
16607 let Some(workspace) = workspace else {
16608 return Ok(Navigated::No);
16609 };
16610
16611 workspace
16612 .update_in(cx, |workspace, window, cx| {
16613 workspace.open_resolved_path(path, window, cx)
16614 })?
16615 .await?;
16616 Ok(Navigated::Yes)
16617 }
16618 None => Ok(Navigated::No),
16619 }
16620 } else {
16621 let Some(workspace) = workspace else {
16622 return Ok(Navigated::No);
16623 };
16624
16625 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16626 let target_range = target_ranges.first().unwrap().clone();
16627
16628 editor.update_in(cx, |editor, window, cx| {
16629 let range = target_range.to_point(target_buffer.read(cx));
16630 let range = editor.range_for_match(&range);
16631 let range = collapse_multiline_range(range);
16632
16633 if !split
16634 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16635 {
16636 editor.go_to_singleton_buffer_range(range, window, cx);
16637 } else {
16638 let pane = workspace.read(cx).active_pane().clone();
16639 window.defer(cx, move |window, cx| {
16640 let target_editor: Entity<Self> =
16641 workspace.update(cx, |workspace, cx| {
16642 let pane = if split {
16643 workspace.adjacent_pane(window, cx)
16644 } else {
16645 workspace.active_pane().clone()
16646 };
16647
16648 workspace.open_project_item(
16649 pane,
16650 target_buffer.clone(),
16651 true,
16652 true,
16653 window,
16654 cx,
16655 )
16656 });
16657 target_editor.update(cx, |target_editor, cx| {
16658 // When selecting a definition in a different buffer, disable the nav history
16659 // to avoid creating a history entry at the previous cursor location.
16660 pane.update(cx, |pane, _| pane.disable_history());
16661 target_editor.go_to_singleton_buffer_range(range, window, cx);
16662 pane.update(cx, |pane, _| pane.enable_history());
16663 });
16664 });
16665 }
16666 Navigated::Yes
16667 })
16668 }
16669 })
16670 }
16671
16672 fn compute_target_location(
16673 &self,
16674 lsp_location: lsp::Location,
16675 server_id: LanguageServerId,
16676 window: &mut Window,
16677 cx: &mut Context<Self>,
16678 ) -> Task<anyhow::Result<Option<Location>>> {
16679 let Some(project) = self.project.clone() else {
16680 return Task::ready(Ok(None));
16681 };
16682
16683 cx.spawn_in(window, async move |editor, cx| {
16684 let location_task = editor.update(cx, |_, cx| {
16685 project.update(cx, |project, cx| {
16686 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16687 })
16688 })?;
16689 let location = Some({
16690 let target_buffer_handle = location_task.await.context("open local buffer")?;
16691 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16692 let target_start = target_buffer
16693 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16694 let target_end = target_buffer
16695 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16696 target_buffer.anchor_after(target_start)
16697 ..target_buffer.anchor_before(target_end)
16698 })?;
16699 Location {
16700 buffer: target_buffer_handle,
16701 range,
16702 }
16703 });
16704 Ok(location)
16705 })
16706 }
16707
16708 pub fn find_all_references(
16709 &mut self,
16710 _: &FindAllReferences,
16711 window: &mut Window,
16712 cx: &mut Context<Self>,
16713 ) -> Option<Task<Result<Navigated>>> {
16714 let selection = self.selections.newest::<usize>(cx);
16715 let multi_buffer = self.buffer.read(cx);
16716 let head = selection.head();
16717
16718 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16719 let head_anchor = multi_buffer_snapshot.anchor_at(
16720 head,
16721 if head < selection.tail() {
16722 Bias::Right
16723 } else {
16724 Bias::Left
16725 },
16726 );
16727
16728 match self
16729 .find_all_references_task_sources
16730 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16731 {
16732 Ok(_) => {
16733 log::info!(
16734 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16735 );
16736 return None;
16737 }
16738 Err(i) => {
16739 self.find_all_references_task_sources.insert(i, head_anchor);
16740 }
16741 }
16742
16743 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16744 let workspace = self.workspace()?;
16745 let project = workspace.read(cx).project().clone();
16746 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16747 Some(cx.spawn_in(window, async move |editor, cx| {
16748 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16749 if let Ok(i) = editor
16750 .find_all_references_task_sources
16751 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16752 {
16753 editor.find_all_references_task_sources.remove(i);
16754 }
16755 });
16756
16757 let Some(locations) = references.await? else {
16758 return anyhow::Ok(Navigated::No);
16759 };
16760 let mut locations = cx.update(|_, cx| {
16761 locations
16762 .into_iter()
16763 .map(|location| {
16764 let buffer = location.buffer.read(cx);
16765 (location.buffer, location.range.to_point(buffer))
16766 })
16767 .into_group_map()
16768 })?;
16769 if locations.is_empty() {
16770 return anyhow::Ok(Navigated::No);
16771 }
16772 for ranges in locations.values_mut() {
16773 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16774 ranges.dedup();
16775 }
16776
16777 workspace.update_in(cx, |workspace, window, cx| {
16778 let target = locations
16779 .iter()
16780 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16781 .map(|(buffer, location)| {
16782 buffer
16783 .read(cx)
16784 .text_for_range(location.clone())
16785 .collect::<String>()
16786 })
16787 .filter(|text| !text.contains('\n'))
16788 .unique()
16789 .take(3)
16790 .join(", ");
16791 let title = if target.is_empty() {
16792 "References".to_owned()
16793 } else {
16794 format!("References to {target}")
16795 };
16796 Self::open_locations_in_multibuffer(
16797 workspace,
16798 locations,
16799 title,
16800 false,
16801 MultibufferSelectionMode::First,
16802 window,
16803 cx,
16804 );
16805 Navigated::Yes
16806 })
16807 }))
16808 }
16809
16810 /// Opens a multibuffer with the given project locations in it
16811 pub fn open_locations_in_multibuffer(
16812 workspace: &mut Workspace,
16813 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16814 title: String,
16815 split: bool,
16816 multibuffer_selection_mode: MultibufferSelectionMode,
16817 window: &mut Window,
16818 cx: &mut Context<Workspace>,
16819 ) {
16820 if locations.is_empty() {
16821 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16822 return;
16823 }
16824
16825 let capability = workspace.project().read(cx).capability();
16826 let mut ranges = <Vec<Range<Anchor>>>::new();
16827
16828 // a key to find existing multibuffer editors with the same set of locations
16829 // to prevent us from opening more and more multibuffer tabs for searches and the like
16830 let mut key = (title.clone(), vec![]);
16831 let excerpt_buffer = cx.new(|cx| {
16832 let key = &mut key.1;
16833 let mut multibuffer = MultiBuffer::new(capability);
16834 for (buffer, mut ranges_for_buffer) in locations {
16835 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16836 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16837 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16838 PathKey::for_buffer(&buffer, cx),
16839 buffer.clone(),
16840 ranges_for_buffer,
16841 multibuffer_context_lines(cx),
16842 cx,
16843 );
16844 ranges.extend(new_ranges)
16845 }
16846
16847 multibuffer.with_title(title)
16848 });
16849 let existing = workspace.active_pane().update(cx, |pane, cx| {
16850 pane.items()
16851 .filter_map(|item| item.downcast::<Editor>())
16852 .find(|editor| {
16853 editor
16854 .read(cx)
16855 .lookup_key
16856 .as_ref()
16857 .and_then(|it| {
16858 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16859 })
16860 .is_some_and(|it| *it == key)
16861 })
16862 });
16863 let editor = existing.unwrap_or_else(|| {
16864 cx.new(|cx| {
16865 let mut editor = Editor::for_multibuffer(
16866 excerpt_buffer,
16867 Some(workspace.project().clone()),
16868 window,
16869 cx,
16870 );
16871 editor.lookup_key = Some(Box::new(key));
16872 editor
16873 })
16874 });
16875 editor.update(cx, |editor, cx| {
16876 match multibuffer_selection_mode {
16877 MultibufferSelectionMode::First => {
16878 if let Some(first_range) = ranges.first() {
16879 editor.change_selections(
16880 SelectionEffects::no_scroll(),
16881 window,
16882 cx,
16883 |selections| {
16884 selections.clear_disjoint();
16885 selections
16886 .select_anchor_ranges(std::iter::once(first_range.clone()));
16887 },
16888 );
16889 }
16890 editor.highlight_background::<Self>(
16891 &ranges,
16892 |theme| theme.colors().editor_highlighted_line_background,
16893 cx,
16894 );
16895 }
16896 MultibufferSelectionMode::All => {
16897 editor.change_selections(
16898 SelectionEffects::no_scroll(),
16899 window,
16900 cx,
16901 |selections| {
16902 selections.clear_disjoint();
16903 selections.select_anchor_ranges(ranges);
16904 },
16905 );
16906 }
16907 }
16908 editor.register_buffers_with_language_servers(cx);
16909 });
16910
16911 let item = Box::new(editor);
16912 let item_id = item.item_id();
16913
16914 if split {
16915 let pane = workspace.adjacent_pane(window, cx);
16916 workspace.add_item(pane, item, None, true, true, window, cx);
16917 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16918 let (preview_item_id, preview_item_idx) =
16919 workspace.active_pane().read_with(cx, |pane, _| {
16920 (pane.preview_item_id(), pane.preview_item_idx())
16921 });
16922
16923 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16924
16925 if let Some(preview_item_id) = preview_item_id {
16926 workspace.active_pane().update(cx, |pane, cx| {
16927 pane.remove_item(preview_item_id, false, false, window, cx);
16928 });
16929 }
16930 } else {
16931 workspace.add_item_to_active_pane(item, None, true, window, cx);
16932 }
16933 workspace.active_pane().update(cx, |pane, cx| {
16934 pane.set_preview_item_id(Some(item_id), cx);
16935 });
16936 }
16937
16938 pub fn rename(
16939 &mut self,
16940 _: &Rename,
16941 window: &mut Window,
16942 cx: &mut Context<Self>,
16943 ) -> Option<Task<Result<()>>> {
16944 use language::ToOffset as _;
16945
16946 let provider = self.semantics_provider.clone()?;
16947 let selection = self.selections.newest_anchor().clone();
16948 let (cursor_buffer, cursor_buffer_position) = self
16949 .buffer
16950 .read(cx)
16951 .text_anchor_for_position(selection.head(), cx)?;
16952 let (tail_buffer, cursor_buffer_position_end) = self
16953 .buffer
16954 .read(cx)
16955 .text_anchor_for_position(selection.tail(), cx)?;
16956 if tail_buffer != cursor_buffer {
16957 return None;
16958 }
16959
16960 let snapshot = cursor_buffer.read(cx).snapshot();
16961 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16962 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16963 let prepare_rename = provider
16964 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16965 .unwrap_or_else(|| Task::ready(Ok(None)));
16966 drop(snapshot);
16967
16968 Some(cx.spawn_in(window, async move |this, cx| {
16969 let rename_range = if let Some(range) = prepare_rename.await? {
16970 Some(range)
16971 } else {
16972 this.update(cx, |this, cx| {
16973 let buffer = this.buffer.read(cx).snapshot(cx);
16974 let mut buffer_highlights = this
16975 .document_highlights_for_position(selection.head(), &buffer)
16976 .filter(|highlight| {
16977 highlight.start.excerpt_id == selection.head().excerpt_id
16978 && highlight.end.excerpt_id == selection.head().excerpt_id
16979 });
16980 buffer_highlights
16981 .next()
16982 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16983 })?
16984 };
16985 if let Some(rename_range) = rename_range {
16986 this.update_in(cx, |this, window, cx| {
16987 let snapshot = cursor_buffer.read(cx).snapshot();
16988 let rename_buffer_range = rename_range.to_offset(&snapshot);
16989 let cursor_offset_in_rename_range =
16990 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16991 let cursor_offset_in_rename_range_end =
16992 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16993
16994 this.take_rename(false, window, cx);
16995 let buffer = this.buffer.read(cx).read(cx);
16996 let cursor_offset = selection.head().to_offset(&buffer);
16997 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16998 let rename_end = rename_start + rename_buffer_range.len();
16999 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17000 let mut old_highlight_id = None;
17001 let old_name: Arc<str> = buffer
17002 .chunks(rename_start..rename_end, true)
17003 .map(|chunk| {
17004 if old_highlight_id.is_none() {
17005 old_highlight_id = chunk.syntax_highlight_id;
17006 }
17007 chunk.text
17008 })
17009 .collect::<String>()
17010 .into();
17011
17012 drop(buffer);
17013
17014 // Position the selection in the rename editor so that it matches the current selection.
17015 this.show_local_selections = false;
17016 let rename_editor = cx.new(|cx| {
17017 let mut editor = Editor::single_line(window, cx);
17018 editor.buffer.update(cx, |buffer, cx| {
17019 buffer.edit([(0..0, old_name.clone())], None, cx)
17020 });
17021 let rename_selection_range = match cursor_offset_in_rename_range
17022 .cmp(&cursor_offset_in_rename_range_end)
17023 {
17024 Ordering::Equal => {
17025 editor.select_all(&SelectAll, window, cx);
17026 return editor;
17027 }
17028 Ordering::Less => {
17029 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17030 }
17031 Ordering::Greater => {
17032 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17033 }
17034 };
17035 if rename_selection_range.end > old_name.len() {
17036 editor.select_all(&SelectAll, window, cx);
17037 } else {
17038 editor.change_selections(Default::default(), window, cx, |s| {
17039 s.select_ranges([rename_selection_range]);
17040 });
17041 }
17042 editor
17043 });
17044 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17045 if e == &EditorEvent::Focused {
17046 cx.emit(EditorEvent::FocusedIn)
17047 }
17048 })
17049 .detach();
17050
17051 let write_highlights =
17052 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17053 let read_highlights =
17054 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17055 let ranges = write_highlights
17056 .iter()
17057 .flat_map(|(_, ranges)| ranges.iter())
17058 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17059 .cloned()
17060 .collect();
17061
17062 this.highlight_text::<Rename>(
17063 ranges,
17064 HighlightStyle {
17065 fade_out: Some(0.6),
17066 ..Default::default()
17067 },
17068 cx,
17069 );
17070 let rename_focus_handle = rename_editor.focus_handle(cx);
17071 window.focus(&rename_focus_handle);
17072 let block_id = this.insert_blocks(
17073 [BlockProperties {
17074 style: BlockStyle::Flex,
17075 placement: BlockPlacement::Below(range.start),
17076 height: Some(1),
17077 render: Arc::new({
17078 let rename_editor = rename_editor.clone();
17079 move |cx: &mut BlockContext| {
17080 let mut text_style = cx.editor_style.text.clone();
17081 if let Some(highlight_style) = old_highlight_id
17082 .and_then(|h| h.style(&cx.editor_style.syntax))
17083 {
17084 text_style = text_style.highlight(highlight_style);
17085 }
17086 div()
17087 .block_mouse_except_scroll()
17088 .pl(cx.anchor_x)
17089 .child(EditorElement::new(
17090 &rename_editor,
17091 EditorStyle {
17092 background: cx.theme().system().transparent,
17093 local_player: cx.editor_style.local_player,
17094 text: text_style,
17095 scrollbar_width: cx.editor_style.scrollbar_width,
17096 syntax: cx.editor_style.syntax.clone(),
17097 status: cx.editor_style.status.clone(),
17098 inlay_hints_style: HighlightStyle {
17099 font_weight: Some(FontWeight::BOLD),
17100 ..make_inlay_hints_style(cx.app)
17101 },
17102 edit_prediction_styles: make_suggestion_styles(
17103 cx.app,
17104 ),
17105 ..EditorStyle::default()
17106 },
17107 ))
17108 .into_any_element()
17109 }
17110 }),
17111 priority: 0,
17112 }],
17113 Some(Autoscroll::fit()),
17114 cx,
17115 )[0];
17116 this.pending_rename = Some(RenameState {
17117 range,
17118 old_name,
17119 editor: rename_editor,
17120 block_id,
17121 });
17122 })?;
17123 }
17124
17125 Ok(())
17126 }))
17127 }
17128
17129 pub fn confirm_rename(
17130 &mut self,
17131 _: &ConfirmRename,
17132 window: &mut Window,
17133 cx: &mut Context<Self>,
17134 ) -> Option<Task<Result<()>>> {
17135 let rename = self.take_rename(false, window, cx)?;
17136 let workspace = self.workspace()?.downgrade();
17137 let (buffer, start) = self
17138 .buffer
17139 .read(cx)
17140 .text_anchor_for_position(rename.range.start, cx)?;
17141 let (end_buffer, _) = self
17142 .buffer
17143 .read(cx)
17144 .text_anchor_for_position(rename.range.end, cx)?;
17145 if buffer != end_buffer {
17146 return None;
17147 }
17148
17149 let old_name = rename.old_name;
17150 let new_name = rename.editor.read(cx).text(cx);
17151
17152 let rename = self.semantics_provider.as_ref()?.perform_rename(
17153 &buffer,
17154 start,
17155 new_name.clone(),
17156 cx,
17157 )?;
17158
17159 Some(cx.spawn_in(window, async move |editor, cx| {
17160 let project_transaction = rename.await?;
17161 Self::open_project_transaction(
17162 &editor,
17163 workspace,
17164 project_transaction,
17165 format!("Rename: {} → {}", old_name, new_name),
17166 cx,
17167 )
17168 .await?;
17169
17170 editor.update(cx, |editor, cx| {
17171 editor.refresh_document_highlights(cx);
17172 })?;
17173 Ok(())
17174 }))
17175 }
17176
17177 fn take_rename(
17178 &mut self,
17179 moving_cursor: bool,
17180 window: &mut Window,
17181 cx: &mut Context<Self>,
17182 ) -> Option<RenameState> {
17183 let rename = self.pending_rename.take()?;
17184 if rename.editor.focus_handle(cx).is_focused(window) {
17185 window.focus(&self.focus_handle);
17186 }
17187
17188 self.remove_blocks(
17189 [rename.block_id].into_iter().collect(),
17190 Some(Autoscroll::fit()),
17191 cx,
17192 );
17193 self.clear_highlights::<Rename>(cx);
17194 self.show_local_selections = true;
17195
17196 if moving_cursor {
17197 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17198 editor.selections.newest::<usize>(cx).head()
17199 });
17200
17201 // Update the selection to match the position of the selection inside
17202 // the rename editor.
17203 let snapshot = self.buffer.read(cx).read(cx);
17204 let rename_range = rename.range.to_offset(&snapshot);
17205 let cursor_in_editor = snapshot
17206 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17207 .min(rename_range.end);
17208 drop(snapshot);
17209
17210 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17211 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17212 });
17213 } else {
17214 self.refresh_document_highlights(cx);
17215 }
17216
17217 Some(rename)
17218 }
17219
17220 pub fn pending_rename(&self) -> Option<&RenameState> {
17221 self.pending_rename.as_ref()
17222 }
17223
17224 fn format(
17225 &mut self,
17226 _: &Format,
17227 window: &mut Window,
17228 cx: &mut Context<Self>,
17229 ) -> Option<Task<Result<()>>> {
17230 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17231
17232 let project = match &self.project {
17233 Some(project) => project.clone(),
17234 None => return None,
17235 };
17236
17237 Some(self.perform_format(
17238 project,
17239 FormatTrigger::Manual,
17240 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17241 window,
17242 cx,
17243 ))
17244 }
17245
17246 fn format_selections(
17247 &mut self,
17248 _: &FormatSelections,
17249 window: &mut Window,
17250 cx: &mut Context<Self>,
17251 ) -> Option<Task<Result<()>>> {
17252 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17253
17254 let project = match &self.project {
17255 Some(project) => project.clone(),
17256 None => return None,
17257 };
17258
17259 let ranges = self
17260 .selections
17261 .all_adjusted(cx)
17262 .into_iter()
17263 .map(|selection| selection.range())
17264 .collect_vec();
17265
17266 Some(self.perform_format(
17267 project,
17268 FormatTrigger::Manual,
17269 FormatTarget::Ranges(ranges),
17270 window,
17271 cx,
17272 ))
17273 }
17274
17275 fn perform_format(
17276 &mut self,
17277 project: Entity<Project>,
17278 trigger: FormatTrigger,
17279 target: FormatTarget,
17280 window: &mut Window,
17281 cx: &mut Context<Self>,
17282 ) -> Task<Result<()>> {
17283 let buffer = self.buffer.clone();
17284 let (buffers, target) = match target {
17285 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17286 FormatTarget::Ranges(selection_ranges) => {
17287 let multi_buffer = buffer.read(cx);
17288 let snapshot = multi_buffer.read(cx);
17289 let mut buffers = HashSet::default();
17290 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17291 BTreeMap::new();
17292 for selection_range in selection_ranges {
17293 for (buffer, buffer_range, _) in
17294 snapshot.range_to_buffer_ranges(selection_range)
17295 {
17296 let buffer_id = buffer.remote_id();
17297 let start = buffer.anchor_before(buffer_range.start);
17298 let end = buffer.anchor_after(buffer_range.end);
17299 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17300 buffer_id_to_ranges
17301 .entry(buffer_id)
17302 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17303 .or_insert_with(|| vec![start..end]);
17304 }
17305 }
17306 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17307 }
17308 };
17309
17310 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17311 let selections_prev = transaction_id_prev
17312 .and_then(|transaction_id_prev| {
17313 // default to selections as they were after the last edit, if we have them,
17314 // instead of how they are now.
17315 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17316 // will take you back to where you made the last edit, instead of staying where you scrolled
17317 self.selection_history
17318 .transaction(transaction_id_prev)
17319 .map(|t| t.0.clone())
17320 })
17321 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17322
17323 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17324 let format = project.update(cx, |project, cx| {
17325 project.format(buffers, target, true, trigger, cx)
17326 });
17327
17328 cx.spawn_in(window, async move |editor, cx| {
17329 let transaction = futures::select_biased! {
17330 transaction = format.log_err().fuse() => transaction,
17331 () = timeout => {
17332 log::warn!("timed out waiting for formatting");
17333 None
17334 }
17335 };
17336
17337 buffer
17338 .update(cx, |buffer, cx| {
17339 if let Some(transaction) = transaction
17340 && !buffer.is_singleton()
17341 {
17342 buffer.push_transaction(&transaction.0, cx);
17343 }
17344 cx.notify();
17345 })
17346 .ok();
17347
17348 if let Some(transaction_id_now) =
17349 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17350 {
17351 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17352 if has_new_transaction {
17353 _ = editor.update(cx, |editor, _| {
17354 editor
17355 .selection_history
17356 .insert_transaction(transaction_id_now, selections_prev);
17357 });
17358 }
17359 }
17360
17361 Ok(())
17362 })
17363 }
17364
17365 fn organize_imports(
17366 &mut self,
17367 _: &OrganizeImports,
17368 window: &mut Window,
17369 cx: &mut Context<Self>,
17370 ) -> Option<Task<Result<()>>> {
17371 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17372 let project = match &self.project {
17373 Some(project) => project.clone(),
17374 None => return None,
17375 };
17376 Some(self.perform_code_action_kind(
17377 project,
17378 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17379 window,
17380 cx,
17381 ))
17382 }
17383
17384 fn perform_code_action_kind(
17385 &mut self,
17386 project: Entity<Project>,
17387 kind: CodeActionKind,
17388 window: &mut Window,
17389 cx: &mut Context<Self>,
17390 ) -> Task<Result<()>> {
17391 let buffer = self.buffer.clone();
17392 let buffers = buffer.read(cx).all_buffers();
17393 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17394 let apply_action = project.update(cx, |project, cx| {
17395 project.apply_code_action_kind(buffers, kind, true, cx)
17396 });
17397 cx.spawn_in(window, async move |_, cx| {
17398 let transaction = futures::select_biased! {
17399 () = timeout => {
17400 log::warn!("timed out waiting for executing code action");
17401 None
17402 }
17403 transaction = apply_action.log_err().fuse() => transaction,
17404 };
17405 buffer
17406 .update(cx, |buffer, cx| {
17407 // check if we need this
17408 if let Some(transaction) = transaction
17409 && !buffer.is_singleton()
17410 {
17411 buffer.push_transaction(&transaction.0, cx);
17412 }
17413 cx.notify();
17414 })
17415 .ok();
17416 Ok(())
17417 })
17418 }
17419
17420 pub fn restart_language_server(
17421 &mut self,
17422 _: &RestartLanguageServer,
17423 _: &mut Window,
17424 cx: &mut Context<Self>,
17425 ) {
17426 if let Some(project) = self.project.clone() {
17427 self.buffer.update(cx, |multi_buffer, cx| {
17428 project.update(cx, |project, cx| {
17429 project.restart_language_servers_for_buffers(
17430 multi_buffer.all_buffers().into_iter().collect(),
17431 HashSet::default(),
17432 cx,
17433 );
17434 });
17435 })
17436 }
17437 }
17438
17439 pub fn stop_language_server(
17440 &mut self,
17441 _: &StopLanguageServer,
17442 _: &mut Window,
17443 cx: &mut Context<Self>,
17444 ) {
17445 if let Some(project) = self.project.clone() {
17446 self.buffer.update(cx, |multi_buffer, cx| {
17447 project.update(cx, |project, cx| {
17448 project.stop_language_servers_for_buffers(
17449 multi_buffer.all_buffers().into_iter().collect(),
17450 HashSet::default(),
17451 cx,
17452 );
17453 cx.emit(project::Event::RefreshInlayHints);
17454 });
17455 });
17456 }
17457 }
17458
17459 fn cancel_language_server_work(
17460 workspace: &mut Workspace,
17461 _: &actions::CancelLanguageServerWork,
17462 _: &mut Window,
17463 cx: &mut Context<Workspace>,
17464 ) {
17465 let project = workspace.project();
17466 let buffers = workspace
17467 .active_item(cx)
17468 .and_then(|item| item.act_as::<Editor>(cx))
17469 .map_or(HashSet::default(), |editor| {
17470 editor.read(cx).buffer.read(cx).all_buffers()
17471 });
17472 project.update(cx, |project, cx| {
17473 project.cancel_language_server_work_for_buffers(buffers, cx);
17474 });
17475 }
17476
17477 fn show_character_palette(
17478 &mut self,
17479 _: &ShowCharacterPalette,
17480 window: &mut Window,
17481 _: &mut Context<Self>,
17482 ) {
17483 window.show_character_palette();
17484 }
17485
17486 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17487 if !self.diagnostics_enabled() {
17488 return;
17489 }
17490
17491 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17492 let buffer = self.buffer.read(cx).snapshot(cx);
17493 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17494 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17495 let is_valid = buffer
17496 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17497 .any(|entry| {
17498 entry.diagnostic.is_primary
17499 && !entry.range.is_empty()
17500 && entry.range.start == primary_range_start
17501 && entry.diagnostic.message == active_diagnostics.active_message
17502 });
17503
17504 if !is_valid {
17505 self.dismiss_diagnostics(cx);
17506 }
17507 }
17508 }
17509
17510 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17511 match &self.active_diagnostics {
17512 ActiveDiagnostic::Group(group) => Some(group),
17513 _ => None,
17514 }
17515 }
17516
17517 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17518 if !self.diagnostics_enabled() {
17519 return;
17520 }
17521 self.dismiss_diagnostics(cx);
17522 self.active_diagnostics = ActiveDiagnostic::All;
17523 }
17524
17525 fn activate_diagnostics(
17526 &mut self,
17527 buffer_id: BufferId,
17528 diagnostic: DiagnosticEntryRef<'_, usize>,
17529 window: &mut Window,
17530 cx: &mut Context<Self>,
17531 ) {
17532 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17533 return;
17534 }
17535 self.dismiss_diagnostics(cx);
17536 let snapshot = self.snapshot(window, cx);
17537 let buffer = self.buffer.read(cx).snapshot(cx);
17538 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17539 return;
17540 };
17541
17542 let diagnostic_group = buffer
17543 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17544 .collect::<Vec<_>>();
17545
17546 let blocks =
17547 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17548
17549 let blocks = self.display_map.update(cx, |display_map, cx| {
17550 display_map.insert_blocks(blocks, cx).into_iter().collect()
17551 });
17552 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17553 active_range: buffer.anchor_before(diagnostic.range.start)
17554 ..buffer.anchor_after(diagnostic.range.end),
17555 active_message: diagnostic.diagnostic.message.clone(),
17556 group_id: diagnostic.diagnostic.group_id,
17557 blocks,
17558 });
17559 cx.notify();
17560 }
17561
17562 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17563 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17564 return;
17565 };
17566
17567 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17568 if let ActiveDiagnostic::Group(group) = prev {
17569 self.display_map.update(cx, |display_map, cx| {
17570 display_map.remove_blocks(group.blocks, cx);
17571 });
17572 cx.notify();
17573 }
17574 }
17575
17576 /// Disable inline diagnostics rendering for this editor.
17577 pub fn disable_inline_diagnostics(&mut self) {
17578 self.inline_diagnostics_enabled = false;
17579 self.inline_diagnostics_update = Task::ready(());
17580 self.inline_diagnostics.clear();
17581 }
17582
17583 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17584 self.diagnostics_enabled = false;
17585 self.dismiss_diagnostics(cx);
17586 self.inline_diagnostics_update = Task::ready(());
17587 self.inline_diagnostics.clear();
17588 }
17589
17590 pub fn disable_word_completions(&mut self) {
17591 self.word_completions_enabled = false;
17592 }
17593
17594 pub fn diagnostics_enabled(&self) -> bool {
17595 self.diagnostics_enabled && self.mode.is_full()
17596 }
17597
17598 pub fn inline_diagnostics_enabled(&self) -> bool {
17599 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17600 }
17601
17602 pub fn show_inline_diagnostics(&self) -> bool {
17603 self.show_inline_diagnostics
17604 }
17605
17606 pub fn toggle_inline_diagnostics(
17607 &mut self,
17608 _: &ToggleInlineDiagnostics,
17609 window: &mut Window,
17610 cx: &mut Context<Editor>,
17611 ) {
17612 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17613 self.refresh_inline_diagnostics(false, window, cx);
17614 }
17615
17616 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17617 self.diagnostics_max_severity = severity;
17618 self.display_map.update(cx, |display_map, _| {
17619 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17620 });
17621 }
17622
17623 pub fn toggle_diagnostics(
17624 &mut self,
17625 _: &ToggleDiagnostics,
17626 window: &mut Window,
17627 cx: &mut Context<Editor>,
17628 ) {
17629 if !self.diagnostics_enabled() {
17630 return;
17631 }
17632
17633 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17634 EditorSettings::get_global(cx)
17635 .diagnostics_max_severity
17636 .filter(|severity| severity != &DiagnosticSeverity::Off)
17637 .unwrap_or(DiagnosticSeverity::Hint)
17638 } else {
17639 DiagnosticSeverity::Off
17640 };
17641 self.set_max_diagnostics_severity(new_severity, cx);
17642 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17643 self.active_diagnostics = ActiveDiagnostic::None;
17644 self.inline_diagnostics_update = Task::ready(());
17645 self.inline_diagnostics.clear();
17646 } else {
17647 self.refresh_inline_diagnostics(false, window, cx);
17648 }
17649
17650 cx.notify();
17651 }
17652
17653 pub fn toggle_minimap(
17654 &mut self,
17655 _: &ToggleMinimap,
17656 window: &mut Window,
17657 cx: &mut Context<Editor>,
17658 ) {
17659 if self.supports_minimap(cx) {
17660 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17661 }
17662 }
17663
17664 fn refresh_inline_diagnostics(
17665 &mut self,
17666 debounce: bool,
17667 window: &mut Window,
17668 cx: &mut Context<Self>,
17669 ) {
17670 let max_severity = ProjectSettings::get_global(cx)
17671 .diagnostics
17672 .inline
17673 .max_severity
17674 .unwrap_or(self.diagnostics_max_severity);
17675
17676 if !self.inline_diagnostics_enabled()
17677 || !self.show_inline_diagnostics
17678 || max_severity == DiagnosticSeverity::Off
17679 {
17680 self.inline_diagnostics_update = Task::ready(());
17681 self.inline_diagnostics.clear();
17682 return;
17683 }
17684
17685 let debounce_ms = ProjectSettings::get_global(cx)
17686 .diagnostics
17687 .inline
17688 .update_debounce_ms;
17689 let debounce = if debounce && debounce_ms > 0 {
17690 Some(Duration::from_millis(debounce_ms))
17691 } else {
17692 None
17693 };
17694 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17695 if let Some(debounce) = debounce {
17696 cx.background_executor().timer(debounce).await;
17697 }
17698 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17699 editor
17700 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17701 .ok()
17702 }) else {
17703 return;
17704 };
17705
17706 let new_inline_diagnostics = cx
17707 .background_spawn(async move {
17708 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17709 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17710 let message = diagnostic_entry
17711 .diagnostic
17712 .message
17713 .split_once('\n')
17714 .map(|(line, _)| line)
17715 .map(SharedString::new)
17716 .unwrap_or_else(|| {
17717 SharedString::new(&*diagnostic_entry.diagnostic.message)
17718 });
17719 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17720 let (Ok(i) | Err(i)) = inline_diagnostics
17721 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17722 inline_diagnostics.insert(
17723 i,
17724 (
17725 start_anchor,
17726 InlineDiagnostic {
17727 message,
17728 group_id: diagnostic_entry.diagnostic.group_id,
17729 start: diagnostic_entry.range.start.to_point(&snapshot),
17730 is_primary: diagnostic_entry.diagnostic.is_primary,
17731 severity: diagnostic_entry.diagnostic.severity,
17732 },
17733 ),
17734 );
17735 }
17736 inline_diagnostics
17737 })
17738 .await;
17739
17740 editor
17741 .update(cx, |editor, cx| {
17742 editor.inline_diagnostics = new_inline_diagnostics;
17743 cx.notify();
17744 })
17745 .ok();
17746 });
17747 }
17748
17749 fn pull_diagnostics(
17750 &mut self,
17751 buffer_id: Option<BufferId>,
17752 window: &Window,
17753 cx: &mut Context<Self>,
17754 ) -> Option<()> {
17755 if !self.mode().is_full() {
17756 return None;
17757 }
17758 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17759 .diagnostics
17760 .lsp_pull_diagnostics;
17761 if !pull_diagnostics_settings.enabled {
17762 return None;
17763 }
17764 let project = self.project()?.downgrade();
17765 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17766 let mut buffers = self.buffer.read(cx).all_buffers();
17767 if let Some(buffer_id) = buffer_id {
17768 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17769 }
17770
17771 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17772 cx.background_executor().timer(debounce).await;
17773
17774 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17775 buffers
17776 .into_iter()
17777 .filter_map(|buffer| {
17778 project
17779 .update(cx, |project, cx| {
17780 project.lsp_store().update(cx, |lsp_store, cx| {
17781 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17782 })
17783 })
17784 .ok()
17785 })
17786 .collect::<FuturesUnordered<_>>()
17787 }) else {
17788 return;
17789 };
17790
17791 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17792 match pull_task {
17793 Ok(()) => {
17794 if editor
17795 .update_in(cx, |editor, window, cx| {
17796 editor.update_diagnostics_state(window, cx);
17797 })
17798 .is_err()
17799 {
17800 return;
17801 }
17802 }
17803 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17804 }
17805 }
17806 });
17807
17808 Some(())
17809 }
17810
17811 pub fn set_selections_from_remote(
17812 &mut self,
17813 selections: Vec<Selection<Anchor>>,
17814 pending_selection: Option<Selection<Anchor>>,
17815 window: &mut Window,
17816 cx: &mut Context<Self>,
17817 ) {
17818 let old_cursor_position = self.selections.newest_anchor().head();
17819 self.selections.change_with(cx, |s| {
17820 s.select_anchors(selections);
17821 if let Some(pending_selection) = pending_selection {
17822 s.set_pending(pending_selection, SelectMode::Character);
17823 } else {
17824 s.clear_pending();
17825 }
17826 });
17827 self.selections_did_change(
17828 false,
17829 &old_cursor_position,
17830 SelectionEffects::default(),
17831 window,
17832 cx,
17833 );
17834 }
17835
17836 pub fn transact(
17837 &mut self,
17838 window: &mut Window,
17839 cx: &mut Context<Self>,
17840 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17841 ) -> Option<TransactionId> {
17842 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17843 this.start_transaction_at(Instant::now(), window, cx);
17844 update(this, window, cx);
17845 this.end_transaction_at(Instant::now(), cx)
17846 })
17847 }
17848
17849 pub fn start_transaction_at(
17850 &mut self,
17851 now: Instant,
17852 window: &mut Window,
17853 cx: &mut Context<Self>,
17854 ) -> Option<TransactionId> {
17855 self.end_selection(window, cx);
17856 if let Some(tx_id) = self
17857 .buffer
17858 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17859 {
17860 self.selection_history
17861 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17862 cx.emit(EditorEvent::TransactionBegun {
17863 transaction_id: tx_id,
17864 });
17865 Some(tx_id)
17866 } else {
17867 None
17868 }
17869 }
17870
17871 pub fn end_transaction_at(
17872 &mut self,
17873 now: Instant,
17874 cx: &mut Context<Self>,
17875 ) -> Option<TransactionId> {
17876 if let Some(transaction_id) = self
17877 .buffer
17878 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17879 {
17880 if let Some((_, end_selections)) =
17881 self.selection_history.transaction_mut(transaction_id)
17882 {
17883 *end_selections = Some(self.selections.disjoint_anchors_arc());
17884 } else {
17885 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17886 }
17887
17888 cx.emit(EditorEvent::Edited { transaction_id });
17889 Some(transaction_id)
17890 } else {
17891 None
17892 }
17893 }
17894
17895 pub fn modify_transaction_selection_history(
17896 &mut self,
17897 transaction_id: TransactionId,
17898 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17899 ) -> bool {
17900 self.selection_history
17901 .transaction_mut(transaction_id)
17902 .map(modify)
17903 .is_some()
17904 }
17905
17906 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17907 if self.selection_mark_mode {
17908 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17909 s.move_with(|_, sel| {
17910 sel.collapse_to(sel.head(), SelectionGoal::None);
17911 });
17912 })
17913 }
17914 self.selection_mark_mode = true;
17915 cx.notify();
17916 }
17917
17918 pub fn swap_selection_ends(
17919 &mut self,
17920 _: &actions::SwapSelectionEnds,
17921 window: &mut Window,
17922 cx: &mut Context<Self>,
17923 ) {
17924 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17925 s.move_with(|_, sel| {
17926 if sel.start != sel.end {
17927 sel.reversed = !sel.reversed
17928 }
17929 });
17930 });
17931 self.request_autoscroll(Autoscroll::newest(), cx);
17932 cx.notify();
17933 }
17934
17935 pub fn toggle_focus(
17936 workspace: &mut Workspace,
17937 _: &actions::ToggleFocus,
17938 window: &mut Window,
17939 cx: &mut Context<Workspace>,
17940 ) {
17941 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17942 return;
17943 };
17944 workspace.activate_item(&item, true, true, window, cx);
17945 }
17946
17947 pub fn toggle_fold(
17948 &mut self,
17949 _: &actions::ToggleFold,
17950 window: &mut Window,
17951 cx: &mut Context<Self>,
17952 ) {
17953 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
17954 let selection = self.selections.newest::<Point>(cx);
17955
17956 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17957 let range = if selection.is_empty() {
17958 let point = selection.head().to_display_point(&display_map);
17959 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17960 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17961 .to_point(&display_map);
17962 start..end
17963 } else {
17964 selection.range()
17965 };
17966 if display_map.folds_in_range(range).next().is_some() {
17967 self.unfold_lines(&Default::default(), window, cx)
17968 } else {
17969 self.fold(&Default::default(), window, cx)
17970 }
17971 } else {
17972 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17973 let buffer_ids: HashSet<_> = self
17974 .selections
17975 .disjoint_anchor_ranges()
17976 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17977 .collect();
17978
17979 let should_unfold = buffer_ids
17980 .iter()
17981 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17982
17983 for buffer_id in buffer_ids {
17984 if should_unfold {
17985 self.unfold_buffer(buffer_id, cx);
17986 } else {
17987 self.fold_buffer(buffer_id, cx);
17988 }
17989 }
17990 }
17991 }
17992
17993 pub fn toggle_fold_recursive(
17994 &mut self,
17995 _: &actions::ToggleFoldRecursive,
17996 window: &mut Window,
17997 cx: &mut Context<Self>,
17998 ) {
17999 let selection = self.selections.newest::<Point>(cx);
18000
18001 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18002 let range = if selection.is_empty() {
18003 let point = selection.head().to_display_point(&display_map);
18004 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18005 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18006 .to_point(&display_map);
18007 start..end
18008 } else {
18009 selection.range()
18010 };
18011 if display_map.folds_in_range(range).next().is_some() {
18012 self.unfold_recursive(&Default::default(), window, cx)
18013 } else {
18014 self.fold_recursive(&Default::default(), window, cx)
18015 }
18016 }
18017
18018 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18019 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18020 let mut to_fold = Vec::new();
18021 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18022 let selections = self.selections.all_adjusted(cx);
18023
18024 for selection in selections {
18025 let range = selection.range().sorted();
18026 let buffer_start_row = range.start.row;
18027
18028 if range.start.row != range.end.row {
18029 let mut found = false;
18030 let mut row = range.start.row;
18031 while row <= range.end.row {
18032 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18033 {
18034 found = true;
18035 row = crease.range().end.row + 1;
18036 to_fold.push(crease);
18037 } else {
18038 row += 1
18039 }
18040 }
18041 if found {
18042 continue;
18043 }
18044 }
18045
18046 for row in (0..=range.start.row).rev() {
18047 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18048 && crease.range().end.row >= buffer_start_row
18049 {
18050 to_fold.push(crease);
18051 if row <= range.start.row {
18052 break;
18053 }
18054 }
18055 }
18056 }
18057
18058 self.fold_creases(to_fold, true, window, cx);
18059 } else {
18060 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18061 let buffer_ids = self
18062 .selections
18063 .disjoint_anchor_ranges()
18064 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18065 .collect::<HashSet<_>>();
18066 for buffer_id in buffer_ids {
18067 self.fold_buffer(buffer_id, cx);
18068 }
18069 }
18070 }
18071
18072 pub fn toggle_fold_all(
18073 &mut self,
18074 _: &actions::ToggleFoldAll,
18075 window: &mut Window,
18076 cx: &mut Context<Self>,
18077 ) {
18078 if self.buffer.read(cx).is_singleton() {
18079 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18080 let has_folds = display_map
18081 .folds_in_range(0..display_map.buffer_snapshot.len())
18082 .next()
18083 .is_some();
18084
18085 if has_folds {
18086 self.unfold_all(&actions::UnfoldAll, window, cx);
18087 } else {
18088 self.fold_all(&actions::FoldAll, window, cx);
18089 }
18090 } else {
18091 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18092 let should_unfold = buffer_ids
18093 .iter()
18094 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18095
18096 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18097 editor
18098 .update_in(cx, |editor, _, cx| {
18099 for buffer_id in buffer_ids {
18100 if should_unfold {
18101 editor.unfold_buffer(buffer_id, cx);
18102 } else {
18103 editor.fold_buffer(buffer_id, cx);
18104 }
18105 }
18106 })
18107 .ok();
18108 });
18109 }
18110 }
18111
18112 fn fold_at_level(
18113 &mut self,
18114 fold_at: &FoldAtLevel,
18115 window: &mut Window,
18116 cx: &mut Context<Self>,
18117 ) {
18118 if !self.buffer.read(cx).is_singleton() {
18119 return;
18120 }
18121
18122 let fold_at_level = fold_at.0;
18123 let snapshot = self.buffer.read(cx).snapshot(cx);
18124 let mut to_fold = Vec::new();
18125 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18126
18127 let row_ranges_to_keep: Vec<Range<u32>> = self
18128 .selections
18129 .all::<Point>(cx)
18130 .into_iter()
18131 .map(|sel| sel.start.row..sel.end.row)
18132 .collect();
18133
18134 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18135 while start_row < end_row {
18136 match self
18137 .snapshot(window, cx)
18138 .crease_for_buffer_row(MultiBufferRow(start_row))
18139 {
18140 Some(crease) => {
18141 let nested_start_row = crease.range().start.row + 1;
18142 let nested_end_row = crease.range().end.row;
18143
18144 if current_level < fold_at_level {
18145 stack.push((nested_start_row, nested_end_row, current_level + 1));
18146 } else if current_level == fold_at_level {
18147 // Fold iff there is no selection completely contained within the fold region
18148 if !row_ranges_to_keep.iter().any(|selection| {
18149 selection.end >= nested_start_row
18150 && selection.start <= nested_end_row
18151 }) {
18152 to_fold.push(crease);
18153 }
18154 }
18155
18156 start_row = nested_end_row + 1;
18157 }
18158 None => start_row += 1,
18159 }
18160 }
18161 }
18162
18163 self.fold_creases(to_fold, true, window, cx);
18164 }
18165
18166 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18167 if self.buffer.read(cx).is_singleton() {
18168 let mut fold_ranges = Vec::new();
18169 let snapshot = self.buffer.read(cx).snapshot(cx);
18170
18171 for row in 0..snapshot.max_row().0 {
18172 if let Some(foldable_range) = self
18173 .snapshot(window, cx)
18174 .crease_for_buffer_row(MultiBufferRow(row))
18175 {
18176 fold_ranges.push(foldable_range);
18177 }
18178 }
18179
18180 self.fold_creases(fold_ranges, true, window, cx);
18181 } else {
18182 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18183 editor
18184 .update_in(cx, |editor, _, cx| {
18185 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18186 editor.fold_buffer(buffer_id, cx);
18187 }
18188 })
18189 .ok();
18190 });
18191 }
18192 }
18193
18194 pub fn fold_function_bodies(
18195 &mut self,
18196 _: &actions::FoldFunctionBodies,
18197 window: &mut Window,
18198 cx: &mut Context<Self>,
18199 ) {
18200 let snapshot = self.buffer.read(cx).snapshot(cx);
18201
18202 let ranges = snapshot
18203 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18204 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18205 .collect::<Vec<_>>();
18206
18207 let creases = ranges
18208 .into_iter()
18209 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18210 .collect();
18211
18212 self.fold_creases(creases, true, window, cx);
18213 }
18214
18215 pub fn fold_recursive(
18216 &mut self,
18217 _: &actions::FoldRecursive,
18218 window: &mut Window,
18219 cx: &mut Context<Self>,
18220 ) {
18221 let mut to_fold = Vec::new();
18222 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18223 let selections = self.selections.all_adjusted(cx);
18224
18225 for selection in selections {
18226 let range = selection.range().sorted();
18227 let buffer_start_row = range.start.row;
18228
18229 if range.start.row != range.end.row {
18230 let mut found = false;
18231 for row in range.start.row..=range.end.row {
18232 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18233 found = true;
18234 to_fold.push(crease);
18235 }
18236 }
18237 if found {
18238 continue;
18239 }
18240 }
18241
18242 for row in (0..=range.start.row).rev() {
18243 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18244 if crease.range().end.row >= buffer_start_row {
18245 to_fold.push(crease);
18246 } else {
18247 break;
18248 }
18249 }
18250 }
18251 }
18252
18253 self.fold_creases(to_fold, true, window, cx);
18254 }
18255
18256 pub fn fold_at(
18257 &mut self,
18258 buffer_row: MultiBufferRow,
18259 window: &mut Window,
18260 cx: &mut Context<Self>,
18261 ) {
18262 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18263
18264 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18265 let autoscroll = self
18266 .selections
18267 .all::<Point>(cx)
18268 .iter()
18269 .any(|selection| crease.range().overlaps(&selection.range()));
18270
18271 self.fold_creases(vec![crease], autoscroll, window, cx);
18272 }
18273 }
18274
18275 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18276 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18277 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18278 let buffer = &display_map.buffer_snapshot;
18279 let selections = self.selections.all::<Point>(cx);
18280 let ranges = selections
18281 .iter()
18282 .map(|s| {
18283 let range = s.display_range(&display_map).sorted();
18284 let mut start = range.start.to_point(&display_map);
18285 let mut end = range.end.to_point(&display_map);
18286 start.column = 0;
18287 end.column = buffer.line_len(MultiBufferRow(end.row));
18288 start..end
18289 })
18290 .collect::<Vec<_>>();
18291
18292 self.unfold_ranges(&ranges, true, true, cx);
18293 } else {
18294 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18295 let buffer_ids = self
18296 .selections
18297 .disjoint_anchor_ranges()
18298 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18299 .collect::<HashSet<_>>();
18300 for buffer_id in buffer_ids {
18301 self.unfold_buffer(buffer_id, cx);
18302 }
18303 }
18304 }
18305
18306 pub fn unfold_recursive(
18307 &mut self,
18308 _: &UnfoldRecursive,
18309 _window: &mut Window,
18310 cx: &mut Context<Self>,
18311 ) {
18312 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18313 let selections = self.selections.all::<Point>(cx);
18314 let ranges = selections
18315 .iter()
18316 .map(|s| {
18317 let mut range = s.display_range(&display_map).sorted();
18318 *range.start.column_mut() = 0;
18319 *range.end.column_mut() = display_map.line_len(range.end.row());
18320 let start = range.start.to_point(&display_map);
18321 let end = range.end.to_point(&display_map);
18322 start..end
18323 })
18324 .collect::<Vec<_>>();
18325
18326 self.unfold_ranges(&ranges, true, true, cx);
18327 }
18328
18329 pub fn unfold_at(
18330 &mut self,
18331 buffer_row: MultiBufferRow,
18332 _window: &mut Window,
18333 cx: &mut Context<Self>,
18334 ) {
18335 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18336
18337 let intersection_range = Point::new(buffer_row.0, 0)
18338 ..Point::new(
18339 buffer_row.0,
18340 display_map.buffer_snapshot.line_len(buffer_row),
18341 );
18342
18343 let autoscroll = self
18344 .selections
18345 .all::<Point>(cx)
18346 .iter()
18347 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18348
18349 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18350 }
18351
18352 pub fn unfold_all(
18353 &mut self,
18354 _: &actions::UnfoldAll,
18355 _window: &mut Window,
18356 cx: &mut Context<Self>,
18357 ) {
18358 if self.buffer.read(cx).is_singleton() {
18359 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18360 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18361 } else {
18362 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18363 editor
18364 .update(cx, |editor, cx| {
18365 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18366 editor.unfold_buffer(buffer_id, cx);
18367 }
18368 })
18369 .ok();
18370 });
18371 }
18372 }
18373
18374 pub fn fold_selected_ranges(
18375 &mut self,
18376 _: &FoldSelectedRanges,
18377 window: &mut Window,
18378 cx: &mut Context<Self>,
18379 ) {
18380 let selections = self.selections.all_adjusted(cx);
18381 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18382 let ranges = selections
18383 .into_iter()
18384 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18385 .collect::<Vec<_>>();
18386 self.fold_creases(ranges, true, window, cx);
18387 }
18388
18389 pub fn fold_ranges<T: ToOffset + Clone>(
18390 &mut self,
18391 ranges: Vec<Range<T>>,
18392 auto_scroll: bool,
18393 window: &mut Window,
18394 cx: &mut Context<Self>,
18395 ) {
18396 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18397 let ranges = ranges
18398 .into_iter()
18399 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18400 .collect::<Vec<_>>();
18401 self.fold_creases(ranges, auto_scroll, window, cx);
18402 }
18403
18404 pub fn fold_creases<T: ToOffset + Clone>(
18405 &mut self,
18406 creases: Vec<Crease<T>>,
18407 auto_scroll: bool,
18408 _window: &mut Window,
18409 cx: &mut Context<Self>,
18410 ) {
18411 if creases.is_empty() {
18412 return;
18413 }
18414
18415 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18416
18417 if auto_scroll {
18418 self.request_autoscroll(Autoscroll::fit(), cx);
18419 }
18420
18421 cx.notify();
18422
18423 self.scrollbar_marker_state.dirty = true;
18424 self.folds_did_change(cx);
18425 }
18426
18427 /// Removes any folds whose ranges intersect any of the given ranges.
18428 pub fn unfold_ranges<T: ToOffset + Clone>(
18429 &mut self,
18430 ranges: &[Range<T>],
18431 inclusive: bool,
18432 auto_scroll: bool,
18433 cx: &mut Context<Self>,
18434 ) {
18435 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18436 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18437 });
18438 self.folds_did_change(cx);
18439 }
18440
18441 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18442 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18443 return;
18444 }
18445 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18446 self.display_map.update(cx, |display_map, cx| {
18447 display_map.fold_buffers([buffer_id], cx)
18448 });
18449 cx.emit(EditorEvent::BufferFoldToggled {
18450 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18451 folded: true,
18452 });
18453 cx.notify();
18454 }
18455
18456 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18457 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18458 return;
18459 }
18460 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18461 self.display_map.update(cx, |display_map, cx| {
18462 display_map.unfold_buffers([buffer_id], cx);
18463 });
18464 cx.emit(EditorEvent::BufferFoldToggled {
18465 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18466 folded: false,
18467 });
18468 cx.notify();
18469 }
18470
18471 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18472 self.display_map.read(cx).is_buffer_folded(buffer)
18473 }
18474
18475 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18476 self.display_map.read(cx).folded_buffers()
18477 }
18478
18479 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18480 self.display_map.update(cx, |display_map, cx| {
18481 display_map.disable_header_for_buffer(buffer_id, cx);
18482 });
18483 cx.notify();
18484 }
18485
18486 /// Removes any folds with the given ranges.
18487 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18488 &mut self,
18489 ranges: &[Range<T>],
18490 type_id: TypeId,
18491 auto_scroll: bool,
18492 cx: &mut Context<Self>,
18493 ) {
18494 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18495 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18496 });
18497 self.folds_did_change(cx);
18498 }
18499
18500 fn remove_folds_with<T: ToOffset + Clone>(
18501 &mut self,
18502 ranges: &[Range<T>],
18503 auto_scroll: bool,
18504 cx: &mut Context<Self>,
18505 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18506 ) {
18507 if ranges.is_empty() {
18508 return;
18509 }
18510
18511 let mut buffers_affected = HashSet::default();
18512 let multi_buffer = self.buffer().read(cx);
18513 for range in ranges {
18514 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18515 buffers_affected.insert(buffer.read(cx).remote_id());
18516 };
18517 }
18518
18519 self.display_map.update(cx, update);
18520
18521 if auto_scroll {
18522 self.request_autoscroll(Autoscroll::fit(), cx);
18523 }
18524
18525 cx.notify();
18526 self.scrollbar_marker_state.dirty = true;
18527 self.active_indent_guides_state.dirty = true;
18528 }
18529
18530 pub fn update_renderer_widths(
18531 &mut self,
18532 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18533 cx: &mut Context<Self>,
18534 ) -> bool {
18535 self.display_map
18536 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18537 }
18538
18539 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18540 self.display_map.read(cx).fold_placeholder.clone()
18541 }
18542
18543 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18544 self.buffer.update(cx, |buffer, cx| {
18545 buffer.set_all_diff_hunks_expanded(cx);
18546 });
18547 }
18548
18549 pub fn expand_all_diff_hunks(
18550 &mut self,
18551 _: &ExpandAllDiffHunks,
18552 _window: &mut Window,
18553 cx: &mut Context<Self>,
18554 ) {
18555 self.buffer.update(cx, |buffer, cx| {
18556 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18557 });
18558 }
18559
18560 pub fn toggle_selected_diff_hunks(
18561 &mut self,
18562 _: &ToggleSelectedDiffHunks,
18563 _window: &mut Window,
18564 cx: &mut Context<Self>,
18565 ) {
18566 let ranges: Vec<_> = self
18567 .selections
18568 .disjoint_anchors()
18569 .iter()
18570 .map(|s| s.range())
18571 .collect();
18572 self.toggle_diff_hunks_in_ranges(ranges, cx);
18573 }
18574
18575 pub fn diff_hunks_in_ranges<'a>(
18576 &'a self,
18577 ranges: &'a [Range<Anchor>],
18578 buffer: &'a MultiBufferSnapshot,
18579 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18580 ranges.iter().flat_map(move |range| {
18581 let end_excerpt_id = range.end.excerpt_id;
18582 let range = range.to_point(buffer);
18583 let mut peek_end = range.end;
18584 if range.end.row < buffer.max_row().0 {
18585 peek_end = Point::new(range.end.row + 1, 0);
18586 }
18587 buffer
18588 .diff_hunks_in_range(range.start..peek_end)
18589 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18590 })
18591 }
18592
18593 pub fn has_stageable_diff_hunks_in_ranges(
18594 &self,
18595 ranges: &[Range<Anchor>],
18596 snapshot: &MultiBufferSnapshot,
18597 ) -> bool {
18598 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18599 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18600 }
18601
18602 pub fn toggle_staged_selected_diff_hunks(
18603 &mut self,
18604 _: &::git::ToggleStaged,
18605 _: &mut Window,
18606 cx: &mut Context<Self>,
18607 ) {
18608 let snapshot = self.buffer.read(cx).snapshot(cx);
18609 let ranges: Vec<_> = self
18610 .selections
18611 .disjoint_anchors()
18612 .iter()
18613 .map(|s| s.range())
18614 .collect();
18615 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18616 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18617 }
18618
18619 pub fn set_render_diff_hunk_controls(
18620 &mut self,
18621 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18622 cx: &mut Context<Self>,
18623 ) {
18624 self.render_diff_hunk_controls = render_diff_hunk_controls;
18625 cx.notify();
18626 }
18627
18628 pub fn stage_and_next(
18629 &mut self,
18630 _: &::git::StageAndNext,
18631 window: &mut Window,
18632 cx: &mut Context<Self>,
18633 ) {
18634 self.do_stage_or_unstage_and_next(true, window, cx);
18635 }
18636
18637 pub fn unstage_and_next(
18638 &mut self,
18639 _: &::git::UnstageAndNext,
18640 window: &mut Window,
18641 cx: &mut Context<Self>,
18642 ) {
18643 self.do_stage_or_unstage_and_next(false, window, cx);
18644 }
18645
18646 pub fn stage_or_unstage_diff_hunks(
18647 &mut self,
18648 stage: bool,
18649 ranges: Vec<Range<Anchor>>,
18650 cx: &mut Context<Self>,
18651 ) {
18652 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18653 cx.spawn(async move |this, cx| {
18654 task.await?;
18655 this.update(cx, |this, cx| {
18656 let snapshot = this.buffer.read(cx).snapshot(cx);
18657 let chunk_by = this
18658 .diff_hunks_in_ranges(&ranges, &snapshot)
18659 .chunk_by(|hunk| hunk.buffer_id);
18660 for (buffer_id, hunks) in &chunk_by {
18661 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18662 }
18663 })
18664 })
18665 .detach_and_log_err(cx);
18666 }
18667
18668 fn save_buffers_for_ranges_if_needed(
18669 &mut self,
18670 ranges: &[Range<Anchor>],
18671 cx: &mut Context<Editor>,
18672 ) -> Task<Result<()>> {
18673 let multibuffer = self.buffer.read(cx);
18674 let snapshot = multibuffer.read(cx);
18675 let buffer_ids: HashSet<_> = ranges
18676 .iter()
18677 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18678 .collect();
18679 drop(snapshot);
18680
18681 let mut buffers = HashSet::default();
18682 for buffer_id in buffer_ids {
18683 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18684 let buffer = buffer_entity.read(cx);
18685 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18686 {
18687 buffers.insert(buffer_entity);
18688 }
18689 }
18690 }
18691
18692 if let Some(project) = &self.project {
18693 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18694 } else {
18695 Task::ready(Ok(()))
18696 }
18697 }
18698
18699 fn do_stage_or_unstage_and_next(
18700 &mut self,
18701 stage: bool,
18702 window: &mut Window,
18703 cx: &mut Context<Self>,
18704 ) {
18705 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18706
18707 if ranges.iter().any(|range| range.start != range.end) {
18708 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18709 return;
18710 }
18711
18712 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18713 let snapshot = self.snapshot(window, cx);
18714 let position = self.selections.newest::<Point>(cx).head();
18715 let mut row = snapshot
18716 .buffer_snapshot
18717 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18718 .find(|hunk| hunk.row_range.start.0 > position.row)
18719 .map(|hunk| hunk.row_range.start);
18720
18721 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18722 // Outside of the project diff editor, wrap around to the beginning.
18723 if !all_diff_hunks_expanded {
18724 row = row.or_else(|| {
18725 snapshot
18726 .buffer_snapshot
18727 .diff_hunks_in_range(Point::zero()..position)
18728 .find(|hunk| hunk.row_range.end.0 < position.row)
18729 .map(|hunk| hunk.row_range.start)
18730 });
18731 }
18732
18733 if let Some(row) = row {
18734 let destination = Point::new(row.0, 0);
18735 let autoscroll = Autoscroll::center();
18736
18737 self.unfold_ranges(&[destination..destination], false, false, cx);
18738 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18739 s.select_ranges([destination..destination]);
18740 });
18741 }
18742 }
18743
18744 fn do_stage_or_unstage(
18745 &self,
18746 stage: bool,
18747 buffer_id: BufferId,
18748 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18749 cx: &mut App,
18750 ) -> Option<()> {
18751 let project = self.project()?;
18752 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18753 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18754 let buffer_snapshot = buffer.read(cx).snapshot();
18755 let file_exists = buffer_snapshot
18756 .file()
18757 .is_some_and(|file| file.disk_state().exists());
18758 diff.update(cx, |diff, cx| {
18759 diff.stage_or_unstage_hunks(
18760 stage,
18761 &hunks
18762 .map(|hunk| buffer_diff::DiffHunk {
18763 buffer_range: hunk.buffer_range,
18764 diff_base_byte_range: hunk.diff_base_byte_range,
18765 secondary_status: hunk.secondary_status,
18766 range: Point::zero()..Point::zero(), // unused
18767 })
18768 .collect::<Vec<_>>(),
18769 &buffer_snapshot,
18770 file_exists,
18771 cx,
18772 )
18773 });
18774 None
18775 }
18776
18777 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18778 let ranges: Vec<_> = self
18779 .selections
18780 .disjoint_anchors()
18781 .iter()
18782 .map(|s| s.range())
18783 .collect();
18784 self.buffer
18785 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18786 }
18787
18788 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18789 self.buffer.update(cx, |buffer, cx| {
18790 let ranges = vec![Anchor::min()..Anchor::max()];
18791 if !buffer.all_diff_hunks_expanded()
18792 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18793 {
18794 buffer.collapse_diff_hunks(ranges, cx);
18795 true
18796 } else {
18797 false
18798 }
18799 })
18800 }
18801
18802 fn toggle_diff_hunks_in_ranges(
18803 &mut self,
18804 ranges: Vec<Range<Anchor>>,
18805 cx: &mut Context<Editor>,
18806 ) {
18807 self.buffer.update(cx, |buffer, cx| {
18808 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18809 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18810 })
18811 }
18812
18813 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18814 self.buffer.update(cx, |buffer, cx| {
18815 let snapshot = buffer.snapshot(cx);
18816 let excerpt_id = range.end.excerpt_id;
18817 let point_range = range.to_point(&snapshot);
18818 let expand = !buffer.single_hunk_is_expanded(range, cx);
18819 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18820 })
18821 }
18822
18823 pub(crate) fn apply_all_diff_hunks(
18824 &mut self,
18825 _: &ApplyAllDiffHunks,
18826 window: &mut Window,
18827 cx: &mut Context<Self>,
18828 ) {
18829 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18830
18831 let buffers = self.buffer.read(cx).all_buffers();
18832 for branch_buffer in buffers {
18833 branch_buffer.update(cx, |branch_buffer, cx| {
18834 branch_buffer.merge_into_base(Vec::new(), cx);
18835 });
18836 }
18837
18838 if let Some(project) = self.project.clone() {
18839 self.save(
18840 SaveOptions {
18841 format: true,
18842 autosave: false,
18843 },
18844 project,
18845 window,
18846 cx,
18847 )
18848 .detach_and_log_err(cx);
18849 }
18850 }
18851
18852 pub(crate) fn apply_selected_diff_hunks(
18853 &mut self,
18854 _: &ApplyDiffHunk,
18855 window: &mut Window,
18856 cx: &mut Context<Self>,
18857 ) {
18858 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18859 let snapshot = self.snapshot(window, cx);
18860 let hunks = snapshot.hunks_for_ranges(
18861 self.selections
18862 .all(cx)
18863 .into_iter()
18864 .map(|selection| selection.range()),
18865 );
18866 let mut ranges_by_buffer = HashMap::default();
18867 self.transact(window, cx, |editor, _window, cx| {
18868 for hunk in hunks {
18869 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18870 ranges_by_buffer
18871 .entry(buffer.clone())
18872 .or_insert_with(Vec::new)
18873 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18874 }
18875 }
18876
18877 for (buffer, ranges) in ranges_by_buffer {
18878 buffer.update(cx, |buffer, cx| {
18879 buffer.merge_into_base(ranges, cx);
18880 });
18881 }
18882 });
18883
18884 if let Some(project) = self.project.clone() {
18885 self.save(
18886 SaveOptions {
18887 format: true,
18888 autosave: false,
18889 },
18890 project,
18891 window,
18892 cx,
18893 )
18894 .detach_and_log_err(cx);
18895 }
18896 }
18897
18898 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18899 if hovered != self.gutter_hovered {
18900 self.gutter_hovered = hovered;
18901 cx.notify();
18902 }
18903 }
18904
18905 pub fn insert_blocks(
18906 &mut self,
18907 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18908 autoscroll: Option<Autoscroll>,
18909 cx: &mut Context<Self>,
18910 ) -> Vec<CustomBlockId> {
18911 let blocks = self
18912 .display_map
18913 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18914 if let Some(autoscroll) = autoscroll {
18915 self.request_autoscroll(autoscroll, cx);
18916 }
18917 cx.notify();
18918 blocks
18919 }
18920
18921 pub fn resize_blocks(
18922 &mut self,
18923 heights: HashMap<CustomBlockId, u32>,
18924 autoscroll: Option<Autoscroll>,
18925 cx: &mut Context<Self>,
18926 ) {
18927 self.display_map
18928 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18929 if let Some(autoscroll) = autoscroll {
18930 self.request_autoscroll(autoscroll, cx);
18931 }
18932 cx.notify();
18933 }
18934
18935 pub fn replace_blocks(
18936 &mut self,
18937 renderers: HashMap<CustomBlockId, RenderBlock>,
18938 autoscroll: Option<Autoscroll>,
18939 cx: &mut Context<Self>,
18940 ) {
18941 self.display_map
18942 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18943 if let Some(autoscroll) = autoscroll {
18944 self.request_autoscroll(autoscroll, cx);
18945 }
18946 cx.notify();
18947 }
18948
18949 pub fn remove_blocks(
18950 &mut self,
18951 block_ids: HashSet<CustomBlockId>,
18952 autoscroll: Option<Autoscroll>,
18953 cx: &mut Context<Self>,
18954 ) {
18955 self.display_map.update(cx, |display_map, cx| {
18956 display_map.remove_blocks(block_ids, cx)
18957 });
18958 if let Some(autoscroll) = autoscroll {
18959 self.request_autoscroll(autoscroll, cx);
18960 }
18961 cx.notify();
18962 }
18963
18964 pub fn row_for_block(
18965 &self,
18966 block_id: CustomBlockId,
18967 cx: &mut Context<Self>,
18968 ) -> Option<DisplayRow> {
18969 self.display_map
18970 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18971 }
18972
18973 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18974 self.focused_block = Some(focused_block);
18975 }
18976
18977 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18978 self.focused_block.take()
18979 }
18980
18981 pub fn insert_creases(
18982 &mut self,
18983 creases: impl IntoIterator<Item = Crease<Anchor>>,
18984 cx: &mut Context<Self>,
18985 ) -> Vec<CreaseId> {
18986 self.display_map
18987 .update(cx, |map, cx| map.insert_creases(creases, cx))
18988 }
18989
18990 pub fn remove_creases(
18991 &mut self,
18992 ids: impl IntoIterator<Item = CreaseId>,
18993 cx: &mut Context<Self>,
18994 ) -> Vec<(CreaseId, Range<Anchor>)> {
18995 self.display_map
18996 .update(cx, |map, cx| map.remove_creases(ids, cx))
18997 }
18998
18999 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19000 self.display_map
19001 .update(cx, |map, cx| map.snapshot(cx))
19002 .longest_row()
19003 }
19004
19005 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19006 self.display_map
19007 .update(cx, |map, cx| map.snapshot(cx))
19008 .max_point()
19009 }
19010
19011 pub fn text(&self, cx: &App) -> String {
19012 self.buffer.read(cx).read(cx).text()
19013 }
19014
19015 pub fn is_empty(&self, cx: &App) -> bool {
19016 self.buffer.read(cx).read(cx).is_empty()
19017 }
19018
19019 pub fn text_option(&self, cx: &App) -> Option<String> {
19020 let text = self.text(cx);
19021 let text = text.trim();
19022
19023 if text.is_empty() {
19024 return None;
19025 }
19026
19027 Some(text.to_string())
19028 }
19029
19030 pub fn set_text(
19031 &mut self,
19032 text: impl Into<Arc<str>>,
19033 window: &mut Window,
19034 cx: &mut Context<Self>,
19035 ) {
19036 self.transact(window, cx, |this, _, cx| {
19037 this.buffer
19038 .read(cx)
19039 .as_singleton()
19040 .expect("you can only call set_text on editors for singleton buffers")
19041 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19042 });
19043 }
19044
19045 pub fn display_text(&self, cx: &mut App) -> String {
19046 self.display_map
19047 .update(cx, |map, cx| map.snapshot(cx))
19048 .text()
19049 }
19050
19051 fn create_minimap(
19052 &self,
19053 minimap_settings: MinimapSettings,
19054 window: &mut Window,
19055 cx: &mut Context<Self>,
19056 ) -> Option<Entity<Self>> {
19057 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19058 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19059 }
19060
19061 fn initialize_new_minimap(
19062 &self,
19063 minimap_settings: MinimapSettings,
19064 window: &mut Window,
19065 cx: &mut Context<Self>,
19066 ) -> Entity<Self> {
19067 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19068
19069 let mut minimap = Editor::new_internal(
19070 EditorMode::Minimap {
19071 parent: cx.weak_entity(),
19072 },
19073 self.buffer.clone(),
19074 None,
19075 Some(self.display_map.clone()),
19076 window,
19077 cx,
19078 );
19079 minimap.scroll_manager.clone_state(&self.scroll_manager);
19080 minimap.set_text_style_refinement(TextStyleRefinement {
19081 font_size: Some(MINIMAP_FONT_SIZE),
19082 font_weight: Some(MINIMAP_FONT_WEIGHT),
19083 ..Default::default()
19084 });
19085 minimap.update_minimap_configuration(minimap_settings, cx);
19086 cx.new(|_| minimap)
19087 }
19088
19089 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19090 let current_line_highlight = minimap_settings
19091 .current_line_highlight
19092 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19093 self.set_current_line_highlight(Some(current_line_highlight));
19094 }
19095
19096 pub fn minimap(&self) -> Option<&Entity<Self>> {
19097 self.minimap
19098 .as_ref()
19099 .filter(|_| self.minimap_visibility.visible())
19100 }
19101
19102 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19103 let mut wrap_guides = smallvec![];
19104
19105 if self.show_wrap_guides == Some(false) {
19106 return wrap_guides;
19107 }
19108
19109 let settings = self.buffer.read(cx).language_settings(cx);
19110 if settings.show_wrap_guides {
19111 match self.soft_wrap_mode(cx) {
19112 SoftWrap::Column(soft_wrap) => {
19113 wrap_guides.push((soft_wrap as usize, true));
19114 }
19115 SoftWrap::Bounded(soft_wrap) => {
19116 wrap_guides.push((soft_wrap as usize, true));
19117 }
19118 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19119 }
19120 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19121 }
19122
19123 wrap_guides
19124 }
19125
19126 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19127 let settings = self.buffer.read(cx).language_settings(cx);
19128 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19129 match mode {
19130 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19131 SoftWrap::None
19132 }
19133 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19134 language_settings::SoftWrap::PreferredLineLength => {
19135 SoftWrap::Column(settings.preferred_line_length)
19136 }
19137 language_settings::SoftWrap::Bounded => {
19138 SoftWrap::Bounded(settings.preferred_line_length)
19139 }
19140 }
19141 }
19142
19143 pub fn set_soft_wrap_mode(
19144 &mut self,
19145 mode: language_settings::SoftWrap,
19146
19147 cx: &mut Context<Self>,
19148 ) {
19149 self.soft_wrap_mode_override = Some(mode);
19150 cx.notify();
19151 }
19152
19153 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19154 self.hard_wrap = hard_wrap;
19155 cx.notify();
19156 }
19157
19158 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19159 self.text_style_refinement = Some(style);
19160 }
19161
19162 /// called by the Element so we know what style we were most recently rendered with.
19163 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19164 // We intentionally do not inform the display map about the minimap style
19165 // so that wrapping is not recalculated and stays consistent for the editor
19166 // and its linked minimap.
19167 if !self.mode.is_minimap() {
19168 let font = style.text.font();
19169 let font_size = style.text.font_size.to_pixels(window.rem_size());
19170 let display_map = self
19171 .placeholder_display_map
19172 .as_ref()
19173 .filter(|_| self.is_empty(cx))
19174 .unwrap_or(&self.display_map);
19175
19176 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19177 }
19178 self.style = Some(style);
19179 }
19180
19181 pub fn style(&self) -> Option<&EditorStyle> {
19182 self.style.as_ref()
19183 }
19184
19185 // Called by the element. This method is not designed to be called outside of the editor
19186 // element's layout code because it does not notify when rewrapping is computed synchronously.
19187 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19188 if self.is_empty(cx) {
19189 self.placeholder_display_map
19190 .as_ref()
19191 .map_or(false, |display_map| {
19192 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19193 })
19194 } else {
19195 self.display_map
19196 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19197 }
19198 }
19199
19200 pub fn set_soft_wrap(&mut self) {
19201 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19202 }
19203
19204 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19205 if self.soft_wrap_mode_override.is_some() {
19206 self.soft_wrap_mode_override.take();
19207 } else {
19208 let soft_wrap = match self.soft_wrap_mode(cx) {
19209 SoftWrap::GitDiff => return,
19210 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19211 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19212 language_settings::SoftWrap::None
19213 }
19214 };
19215 self.soft_wrap_mode_override = Some(soft_wrap);
19216 }
19217 cx.notify();
19218 }
19219
19220 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19221 let Some(workspace) = self.workspace() else {
19222 return;
19223 };
19224 let fs = workspace.read(cx).app_state().fs.clone();
19225 let current_show = TabBarSettings::get_global(cx).show;
19226 update_settings_file(fs, cx, move |setting, _| {
19227 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19228 });
19229 }
19230
19231 pub fn toggle_indent_guides(
19232 &mut self,
19233 _: &ToggleIndentGuides,
19234 _: &mut Window,
19235 cx: &mut Context<Self>,
19236 ) {
19237 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19238 self.buffer
19239 .read(cx)
19240 .language_settings(cx)
19241 .indent_guides
19242 .enabled
19243 });
19244 self.show_indent_guides = Some(!currently_enabled);
19245 cx.notify();
19246 }
19247
19248 fn should_show_indent_guides(&self) -> Option<bool> {
19249 self.show_indent_guides
19250 }
19251
19252 pub fn toggle_line_numbers(
19253 &mut self,
19254 _: &ToggleLineNumbers,
19255 _: &mut Window,
19256 cx: &mut Context<Self>,
19257 ) {
19258 let mut editor_settings = EditorSettings::get_global(cx).clone();
19259 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19260 EditorSettings::override_global(editor_settings, cx);
19261 }
19262
19263 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19264 if let Some(show_line_numbers) = self.show_line_numbers {
19265 return show_line_numbers;
19266 }
19267 EditorSettings::get_global(cx).gutter.line_numbers
19268 }
19269
19270 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19271 self.use_relative_line_numbers
19272 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19273 }
19274
19275 pub fn toggle_relative_line_numbers(
19276 &mut self,
19277 _: &ToggleRelativeLineNumbers,
19278 _: &mut Window,
19279 cx: &mut Context<Self>,
19280 ) {
19281 let is_relative = self.should_use_relative_line_numbers(cx);
19282 self.set_relative_line_number(Some(!is_relative), cx)
19283 }
19284
19285 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19286 self.use_relative_line_numbers = is_relative;
19287 cx.notify();
19288 }
19289
19290 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19291 self.show_gutter = show_gutter;
19292 cx.notify();
19293 }
19294
19295 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19296 self.show_scrollbars = ScrollbarAxes {
19297 horizontal: show,
19298 vertical: show,
19299 };
19300 cx.notify();
19301 }
19302
19303 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19304 self.show_scrollbars.vertical = show;
19305 cx.notify();
19306 }
19307
19308 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19309 self.show_scrollbars.horizontal = show;
19310 cx.notify();
19311 }
19312
19313 pub fn set_minimap_visibility(
19314 &mut self,
19315 minimap_visibility: MinimapVisibility,
19316 window: &mut Window,
19317 cx: &mut Context<Self>,
19318 ) {
19319 if self.minimap_visibility != minimap_visibility {
19320 if minimap_visibility.visible() && self.minimap.is_none() {
19321 let minimap_settings = EditorSettings::get_global(cx).minimap;
19322 self.minimap =
19323 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19324 }
19325 self.minimap_visibility = minimap_visibility;
19326 cx.notify();
19327 }
19328 }
19329
19330 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19331 self.set_show_scrollbars(false, cx);
19332 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19333 }
19334
19335 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19336 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19337 }
19338
19339 /// Normally the text in full mode and auto height editors is padded on the
19340 /// left side by roughly half a character width for improved hit testing.
19341 ///
19342 /// Use this method to disable this for cases where this is not wanted (e.g.
19343 /// if you want to align the editor text with some other text above or below)
19344 /// or if you want to add this padding to single-line editors.
19345 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19346 self.offset_content = offset_content;
19347 cx.notify();
19348 }
19349
19350 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19351 self.show_line_numbers = Some(show_line_numbers);
19352 cx.notify();
19353 }
19354
19355 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19356 self.disable_expand_excerpt_buttons = true;
19357 cx.notify();
19358 }
19359
19360 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19361 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19362 cx.notify();
19363 }
19364
19365 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19366 self.show_code_actions = Some(show_code_actions);
19367 cx.notify();
19368 }
19369
19370 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19371 self.show_runnables = Some(show_runnables);
19372 cx.notify();
19373 }
19374
19375 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19376 self.show_breakpoints = Some(show_breakpoints);
19377 cx.notify();
19378 }
19379
19380 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19381 if self.display_map.read(cx).masked != masked {
19382 self.display_map.update(cx, |map, _| map.masked = masked);
19383 }
19384 cx.notify()
19385 }
19386
19387 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19388 self.show_wrap_guides = Some(show_wrap_guides);
19389 cx.notify();
19390 }
19391
19392 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19393 self.show_indent_guides = Some(show_indent_guides);
19394 cx.notify();
19395 }
19396
19397 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19398 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19399 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19400 && let Some(dir) = file.abs_path(cx).parent()
19401 {
19402 return Some(dir.to_owned());
19403 }
19404 }
19405
19406 None
19407 }
19408
19409 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19410 self.active_excerpt(cx)?
19411 .1
19412 .read(cx)
19413 .file()
19414 .and_then(|f| f.as_local())
19415 }
19416
19417 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19418 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19419 let buffer = buffer.read(cx);
19420 if let Some(project_path) = buffer.project_path(cx) {
19421 let project = self.project()?.read(cx);
19422 project.absolute_path(&project_path, cx)
19423 } else {
19424 buffer
19425 .file()
19426 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19427 }
19428 })
19429 }
19430
19431 pub fn reveal_in_finder(
19432 &mut self,
19433 _: &RevealInFileManager,
19434 _window: &mut Window,
19435 cx: &mut Context<Self>,
19436 ) {
19437 if let Some(target) = self.target_file(cx) {
19438 cx.reveal_path(&target.abs_path(cx));
19439 }
19440 }
19441
19442 pub fn copy_path(
19443 &mut self,
19444 _: &zed_actions::workspace::CopyPath,
19445 _window: &mut Window,
19446 cx: &mut Context<Self>,
19447 ) {
19448 if let Some(path) = self.target_file_abs_path(cx)
19449 && let Some(path) = path.to_str()
19450 {
19451 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19452 } else {
19453 cx.propagate();
19454 }
19455 }
19456
19457 pub fn copy_relative_path(
19458 &mut self,
19459 _: &zed_actions::workspace::CopyRelativePath,
19460 _window: &mut Window,
19461 cx: &mut Context<Self>,
19462 ) {
19463 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19464 let project = self.project()?.read(cx);
19465 let path = buffer.read(cx).file()?.path();
19466 let path = path.display(project.path_style(cx));
19467 Some(path)
19468 }) {
19469 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19470 } else {
19471 cx.propagate();
19472 }
19473 }
19474
19475 /// Returns the project path for the editor's buffer, if any buffer is
19476 /// opened in the editor.
19477 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19478 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19479 buffer.read(cx).project_path(cx)
19480 } else {
19481 None
19482 }
19483 }
19484
19485 // Returns true if the editor handled a go-to-line request
19486 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19487 maybe!({
19488 let breakpoint_store = self.breakpoint_store.as_ref()?;
19489
19490 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19491 else {
19492 self.clear_row_highlights::<ActiveDebugLine>();
19493 return None;
19494 };
19495
19496 let position = active_stack_frame.position;
19497 let buffer_id = position.buffer_id?;
19498 let snapshot = self
19499 .project
19500 .as_ref()?
19501 .read(cx)
19502 .buffer_for_id(buffer_id, cx)?
19503 .read(cx)
19504 .snapshot();
19505
19506 let mut handled = false;
19507 for (id, ExcerptRange { context, .. }) in
19508 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19509 {
19510 if context.start.cmp(&position, &snapshot).is_ge()
19511 || context.end.cmp(&position, &snapshot).is_lt()
19512 {
19513 continue;
19514 }
19515 let snapshot = self.buffer.read(cx).snapshot(cx);
19516 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19517
19518 handled = true;
19519 self.clear_row_highlights::<ActiveDebugLine>();
19520
19521 self.go_to_line::<ActiveDebugLine>(
19522 multibuffer_anchor,
19523 Some(cx.theme().colors().editor_debugger_active_line_background),
19524 window,
19525 cx,
19526 );
19527
19528 cx.notify();
19529 }
19530
19531 handled.then_some(())
19532 })
19533 .is_some()
19534 }
19535
19536 pub fn copy_file_name_without_extension(
19537 &mut self,
19538 _: &CopyFileNameWithoutExtension,
19539 _: &mut Window,
19540 cx: &mut Context<Self>,
19541 ) {
19542 if let Some(file) = self.target_file(cx)
19543 && let Some(file_stem) = file.path().file_stem()
19544 {
19545 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19546 }
19547 }
19548
19549 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19550 if let Some(file) = self.target_file(cx)
19551 && let Some(name) = file.path().file_name()
19552 {
19553 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19554 }
19555 }
19556
19557 pub fn toggle_git_blame(
19558 &mut self,
19559 _: &::git::Blame,
19560 window: &mut Window,
19561 cx: &mut Context<Self>,
19562 ) {
19563 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19564
19565 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19566 self.start_git_blame(true, window, cx);
19567 }
19568
19569 cx.notify();
19570 }
19571
19572 pub fn toggle_git_blame_inline(
19573 &mut self,
19574 _: &ToggleGitBlameInline,
19575 window: &mut Window,
19576 cx: &mut Context<Self>,
19577 ) {
19578 self.toggle_git_blame_inline_internal(true, window, cx);
19579 cx.notify();
19580 }
19581
19582 pub fn open_git_blame_commit(
19583 &mut self,
19584 _: &OpenGitBlameCommit,
19585 window: &mut Window,
19586 cx: &mut Context<Self>,
19587 ) {
19588 self.open_git_blame_commit_internal(window, cx);
19589 }
19590
19591 fn open_git_blame_commit_internal(
19592 &mut self,
19593 window: &mut Window,
19594 cx: &mut Context<Self>,
19595 ) -> Option<()> {
19596 let blame = self.blame.as_ref()?;
19597 let snapshot = self.snapshot(window, cx);
19598 let cursor = self.selections.newest::<Point>(cx).head();
19599 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19600 let (_, blame_entry) = blame
19601 .update(cx, |blame, cx| {
19602 blame
19603 .blame_for_rows(
19604 &[RowInfo {
19605 buffer_id: Some(buffer.remote_id()),
19606 buffer_row: Some(point.row),
19607 ..Default::default()
19608 }],
19609 cx,
19610 )
19611 .next()
19612 })
19613 .flatten()?;
19614 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19615 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19616 let workspace = self.workspace()?.downgrade();
19617 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19618 None
19619 }
19620
19621 pub fn git_blame_inline_enabled(&self) -> bool {
19622 self.git_blame_inline_enabled
19623 }
19624
19625 pub fn toggle_selection_menu(
19626 &mut self,
19627 _: &ToggleSelectionMenu,
19628 _: &mut Window,
19629 cx: &mut Context<Self>,
19630 ) {
19631 self.show_selection_menu = self
19632 .show_selection_menu
19633 .map(|show_selections_menu| !show_selections_menu)
19634 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19635
19636 cx.notify();
19637 }
19638
19639 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19640 self.show_selection_menu
19641 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19642 }
19643
19644 fn start_git_blame(
19645 &mut self,
19646 user_triggered: bool,
19647 window: &mut Window,
19648 cx: &mut Context<Self>,
19649 ) {
19650 if let Some(project) = self.project() {
19651 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19652 && buffer.read(cx).file().is_none()
19653 {
19654 return;
19655 }
19656
19657 let focused = self.focus_handle(cx).contains_focused(window, cx);
19658
19659 let project = project.clone();
19660 let blame = cx
19661 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19662 self.blame_subscription =
19663 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19664 self.blame = Some(blame);
19665 }
19666 }
19667
19668 fn toggle_git_blame_inline_internal(
19669 &mut self,
19670 user_triggered: bool,
19671 window: &mut Window,
19672 cx: &mut Context<Self>,
19673 ) {
19674 if self.git_blame_inline_enabled {
19675 self.git_blame_inline_enabled = false;
19676 self.show_git_blame_inline = false;
19677 self.show_git_blame_inline_delay_task.take();
19678 } else {
19679 self.git_blame_inline_enabled = true;
19680 self.start_git_blame_inline(user_triggered, window, cx);
19681 }
19682
19683 cx.notify();
19684 }
19685
19686 fn start_git_blame_inline(
19687 &mut self,
19688 user_triggered: bool,
19689 window: &mut Window,
19690 cx: &mut Context<Self>,
19691 ) {
19692 self.start_git_blame(user_triggered, window, cx);
19693
19694 if ProjectSettings::get_global(cx)
19695 .git
19696 .inline_blame_delay()
19697 .is_some()
19698 {
19699 self.start_inline_blame_timer(window, cx);
19700 } else {
19701 self.show_git_blame_inline = true
19702 }
19703 }
19704
19705 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19706 self.blame.as_ref()
19707 }
19708
19709 pub fn show_git_blame_gutter(&self) -> bool {
19710 self.show_git_blame_gutter
19711 }
19712
19713 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19714 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19715 }
19716
19717 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19718 self.show_git_blame_inline
19719 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19720 && !self.newest_selection_head_on_empty_line(cx)
19721 && self.has_blame_entries(cx)
19722 }
19723
19724 fn has_blame_entries(&self, cx: &App) -> bool {
19725 self.blame()
19726 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19727 }
19728
19729 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19730 let cursor_anchor = self.selections.newest_anchor().head();
19731
19732 let snapshot = self.buffer.read(cx).snapshot(cx);
19733 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19734
19735 snapshot.line_len(buffer_row) == 0
19736 }
19737
19738 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19739 let buffer_and_selection = maybe!({
19740 let selection = self.selections.newest::<Point>(cx);
19741 let selection_range = selection.range();
19742
19743 let multi_buffer = self.buffer().read(cx);
19744 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19745 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19746
19747 let (buffer, range, _) = if selection.reversed {
19748 buffer_ranges.first()
19749 } else {
19750 buffer_ranges.last()
19751 }?;
19752
19753 let selection = text::ToPoint::to_point(&range.start, buffer).row
19754 ..text::ToPoint::to_point(&range.end, buffer).row;
19755 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19756 });
19757
19758 let Some((buffer, selection)) = buffer_and_selection else {
19759 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19760 };
19761
19762 let Some(project) = self.project() else {
19763 return Task::ready(Err(anyhow!("editor does not have project")));
19764 };
19765
19766 project.update(cx, |project, cx| {
19767 project.get_permalink_to_line(&buffer, selection, cx)
19768 })
19769 }
19770
19771 pub fn copy_permalink_to_line(
19772 &mut self,
19773 _: &CopyPermalinkToLine,
19774 window: &mut Window,
19775 cx: &mut Context<Self>,
19776 ) {
19777 let permalink_task = self.get_permalink_to_line(cx);
19778 let workspace = self.workspace();
19779
19780 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19781 Ok(permalink) => {
19782 cx.update(|_, cx| {
19783 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19784 })
19785 .ok();
19786 }
19787 Err(err) => {
19788 let message = format!("Failed to copy permalink: {err}");
19789
19790 anyhow::Result::<()>::Err(err).log_err();
19791
19792 if let Some(workspace) = workspace {
19793 workspace
19794 .update_in(cx, |workspace, _, cx| {
19795 struct CopyPermalinkToLine;
19796
19797 workspace.show_toast(
19798 Toast::new(
19799 NotificationId::unique::<CopyPermalinkToLine>(),
19800 message,
19801 ),
19802 cx,
19803 )
19804 })
19805 .ok();
19806 }
19807 }
19808 })
19809 .detach();
19810 }
19811
19812 pub fn copy_file_location(
19813 &mut self,
19814 _: &CopyFileLocation,
19815 _: &mut Window,
19816 cx: &mut Context<Self>,
19817 ) {
19818 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19819 if let Some(file) = self.target_file(cx) {
19820 let path = file.path().display(file.path_style(cx));
19821 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19822 }
19823 }
19824
19825 pub fn open_permalink_to_line(
19826 &mut self,
19827 _: &OpenPermalinkToLine,
19828 window: &mut Window,
19829 cx: &mut Context<Self>,
19830 ) {
19831 let permalink_task = self.get_permalink_to_line(cx);
19832 let workspace = self.workspace();
19833
19834 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19835 Ok(permalink) => {
19836 cx.update(|_, cx| {
19837 cx.open_url(permalink.as_ref());
19838 })
19839 .ok();
19840 }
19841 Err(err) => {
19842 let message = format!("Failed to open permalink: {err}");
19843
19844 anyhow::Result::<()>::Err(err).log_err();
19845
19846 if let Some(workspace) = workspace {
19847 workspace
19848 .update(cx, |workspace, cx| {
19849 struct OpenPermalinkToLine;
19850
19851 workspace.show_toast(
19852 Toast::new(
19853 NotificationId::unique::<OpenPermalinkToLine>(),
19854 message,
19855 ),
19856 cx,
19857 )
19858 })
19859 .ok();
19860 }
19861 }
19862 })
19863 .detach();
19864 }
19865
19866 pub fn insert_uuid_v4(
19867 &mut self,
19868 _: &InsertUuidV4,
19869 window: &mut Window,
19870 cx: &mut Context<Self>,
19871 ) {
19872 self.insert_uuid(UuidVersion::V4, window, cx);
19873 }
19874
19875 pub fn insert_uuid_v7(
19876 &mut self,
19877 _: &InsertUuidV7,
19878 window: &mut Window,
19879 cx: &mut Context<Self>,
19880 ) {
19881 self.insert_uuid(UuidVersion::V7, window, cx);
19882 }
19883
19884 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19885 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19886 self.transact(window, cx, |this, window, cx| {
19887 let edits = this
19888 .selections
19889 .all::<Point>(cx)
19890 .into_iter()
19891 .map(|selection| {
19892 let uuid = match version {
19893 UuidVersion::V4 => uuid::Uuid::new_v4(),
19894 UuidVersion::V7 => uuid::Uuid::now_v7(),
19895 };
19896
19897 (selection.range(), uuid.to_string())
19898 });
19899 this.edit(edits, cx);
19900 this.refresh_edit_prediction(true, false, window, cx);
19901 });
19902 }
19903
19904 pub fn open_selections_in_multibuffer(
19905 &mut self,
19906 _: &OpenSelectionsInMultibuffer,
19907 window: &mut Window,
19908 cx: &mut Context<Self>,
19909 ) {
19910 let multibuffer = self.buffer.read(cx);
19911
19912 let Some(buffer) = multibuffer.as_singleton() else {
19913 return;
19914 };
19915
19916 let Some(workspace) = self.workspace() else {
19917 return;
19918 };
19919
19920 let title = multibuffer.title(cx).to_string();
19921
19922 let locations = self
19923 .selections
19924 .all_anchors(cx)
19925 .iter()
19926 .map(|selection| {
19927 (
19928 buffer.clone(),
19929 (selection.start.text_anchor..selection.end.text_anchor)
19930 .to_point(buffer.read(cx)),
19931 )
19932 })
19933 .into_group_map();
19934
19935 cx.spawn_in(window, async move |_, cx| {
19936 workspace.update_in(cx, |workspace, window, cx| {
19937 Self::open_locations_in_multibuffer(
19938 workspace,
19939 locations,
19940 format!("Selections for '{title}'"),
19941 false,
19942 MultibufferSelectionMode::All,
19943 window,
19944 cx,
19945 );
19946 })
19947 })
19948 .detach();
19949 }
19950
19951 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19952 /// last highlight added will be used.
19953 ///
19954 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19955 pub fn highlight_rows<T: 'static>(
19956 &mut self,
19957 range: Range<Anchor>,
19958 color: Hsla,
19959 options: RowHighlightOptions,
19960 cx: &mut Context<Self>,
19961 ) {
19962 let snapshot = self.buffer().read(cx).snapshot(cx);
19963 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19964 let ix = row_highlights.binary_search_by(|highlight| {
19965 Ordering::Equal
19966 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19967 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19968 });
19969
19970 if let Err(mut ix) = ix {
19971 let index = post_inc(&mut self.highlight_order);
19972
19973 // If this range intersects with the preceding highlight, then merge it with
19974 // the preceding highlight. Otherwise insert a new highlight.
19975 let mut merged = false;
19976 if ix > 0 {
19977 let prev_highlight = &mut row_highlights[ix - 1];
19978 if prev_highlight
19979 .range
19980 .end
19981 .cmp(&range.start, &snapshot)
19982 .is_ge()
19983 {
19984 ix -= 1;
19985 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19986 prev_highlight.range.end = range.end;
19987 }
19988 merged = true;
19989 prev_highlight.index = index;
19990 prev_highlight.color = color;
19991 prev_highlight.options = options;
19992 }
19993 }
19994
19995 if !merged {
19996 row_highlights.insert(
19997 ix,
19998 RowHighlight {
19999 range,
20000 index,
20001 color,
20002 options,
20003 type_id: TypeId::of::<T>(),
20004 },
20005 );
20006 }
20007
20008 // If any of the following highlights intersect with this one, merge them.
20009 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20010 let highlight = &row_highlights[ix];
20011 if next_highlight
20012 .range
20013 .start
20014 .cmp(&highlight.range.end, &snapshot)
20015 .is_le()
20016 {
20017 if next_highlight
20018 .range
20019 .end
20020 .cmp(&highlight.range.end, &snapshot)
20021 .is_gt()
20022 {
20023 row_highlights[ix].range.end = next_highlight.range.end;
20024 }
20025 row_highlights.remove(ix + 1);
20026 } else {
20027 break;
20028 }
20029 }
20030 }
20031 }
20032
20033 /// Remove any highlighted row ranges of the given type that intersect the
20034 /// given ranges.
20035 pub fn remove_highlighted_rows<T: 'static>(
20036 &mut self,
20037 ranges_to_remove: Vec<Range<Anchor>>,
20038 cx: &mut Context<Self>,
20039 ) {
20040 let snapshot = self.buffer().read(cx).snapshot(cx);
20041 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20042 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20043 row_highlights.retain(|highlight| {
20044 while let Some(range_to_remove) = ranges_to_remove.peek() {
20045 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20046 Ordering::Less | Ordering::Equal => {
20047 ranges_to_remove.next();
20048 }
20049 Ordering::Greater => {
20050 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20051 Ordering::Less | Ordering::Equal => {
20052 return false;
20053 }
20054 Ordering::Greater => break,
20055 }
20056 }
20057 }
20058 }
20059
20060 true
20061 })
20062 }
20063
20064 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20065 pub fn clear_row_highlights<T: 'static>(&mut self) {
20066 self.highlighted_rows.remove(&TypeId::of::<T>());
20067 }
20068
20069 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20070 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20071 self.highlighted_rows
20072 .get(&TypeId::of::<T>())
20073 .map_or(&[] as &[_], |vec| vec.as_slice())
20074 .iter()
20075 .map(|highlight| (highlight.range.clone(), highlight.color))
20076 }
20077
20078 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20079 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20080 /// Allows to ignore certain kinds of highlights.
20081 pub fn highlighted_display_rows(
20082 &self,
20083 window: &mut Window,
20084 cx: &mut App,
20085 ) -> BTreeMap<DisplayRow, LineHighlight> {
20086 let snapshot = self.snapshot(window, cx);
20087 let mut used_highlight_orders = HashMap::default();
20088 self.highlighted_rows
20089 .iter()
20090 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20091 .fold(
20092 BTreeMap::<DisplayRow, LineHighlight>::new(),
20093 |mut unique_rows, highlight| {
20094 let start = highlight.range.start.to_display_point(&snapshot);
20095 let end = highlight.range.end.to_display_point(&snapshot);
20096 let start_row = start.row().0;
20097 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20098 && end.column() == 0
20099 {
20100 end.row().0.saturating_sub(1)
20101 } else {
20102 end.row().0
20103 };
20104 for row in start_row..=end_row {
20105 let used_index =
20106 used_highlight_orders.entry(row).or_insert(highlight.index);
20107 if highlight.index >= *used_index {
20108 *used_index = highlight.index;
20109 unique_rows.insert(
20110 DisplayRow(row),
20111 LineHighlight {
20112 include_gutter: highlight.options.include_gutter,
20113 border: None,
20114 background: highlight.color.into(),
20115 type_id: Some(highlight.type_id),
20116 },
20117 );
20118 }
20119 }
20120 unique_rows
20121 },
20122 )
20123 }
20124
20125 pub fn highlighted_display_row_for_autoscroll(
20126 &self,
20127 snapshot: &DisplaySnapshot,
20128 ) -> Option<DisplayRow> {
20129 self.highlighted_rows
20130 .values()
20131 .flat_map(|highlighted_rows| highlighted_rows.iter())
20132 .filter_map(|highlight| {
20133 if highlight.options.autoscroll {
20134 Some(highlight.range.start.to_display_point(snapshot).row())
20135 } else {
20136 None
20137 }
20138 })
20139 .min()
20140 }
20141
20142 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20143 self.highlight_background::<SearchWithinRange>(
20144 ranges,
20145 |colors| colors.colors().editor_document_highlight_read_background,
20146 cx,
20147 )
20148 }
20149
20150 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20151 self.breadcrumb_header = Some(new_header);
20152 }
20153
20154 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20155 self.clear_background_highlights::<SearchWithinRange>(cx);
20156 }
20157
20158 pub fn highlight_background<T: 'static>(
20159 &mut self,
20160 ranges: &[Range<Anchor>],
20161 color_fetcher: fn(&Theme) -> Hsla,
20162 cx: &mut Context<Self>,
20163 ) {
20164 self.background_highlights.insert(
20165 HighlightKey::Type(TypeId::of::<T>()),
20166 (color_fetcher, Arc::from(ranges)),
20167 );
20168 self.scrollbar_marker_state.dirty = true;
20169 cx.notify();
20170 }
20171
20172 pub fn highlight_background_key<T: 'static>(
20173 &mut self,
20174 key: usize,
20175 ranges: &[Range<Anchor>],
20176 color_fetcher: fn(&Theme) -> Hsla,
20177 cx: &mut Context<Self>,
20178 ) {
20179 self.background_highlights.insert(
20180 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20181 (color_fetcher, Arc::from(ranges)),
20182 );
20183 self.scrollbar_marker_state.dirty = true;
20184 cx.notify();
20185 }
20186
20187 pub fn clear_background_highlights<T: 'static>(
20188 &mut self,
20189 cx: &mut Context<Self>,
20190 ) -> Option<BackgroundHighlight> {
20191 let text_highlights = self
20192 .background_highlights
20193 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20194 if !text_highlights.1.is_empty() {
20195 self.scrollbar_marker_state.dirty = true;
20196 cx.notify();
20197 }
20198 Some(text_highlights)
20199 }
20200
20201 pub fn highlight_gutter<T: 'static>(
20202 &mut self,
20203 ranges: impl Into<Vec<Range<Anchor>>>,
20204 color_fetcher: fn(&App) -> Hsla,
20205 cx: &mut Context<Self>,
20206 ) {
20207 self.gutter_highlights
20208 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20209 cx.notify();
20210 }
20211
20212 pub fn clear_gutter_highlights<T: 'static>(
20213 &mut self,
20214 cx: &mut Context<Self>,
20215 ) -> Option<GutterHighlight> {
20216 cx.notify();
20217 self.gutter_highlights.remove(&TypeId::of::<T>())
20218 }
20219
20220 pub fn insert_gutter_highlight<T: 'static>(
20221 &mut self,
20222 range: Range<Anchor>,
20223 color_fetcher: fn(&App) -> Hsla,
20224 cx: &mut Context<Self>,
20225 ) {
20226 let snapshot = self.buffer().read(cx).snapshot(cx);
20227 let mut highlights = self
20228 .gutter_highlights
20229 .remove(&TypeId::of::<T>())
20230 .map(|(_, highlights)| highlights)
20231 .unwrap_or_default();
20232 let ix = highlights.binary_search_by(|highlight| {
20233 Ordering::Equal
20234 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20235 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20236 });
20237 if let Err(ix) = ix {
20238 highlights.insert(ix, range);
20239 }
20240 self.gutter_highlights
20241 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20242 }
20243
20244 pub fn remove_gutter_highlights<T: 'static>(
20245 &mut self,
20246 ranges_to_remove: Vec<Range<Anchor>>,
20247 cx: &mut Context<Self>,
20248 ) {
20249 let snapshot = self.buffer().read(cx).snapshot(cx);
20250 let Some((color_fetcher, mut gutter_highlights)) =
20251 self.gutter_highlights.remove(&TypeId::of::<T>())
20252 else {
20253 return;
20254 };
20255 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20256 gutter_highlights.retain(|highlight| {
20257 while let Some(range_to_remove) = ranges_to_remove.peek() {
20258 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20259 Ordering::Less | Ordering::Equal => {
20260 ranges_to_remove.next();
20261 }
20262 Ordering::Greater => {
20263 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20264 Ordering::Less | Ordering::Equal => {
20265 return false;
20266 }
20267 Ordering::Greater => break,
20268 }
20269 }
20270 }
20271 }
20272
20273 true
20274 });
20275 self.gutter_highlights
20276 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20277 }
20278
20279 #[cfg(feature = "test-support")]
20280 pub fn all_text_highlights(
20281 &self,
20282 window: &mut Window,
20283 cx: &mut Context<Self>,
20284 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20285 let snapshot = self.snapshot(window, cx);
20286 self.display_map.update(cx, |display_map, _| {
20287 display_map
20288 .all_text_highlights()
20289 .map(|highlight| {
20290 let (style, ranges) = highlight.as_ref();
20291 (
20292 *style,
20293 ranges
20294 .iter()
20295 .map(|range| range.clone().to_display_points(&snapshot))
20296 .collect(),
20297 )
20298 })
20299 .collect()
20300 })
20301 }
20302
20303 #[cfg(feature = "test-support")]
20304 pub fn all_text_background_highlights(
20305 &self,
20306 window: &mut Window,
20307 cx: &mut Context<Self>,
20308 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20309 let snapshot = self.snapshot(window, cx);
20310 let buffer = &snapshot.buffer_snapshot;
20311 let start = buffer.anchor_before(0);
20312 let end = buffer.anchor_after(buffer.len());
20313 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20314 }
20315
20316 #[cfg(any(test, feature = "test-support"))]
20317 pub fn sorted_background_highlights_in_range(
20318 &self,
20319 search_range: Range<Anchor>,
20320 display_snapshot: &DisplaySnapshot,
20321 theme: &Theme,
20322 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20323 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20324 res.sort_by(|a, b| {
20325 a.0.start
20326 .cmp(&b.0.start)
20327 .then_with(|| a.0.end.cmp(&b.0.end))
20328 .then_with(|| a.1.cmp(&b.1))
20329 });
20330 res
20331 }
20332
20333 #[cfg(feature = "test-support")]
20334 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20335 let snapshot = self.buffer().read(cx).snapshot(cx);
20336
20337 let highlights = self
20338 .background_highlights
20339 .get(&HighlightKey::Type(TypeId::of::<
20340 items::BufferSearchHighlights,
20341 >()));
20342
20343 if let Some((_color, ranges)) = highlights {
20344 ranges
20345 .iter()
20346 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20347 .collect_vec()
20348 } else {
20349 vec![]
20350 }
20351 }
20352
20353 fn document_highlights_for_position<'a>(
20354 &'a self,
20355 position: Anchor,
20356 buffer: &'a MultiBufferSnapshot,
20357 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20358 let read_highlights = self
20359 .background_highlights
20360 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20361 .map(|h| &h.1);
20362 let write_highlights = self
20363 .background_highlights
20364 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20365 .map(|h| &h.1);
20366 let left_position = position.bias_left(buffer);
20367 let right_position = position.bias_right(buffer);
20368 read_highlights
20369 .into_iter()
20370 .chain(write_highlights)
20371 .flat_map(move |ranges| {
20372 let start_ix = match ranges.binary_search_by(|probe| {
20373 let cmp = probe.end.cmp(&left_position, buffer);
20374 if cmp.is_ge() {
20375 Ordering::Greater
20376 } else {
20377 Ordering::Less
20378 }
20379 }) {
20380 Ok(i) | Err(i) => i,
20381 };
20382
20383 ranges[start_ix..]
20384 .iter()
20385 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20386 })
20387 }
20388
20389 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20390 self.background_highlights
20391 .get(&HighlightKey::Type(TypeId::of::<T>()))
20392 .is_some_and(|(_, highlights)| !highlights.is_empty())
20393 }
20394
20395 /// Returns all background highlights for a given range.
20396 ///
20397 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20398 pub fn background_highlights_in_range(
20399 &self,
20400 search_range: Range<Anchor>,
20401 display_snapshot: &DisplaySnapshot,
20402 theme: &Theme,
20403 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20404 let mut results = Vec::new();
20405 for (color_fetcher, ranges) in self.background_highlights.values() {
20406 let color = color_fetcher(theme);
20407 let start_ix = match ranges.binary_search_by(|probe| {
20408 let cmp = probe
20409 .end
20410 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20411 if cmp.is_gt() {
20412 Ordering::Greater
20413 } else {
20414 Ordering::Less
20415 }
20416 }) {
20417 Ok(i) | Err(i) => i,
20418 };
20419 for range in &ranges[start_ix..] {
20420 if range
20421 .start
20422 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20423 .is_ge()
20424 {
20425 break;
20426 }
20427
20428 let start = range.start.to_display_point(display_snapshot);
20429 let end = range.end.to_display_point(display_snapshot);
20430 results.push((start..end, color))
20431 }
20432 }
20433 results
20434 }
20435
20436 pub fn gutter_highlights_in_range(
20437 &self,
20438 search_range: Range<Anchor>,
20439 display_snapshot: &DisplaySnapshot,
20440 cx: &App,
20441 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20442 let mut results = Vec::new();
20443 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20444 let color = color_fetcher(cx);
20445 let start_ix = match ranges.binary_search_by(|probe| {
20446 let cmp = probe
20447 .end
20448 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20449 if cmp.is_gt() {
20450 Ordering::Greater
20451 } else {
20452 Ordering::Less
20453 }
20454 }) {
20455 Ok(i) | Err(i) => i,
20456 };
20457 for range in &ranges[start_ix..] {
20458 if range
20459 .start
20460 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20461 .is_ge()
20462 {
20463 break;
20464 }
20465
20466 let start = range.start.to_display_point(display_snapshot);
20467 let end = range.end.to_display_point(display_snapshot);
20468 results.push((start..end, color))
20469 }
20470 }
20471 results
20472 }
20473
20474 /// Get the text ranges corresponding to the redaction query
20475 pub fn redacted_ranges(
20476 &self,
20477 search_range: Range<Anchor>,
20478 display_snapshot: &DisplaySnapshot,
20479 cx: &App,
20480 ) -> Vec<Range<DisplayPoint>> {
20481 display_snapshot
20482 .buffer_snapshot
20483 .redacted_ranges(search_range, |file| {
20484 if let Some(file) = file {
20485 file.is_private()
20486 && EditorSettings::get(
20487 Some(SettingsLocation {
20488 worktree_id: file.worktree_id(cx),
20489 path: file.path().as_ref(),
20490 }),
20491 cx,
20492 )
20493 .redact_private_values
20494 } else {
20495 false
20496 }
20497 })
20498 .map(|range| {
20499 range.start.to_display_point(display_snapshot)
20500 ..range.end.to_display_point(display_snapshot)
20501 })
20502 .collect()
20503 }
20504
20505 pub fn highlight_text_key<T: 'static>(
20506 &mut self,
20507 key: usize,
20508 ranges: Vec<Range<Anchor>>,
20509 style: HighlightStyle,
20510 cx: &mut Context<Self>,
20511 ) {
20512 self.display_map.update(cx, |map, _| {
20513 map.highlight_text(
20514 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20515 ranges,
20516 style,
20517 );
20518 });
20519 cx.notify();
20520 }
20521
20522 pub fn highlight_text<T: 'static>(
20523 &mut self,
20524 ranges: Vec<Range<Anchor>>,
20525 style: HighlightStyle,
20526 cx: &mut Context<Self>,
20527 ) {
20528 self.display_map.update(cx, |map, _| {
20529 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20530 });
20531 cx.notify();
20532 }
20533
20534 pub(crate) fn highlight_inlays<T: 'static>(
20535 &mut self,
20536 highlights: Vec<InlayHighlight>,
20537 style: HighlightStyle,
20538 cx: &mut Context<Self>,
20539 ) {
20540 self.display_map.update(cx, |map, _| {
20541 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20542 });
20543 cx.notify();
20544 }
20545
20546 pub fn text_highlights<'a, T: 'static>(
20547 &'a self,
20548 cx: &'a App,
20549 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20550 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20551 }
20552
20553 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20554 let cleared = self
20555 .display_map
20556 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20557 if cleared {
20558 cx.notify();
20559 }
20560 }
20561
20562 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20563 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20564 && self.focus_handle.is_focused(window)
20565 }
20566
20567 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20568 self.show_cursor_when_unfocused = is_enabled;
20569 cx.notify();
20570 }
20571
20572 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20573 cx.notify();
20574 }
20575
20576 fn on_debug_session_event(
20577 &mut self,
20578 _session: Entity<Session>,
20579 event: &SessionEvent,
20580 cx: &mut Context<Self>,
20581 ) {
20582 if let SessionEvent::InvalidateInlineValue = event {
20583 self.refresh_inline_values(cx);
20584 }
20585 }
20586
20587 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20588 let Some(project) = self.project.clone() else {
20589 return;
20590 };
20591
20592 if !self.inline_value_cache.enabled {
20593 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20594 self.splice_inlays(&inlays, Vec::new(), cx);
20595 return;
20596 }
20597
20598 let current_execution_position = self
20599 .highlighted_rows
20600 .get(&TypeId::of::<ActiveDebugLine>())
20601 .and_then(|lines| lines.last().map(|line| line.range.end));
20602
20603 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20604 let inline_values = editor
20605 .update(cx, |editor, cx| {
20606 let Some(current_execution_position) = current_execution_position else {
20607 return Some(Task::ready(Ok(Vec::new())));
20608 };
20609
20610 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20611 let snapshot = buffer.snapshot(cx);
20612
20613 let excerpt = snapshot.excerpt_containing(
20614 current_execution_position..current_execution_position,
20615 )?;
20616
20617 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20618 })?;
20619
20620 let range =
20621 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20622
20623 project.inline_values(buffer, range, cx)
20624 })
20625 .ok()
20626 .flatten()?
20627 .await
20628 .context("refreshing debugger inlays")
20629 .log_err()?;
20630
20631 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20632
20633 for (buffer_id, inline_value) in inline_values
20634 .into_iter()
20635 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20636 {
20637 buffer_inline_values
20638 .entry(buffer_id)
20639 .or_default()
20640 .push(inline_value);
20641 }
20642
20643 editor
20644 .update(cx, |editor, cx| {
20645 let snapshot = editor.buffer.read(cx).snapshot(cx);
20646 let mut new_inlays = Vec::default();
20647
20648 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20649 let buffer_id = buffer_snapshot.remote_id();
20650 buffer_inline_values
20651 .get(&buffer_id)
20652 .into_iter()
20653 .flatten()
20654 .for_each(|hint| {
20655 let inlay = Inlay::debugger(
20656 post_inc(&mut editor.next_inlay_id),
20657 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20658 hint.text(),
20659 );
20660 if !inlay.text().chars().contains(&'\n') {
20661 new_inlays.push(inlay);
20662 }
20663 });
20664 }
20665
20666 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20667 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20668
20669 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20670 })
20671 .ok()?;
20672 Some(())
20673 });
20674 }
20675
20676 fn on_buffer_event(
20677 &mut self,
20678 multibuffer: &Entity<MultiBuffer>,
20679 event: &multi_buffer::Event,
20680 window: &mut Window,
20681 cx: &mut Context<Self>,
20682 ) {
20683 match event {
20684 multi_buffer::Event::Edited {
20685 singleton_buffer_edited,
20686 edited_buffer,
20687 } => {
20688 self.scrollbar_marker_state.dirty = true;
20689 self.active_indent_guides_state.dirty = true;
20690 self.refresh_active_diagnostics(cx);
20691 self.refresh_code_actions(window, cx);
20692 self.refresh_selected_text_highlights(true, window, cx);
20693 self.refresh_single_line_folds(window, cx);
20694 refresh_matching_bracket_highlights(self, window, cx);
20695 if self.has_active_edit_prediction() {
20696 self.update_visible_edit_prediction(window, cx);
20697 }
20698 if let Some(project) = self.project.as_ref()
20699 && let Some(edited_buffer) = edited_buffer
20700 {
20701 project.update(cx, |project, cx| {
20702 self.registered_buffers
20703 .entry(edited_buffer.read(cx).remote_id())
20704 .or_insert_with(|| {
20705 project.register_buffer_with_language_servers(edited_buffer, cx)
20706 });
20707 });
20708 }
20709 cx.emit(EditorEvent::BufferEdited);
20710 cx.emit(SearchEvent::MatchesInvalidated);
20711
20712 if let Some(buffer) = edited_buffer {
20713 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20714 }
20715
20716 if *singleton_buffer_edited {
20717 if let Some(buffer) = edited_buffer
20718 && buffer.read(cx).file().is_none()
20719 {
20720 cx.emit(EditorEvent::TitleChanged);
20721 }
20722 if let Some(project) = &self.project {
20723 #[allow(clippy::mutable_key_type)]
20724 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20725 multibuffer
20726 .all_buffers()
20727 .into_iter()
20728 .filter_map(|buffer| {
20729 buffer.update(cx, |buffer, cx| {
20730 let language = buffer.language()?;
20731 let should_discard = project.update(cx, |project, cx| {
20732 project.is_local()
20733 && !project.has_language_servers_for(buffer, cx)
20734 });
20735 should_discard.not().then_some(language.clone())
20736 })
20737 })
20738 .collect::<HashSet<_>>()
20739 });
20740 if !languages_affected.is_empty() {
20741 self.refresh_inlay_hints(
20742 InlayHintRefreshReason::BufferEdited(languages_affected),
20743 cx,
20744 );
20745 }
20746 }
20747 }
20748
20749 let Some(project) = &self.project else { return };
20750 let (telemetry, is_via_ssh) = {
20751 let project = project.read(cx);
20752 let telemetry = project.client().telemetry().clone();
20753 let is_via_ssh = project.is_via_remote_server();
20754 (telemetry, is_via_ssh)
20755 };
20756 refresh_linked_ranges(self, window, cx);
20757 telemetry.log_edit_event("editor", is_via_ssh);
20758 }
20759 multi_buffer::Event::ExcerptsAdded {
20760 buffer,
20761 predecessor,
20762 excerpts,
20763 } => {
20764 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20765 let buffer_id = buffer.read(cx).remote_id();
20766 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20767 && let Some(project) = &self.project
20768 {
20769 update_uncommitted_diff_for_buffer(
20770 cx.entity(),
20771 project,
20772 [buffer.clone()],
20773 self.buffer.clone(),
20774 cx,
20775 )
20776 .detach();
20777 }
20778 if self.active_diagnostics != ActiveDiagnostic::All {
20779 self.update_lsp_data(false, Some(buffer_id), window, cx);
20780 }
20781 cx.emit(EditorEvent::ExcerptsAdded {
20782 buffer: buffer.clone(),
20783 predecessor: *predecessor,
20784 excerpts: excerpts.clone(),
20785 });
20786 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20787 }
20788 multi_buffer::Event::ExcerptsRemoved {
20789 ids,
20790 removed_buffer_ids,
20791 } => {
20792 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20793 let buffer = self.buffer.read(cx);
20794 self.registered_buffers
20795 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20796 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20797 cx.emit(EditorEvent::ExcerptsRemoved {
20798 ids: ids.clone(),
20799 removed_buffer_ids: removed_buffer_ids.clone(),
20800 });
20801 }
20802 multi_buffer::Event::ExcerptsEdited {
20803 excerpt_ids,
20804 buffer_ids,
20805 } => {
20806 self.display_map.update(cx, |map, cx| {
20807 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20808 });
20809 cx.emit(EditorEvent::ExcerptsEdited {
20810 ids: excerpt_ids.clone(),
20811 });
20812 }
20813 multi_buffer::Event::ExcerptsExpanded { ids } => {
20814 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20815 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20816 }
20817 multi_buffer::Event::Reparsed(buffer_id) => {
20818 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20819 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20820
20821 cx.emit(EditorEvent::Reparsed(*buffer_id));
20822 }
20823 multi_buffer::Event::DiffHunksToggled => {
20824 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20825 }
20826 multi_buffer::Event::LanguageChanged(buffer_id) => {
20827 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20828 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20829 cx.emit(EditorEvent::Reparsed(*buffer_id));
20830 cx.notify();
20831 }
20832 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20833 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20834 multi_buffer::Event::FileHandleChanged
20835 | multi_buffer::Event::Reloaded
20836 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20837 multi_buffer::Event::DiagnosticsUpdated => {
20838 self.update_diagnostics_state(window, cx);
20839 }
20840 _ => {}
20841 };
20842 }
20843
20844 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20845 if !self.diagnostics_enabled() {
20846 return;
20847 }
20848 self.refresh_active_diagnostics(cx);
20849 self.refresh_inline_diagnostics(true, window, cx);
20850 self.scrollbar_marker_state.dirty = true;
20851 cx.notify();
20852 }
20853
20854 pub fn start_temporary_diff_override(&mut self) {
20855 self.load_diff_task.take();
20856 self.temporary_diff_override = true;
20857 }
20858
20859 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20860 self.temporary_diff_override = false;
20861 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20862 self.buffer.update(cx, |buffer, cx| {
20863 buffer.set_all_diff_hunks_collapsed(cx);
20864 });
20865
20866 if let Some(project) = self.project.clone() {
20867 self.load_diff_task = Some(
20868 update_uncommitted_diff_for_buffer(
20869 cx.entity(),
20870 &project,
20871 self.buffer.read(cx).all_buffers(),
20872 self.buffer.clone(),
20873 cx,
20874 )
20875 .shared(),
20876 );
20877 }
20878 }
20879
20880 fn on_display_map_changed(
20881 &mut self,
20882 _: Entity<DisplayMap>,
20883 _: &mut Window,
20884 cx: &mut Context<Self>,
20885 ) {
20886 cx.notify();
20887 }
20888
20889 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20890 if self.diagnostics_enabled() {
20891 let new_severity = EditorSettings::get_global(cx)
20892 .diagnostics_max_severity
20893 .unwrap_or(DiagnosticSeverity::Hint);
20894 self.set_max_diagnostics_severity(new_severity, cx);
20895 }
20896 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20897 self.update_edit_prediction_settings(cx);
20898 self.refresh_edit_prediction(true, false, window, cx);
20899 self.refresh_inline_values(cx);
20900 self.refresh_inlay_hints(
20901 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20902 self.selections.newest_anchor().head(),
20903 &self.buffer.read(cx).snapshot(cx),
20904 cx,
20905 )),
20906 cx,
20907 );
20908
20909 let old_cursor_shape = self.cursor_shape;
20910 let old_show_breadcrumbs = self.show_breadcrumbs;
20911
20912 {
20913 let editor_settings = EditorSettings::get_global(cx);
20914 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20915 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20916 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20917 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20918 }
20919
20920 if old_cursor_shape != self.cursor_shape {
20921 cx.emit(EditorEvent::CursorShapeChanged);
20922 }
20923
20924 if old_show_breadcrumbs != self.show_breadcrumbs {
20925 cx.emit(EditorEvent::BreadcrumbsChanged);
20926 }
20927
20928 let project_settings = ProjectSettings::get_global(cx);
20929 self.serialize_dirty_buffers =
20930 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20931
20932 if self.mode.is_full() {
20933 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20934 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20935 if self.show_inline_diagnostics != show_inline_diagnostics {
20936 self.show_inline_diagnostics = show_inline_diagnostics;
20937 self.refresh_inline_diagnostics(false, window, cx);
20938 }
20939
20940 if self.git_blame_inline_enabled != inline_blame_enabled {
20941 self.toggle_git_blame_inline_internal(false, window, cx);
20942 }
20943
20944 let minimap_settings = EditorSettings::get_global(cx).minimap;
20945 if self.minimap_visibility != MinimapVisibility::Disabled {
20946 if self.minimap_visibility.settings_visibility()
20947 != minimap_settings.minimap_enabled()
20948 {
20949 self.set_minimap_visibility(
20950 MinimapVisibility::for_mode(self.mode(), cx),
20951 window,
20952 cx,
20953 );
20954 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20955 minimap_entity.update(cx, |minimap_editor, cx| {
20956 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20957 })
20958 }
20959 }
20960 }
20961
20962 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20963 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20964 }) {
20965 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20966 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20967 }
20968 self.refresh_colors(false, None, window, cx);
20969 }
20970
20971 cx.notify();
20972 }
20973
20974 pub fn set_searchable(&mut self, searchable: bool) {
20975 self.searchable = searchable;
20976 }
20977
20978 pub fn searchable(&self) -> bool {
20979 self.searchable
20980 }
20981
20982 fn open_proposed_changes_editor(
20983 &mut self,
20984 _: &OpenProposedChangesEditor,
20985 window: &mut Window,
20986 cx: &mut Context<Self>,
20987 ) {
20988 let Some(workspace) = self.workspace() else {
20989 cx.propagate();
20990 return;
20991 };
20992
20993 let selections = self.selections.all::<usize>(cx);
20994 let multi_buffer = self.buffer.read(cx);
20995 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20996 let mut new_selections_by_buffer = HashMap::default();
20997 for selection in selections {
20998 for (buffer, range, _) in
20999 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21000 {
21001 let mut range = range.to_point(buffer);
21002 range.start.column = 0;
21003 range.end.column = buffer.line_len(range.end.row);
21004 new_selections_by_buffer
21005 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21006 .or_insert(Vec::new())
21007 .push(range)
21008 }
21009 }
21010
21011 let proposed_changes_buffers = new_selections_by_buffer
21012 .into_iter()
21013 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21014 .collect::<Vec<_>>();
21015 let proposed_changes_editor = cx.new(|cx| {
21016 ProposedChangesEditor::new(
21017 "Proposed changes",
21018 proposed_changes_buffers,
21019 self.project.clone(),
21020 window,
21021 cx,
21022 )
21023 });
21024
21025 window.defer(cx, move |window, cx| {
21026 workspace.update(cx, |workspace, cx| {
21027 workspace.active_pane().update(cx, |pane, cx| {
21028 pane.add_item(
21029 Box::new(proposed_changes_editor),
21030 true,
21031 true,
21032 None,
21033 window,
21034 cx,
21035 );
21036 });
21037 });
21038 });
21039 }
21040
21041 pub fn open_excerpts_in_split(
21042 &mut self,
21043 _: &OpenExcerptsSplit,
21044 window: &mut Window,
21045 cx: &mut Context<Self>,
21046 ) {
21047 self.open_excerpts_common(None, true, window, cx)
21048 }
21049
21050 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21051 self.open_excerpts_common(None, false, window, cx)
21052 }
21053
21054 fn open_excerpts_common(
21055 &mut self,
21056 jump_data: Option<JumpData>,
21057 split: bool,
21058 window: &mut Window,
21059 cx: &mut Context<Self>,
21060 ) {
21061 let Some(workspace) = self.workspace() else {
21062 cx.propagate();
21063 return;
21064 };
21065
21066 if self.buffer.read(cx).is_singleton() {
21067 cx.propagate();
21068 return;
21069 }
21070
21071 let mut new_selections_by_buffer = HashMap::default();
21072 match &jump_data {
21073 Some(JumpData::MultiBufferPoint {
21074 excerpt_id,
21075 position,
21076 anchor,
21077 line_offset_from_top,
21078 }) => {
21079 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21080 if let Some(buffer) = multi_buffer_snapshot
21081 .buffer_id_for_excerpt(*excerpt_id)
21082 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21083 {
21084 let buffer_snapshot = buffer.read(cx).snapshot();
21085 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21086 language::ToPoint::to_point(anchor, &buffer_snapshot)
21087 } else {
21088 buffer_snapshot.clip_point(*position, Bias::Left)
21089 };
21090 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21091 new_selections_by_buffer.insert(
21092 buffer,
21093 (
21094 vec![jump_to_offset..jump_to_offset],
21095 Some(*line_offset_from_top),
21096 ),
21097 );
21098 }
21099 }
21100 Some(JumpData::MultiBufferRow {
21101 row,
21102 line_offset_from_top,
21103 }) => {
21104 let point = MultiBufferPoint::new(row.0, 0);
21105 if let Some((buffer, buffer_point, _)) =
21106 self.buffer.read(cx).point_to_buffer_point(point, cx)
21107 {
21108 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21109 new_selections_by_buffer
21110 .entry(buffer)
21111 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21112 .0
21113 .push(buffer_offset..buffer_offset)
21114 }
21115 }
21116 None => {
21117 let selections = self.selections.all::<usize>(cx);
21118 let multi_buffer = self.buffer.read(cx);
21119 for selection in selections {
21120 for (snapshot, range, _, anchor) in multi_buffer
21121 .snapshot(cx)
21122 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21123 {
21124 if let Some(anchor) = anchor {
21125 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21126 else {
21127 continue;
21128 };
21129 let offset = text::ToOffset::to_offset(
21130 &anchor.text_anchor,
21131 &buffer_handle.read(cx).snapshot(),
21132 );
21133 let range = offset..offset;
21134 new_selections_by_buffer
21135 .entry(buffer_handle)
21136 .or_insert((Vec::new(), None))
21137 .0
21138 .push(range)
21139 } else {
21140 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21141 else {
21142 continue;
21143 };
21144 new_selections_by_buffer
21145 .entry(buffer_handle)
21146 .or_insert((Vec::new(), None))
21147 .0
21148 .push(range)
21149 }
21150 }
21151 }
21152 }
21153 }
21154
21155 new_selections_by_buffer
21156 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21157
21158 if new_selections_by_buffer.is_empty() {
21159 return;
21160 }
21161
21162 // We defer the pane interaction because we ourselves are a workspace item
21163 // and activating a new item causes the pane to call a method on us reentrantly,
21164 // which panics if we're on the stack.
21165 window.defer(cx, move |window, cx| {
21166 workspace.update(cx, |workspace, cx| {
21167 let pane = if split {
21168 workspace.adjacent_pane(window, cx)
21169 } else {
21170 workspace.active_pane().clone()
21171 };
21172
21173 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21174 let editor = buffer
21175 .read(cx)
21176 .file()
21177 .is_none()
21178 .then(|| {
21179 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21180 // so `workspace.open_project_item` will never find them, always opening a new editor.
21181 // Instead, we try to activate the existing editor in the pane first.
21182 let (editor, pane_item_index) =
21183 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21184 let editor = item.downcast::<Editor>()?;
21185 let singleton_buffer =
21186 editor.read(cx).buffer().read(cx).as_singleton()?;
21187 if singleton_buffer == buffer {
21188 Some((editor, i))
21189 } else {
21190 None
21191 }
21192 })?;
21193 pane.update(cx, |pane, cx| {
21194 pane.activate_item(pane_item_index, true, true, window, cx)
21195 });
21196 Some(editor)
21197 })
21198 .flatten()
21199 .unwrap_or_else(|| {
21200 workspace.open_project_item::<Self>(
21201 pane.clone(),
21202 buffer,
21203 true,
21204 true,
21205 window,
21206 cx,
21207 )
21208 });
21209
21210 editor.update(cx, |editor, cx| {
21211 let autoscroll = match scroll_offset {
21212 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21213 None => Autoscroll::newest(),
21214 };
21215 let nav_history = editor.nav_history.take();
21216 editor.change_selections(
21217 SelectionEffects::scroll(autoscroll),
21218 window,
21219 cx,
21220 |s| {
21221 s.select_ranges(ranges);
21222 },
21223 );
21224 editor.nav_history = nav_history;
21225 });
21226 }
21227 })
21228 });
21229 }
21230
21231 // For now, don't allow opening excerpts in buffers that aren't backed by
21232 // regular project files.
21233 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21234 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21235 }
21236
21237 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21238 let snapshot = self.buffer.read(cx).read(cx);
21239 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21240 Some(
21241 ranges
21242 .iter()
21243 .map(move |range| {
21244 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21245 })
21246 .collect(),
21247 )
21248 }
21249
21250 fn selection_replacement_ranges(
21251 &self,
21252 range: Range<OffsetUtf16>,
21253 cx: &mut App,
21254 ) -> Vec<Range<OffsetUtf16>> {
21255 let selections = self.selections.all::<OffsetUtf16>(cx);
21256 let newest_selection = selections
21257 .iter()
21258 .max_by_key(|selection| selection.id)
21259 .unwrap();
21260 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21261 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21262 let snapshot = self.buffer.read(cx).read(cx);
21263 selections
21264 .into_iter()
21265 .map(|mut selection| {
21266 selection.start.0 =
21267 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21268 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21269 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21270 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21271 })
21272 .collect()
21273 }
21274
21275 fn report_editor_event(
21276 &self,
21277 reported_event: ReportEditorEvent,
21278 file_extension: Option<String>,
21279 cx: &App,
21280 ) {
21281 if cfg!(any(test, feature = "test-support")) {
21282 return;
21283 }
21284
21285 let Some(project) = &self.project else { return };
21286
21287 // If None, we are in a file without an extension
21288 let file = self
21289 .buffer
21290 .read(cx)
21291 .as_singleton()
21292 .and_then(|b| b.read(cx).file());
21293 let file_extension = file_extension.or(file
21294 .as_ref()
21295 .and_then(|file| Path::new(file.file_name(cx)).extension())
21296 .and_then(|e| e.to_str())
21297 .map(|a| a.to_string()));
21298
21299 let vim_mode = vim_enabled(cx);
21300
21301 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21302 let copilot_enabled = edit_predictions_provider
21303 == language::language_settings::EditPredictionProvider::Copilot;
21304 let copilot_enabled_for_language = self
21305 .buffer
21306 .read(cx)
21307 .language_settings(cx)
21308 .show_edit_predictions;
21309
21310 let project = project.read(cx);
21311 let event_type = reported_event.event_type();
21312
21313 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21314 telemetry::event!(
21315 event_type,
21316 type = if auto_saved {"autosave"} else {"manual"},
21317 file_extension,
21318 vim_mode,
21319 copilot_enabled,
21320 copilot_enabled_for_language,
21321 edit_predictions_provider,
21322 is_via_ssh = project.is_via_remote_server(),
21323 );
21324 } else {
21325 telemetry::event!(
21326 event_type,
21327 file_extension,
21328 vim_mode,
21329 copilot_enabled,
21330 copilot_enabled_for_language,
21331 edit_predictions_provider,
21332 is_via_ssh = project.is_via_remote_server(),
21333 );
21334 };
21335 }
21336
21337 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21338 /// with each line being an array of {text, highlight} objects.
21339 fn copy_highlight_json(
21340 &mut self,
21341 _: &CopyHighlightJson,
21342 window: &mut Window,
21343 cx: &mut Context<Self>,
21344 ) {
21345 #[derive(Serialize)]
21346 struct Chunk<'a> {
21347 text: String,
21348 highlight: Option<&'a str>,
21349 }
21350
21351 let snapshot = self.buffer.read(cx).snapshot(cx);
21352 let range = self
21353 .selected_text_range(false, window, cx)
21354 .and_then(|selection| {
21355 if selection.range.is_empty() {
21356 None
21357 } else {
21358 Some(selection.range)
21359 }
21360 })
21361 .unwrap_or_else(|| 0..snapshot.len());
21362
21363 let chunks = snapshot.chunks(range, true);
21364 let mut lines = Vec::new();
21365 let mut line: VecDeque<Chunk> = VecDeque::new();
21366
21367 let Some(style) = self.style.as_ref() else {
21368 return;
21369 };
21370
21371 for chunk in chunks {
21372 let highlight = chunk
21373 .syntax_highlight_id
21374 .and_then(|id| id.name(&style.syntax));
21375 let mut chunk_lines = chunk.text.split('\n').peekable();
21376 while let Some(text) = chunk_lines.next() {
21377 let mut merged_with_last_token = false;
21378 if let Some(last_token) = line.back_mut()
21379 && last_token.highlight == highlight
21380 {
21381 last_token.text.push_str(text);
21382 merged_with_last_token = true;
21383 }
21384
21385 if !merged_with_last_token {
21386 line.push_back(Chunk {
21387 text: text.into(),
21388 highlight,
21389 });
21390 }
21391
21392 if chunk_lines.peek().is_some() {
21393 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21394 line.pop_front();
21395 }
21396 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21397 line.pop_back();
21398 }
21399
21400 lines.push(mem::take(&mut line));
21401 }
21402 }
21403 }
21404
21405 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21406 return;
21407 };
21408 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21409 }
21410
21411 pub fn open_context_menu(
21412 &mut self,
21413 _: &OpenContextMenu,
21414 window: &mut Window,
21415 cx: &mut Context<Self>,
21416 ) {
21417 self.request_autoscroll(Autoscroll::newest(), cx);
21418 let position = self.selections.newest_display(cx).start;
21419 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21420 }
21421
21422 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21423 &self.inlay_hint_cache
21424 }
21425
21426 pub fn replay_insert_event(
21427 &mut self,
21428 text: &str,
21429 relative_utf16_range: Option<Range<isize>>,
21430 window: &mut Window,
21431 cx: &mut Context<Self>,
21432 ) {
21433 if !self.input_enabled {
21434 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21435 return;
21436 }
21437 if let Some(relative_utf16_range) = relative_utf16_range {
21438 let selections = self.selections.all::<OffsetUtf16>(cx);
21439 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21440 let new_ranges = selections.into_iter().map(|range| {
21441 let start = OffsetUtf16(
21442 range
21443 .head()
21444 .0
21445 .saturating_add_signed(relative_utf16_range.start),
21446 );
21447 let end = OffsetUtf16(
21448 range
21449 .head()
21450 .0
21451 .saturating_add_signed(relative_utf16_range.end),
21452 );
21453 start..end
21454 });
21455 s.select_ranges(new_ranges);
21456 });
21457 }
21458
21459 self.handle_input(text, window, cx);
21460 }
21461
21462 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21463 let Some(provider) = self.semantics_provider.as_ref() else {
21464 return false;
21465 };
21466
21467 let mut supports = false;
21468 self.buffer().update(cx, |this, cx| {
21469 this.for_each_buffer(|buffer| {
21470 supports |= provider.supports_inlay_hints(buffer, cx);
21471 });
21472 });
21473
21474 supports
21475 }
21476
21477 pub fn is_focused(&self, window: &Window) -> bool {
21478 self.focus_handle.is_focused(window)
21479 }
21480
21481 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21482 cx.emit(EditorEvent::Focused);
21483
21484 if let Some(descendant) = self
21485 .last_focused_descendant
21486 .take()
21487 .and_then(|descendant| descendant.upgrade())
21488 {
21489 window.focus(&descendant);
21490 } else {
21491 if let Some(blame) = self.blame.as_ref() {
21492 blame.update(cx, GitBlame::focus)
21493 }
21494
21495 self.blink_manager.update(cx, BlinkManager::enable);
21496 self.show_cursor_names(window, cx);
21497 self.buffer.update(cx, |buffer, cx| {
21498 buffer.finalize_last_transaction(cx);
21499 if self.leader_id.is_none() {
21500 buffer.set_active_selections(
21501 &self.selections.disjoint_anchors_arc(),
21502 self.selections.line_mode(),
21503 self.cursor_shape,
21504 cx,
21505 );
21506 }
21507 });
21508 }
21509 }
21510
21511 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21512 cx.emit(EditorEvent::FocusedIn)
21513 }
21514
21515 fn handle_focus_out(
21516 &mut self,
21517 event: FocusOutEvent,
21518 _window: &mut Window,
21519 cx: &mut Context<Self>,
21520 ) {
21521 if event.blurred != self.focus_handle {
21522 self.last_focused_descendant = Some(event.blurred);
21523 }
21524 self.selection_drag_state = SelectionDragState::None;
21525 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21526 }
21527
21528 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21529 self.blink_manager.update(cx, BlinkManager::disable);
21530 self.buffer
21531 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21532
21533 if let Some(blame) = self.blame.as_ref() {
21534 blame.update(cx, GitBlame::blur)
21535 }
21536 if !self.hover_state.focused(window, cx) {
21537 hide_hover(self, cx);
21538 }
21539 if !self
21540 .context_menu
21541 .borrow()
21542 .as_ref()
21543 .is_some_and(|context_menu| context_menu.focused(window, cx))
21544 {
21545 self.hide_context_menu(window, cx);
21546 }
21547 self.take_active_edit_prediction(cx);
21548 cx.emit(EditorEvent::Blurred);
21549 cx.notify();
21550 }
21551
21552 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21553 let mut pending: String = window
21554 .pending_input_keystrokes()
21555 .into_iter()
21556 .flatten()
21557 .filter_map(|keystroke| {
21558 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21559 keystroke.key_char.clone()
21560 } else {
21561 None
21562 }
21563 })
21564 .collect();
21565
21566 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21567 pending = "".to_string();
21568 }
21569
21570 let existing_pending = self
21571 .text_highlights::<PendingInput>(cx)
21572 .map(|(_, ranges)| ranges.to_vec());
21573 if existing_pending.is_none() && pending.is_empty() {
21574 return;
21575 }
21576 let transaction =
21577 self.transact(window, cx, |this, window, cx| {
21578 let selections = this.selections.all::<usize>(cx);
21579 let edits = selections
21580 .iter()
21581 .map(|selection| (selection.end..selection.end, pending.clone()));
21582 this.edit(edits, cx);
21583 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21584 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21585 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21586 }));
21587 });
21588 if let Some(existing_ranges) = existing_pending {
21589 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21590 this.edit(edits, cx);
21591 }
21592 });
21593
21594 let snapshot = self.snapshot(window, cx);
21595 let ranges = self
21596 .selections
21597 .all::<usize>(cx)
21598 .into_iter()
21599 .map(|selection| {
21600 snapshot.buffer_snapshot.anchor_after(selection.end)
21601 ..snapshot
21602 .buffer_snapshot
21603 .anchor_before(selection.end + pending.len())
21604 })
21605 .collect();
21606
21607 if pending.is_empty() {
21608 self.clear_highlights::<PendingInput>(cx);
21609 } else {
21610 self.highlight_text::<PendingInput>(
21611 ranges,
21612 HighlightStyle {
21613 underline: Some(UnderlineStyle {
21614 thickness: px(1.),
21615 color: None,
21616 wavy: false,
21617 }),
21618 ..Default::default()
21619 },
21620 cx,
21621 );
21622 }
21623
21624 self.ime_transaction = self.ime_transaction.or(transaction);
21625 if let Some(transaction) = self.ime_transaction {
21626 self.buffer.update(cx, |buffer, cx| {
21627 buffer.group_until_transaction(transaction, cx);
21628 });
21629 }
21630
21631 if self.text_highlights::<PendingInput>(cx).is_none() {
21632 self.ime_transaction.take();
21633 }
21634 }
21635
21636 pub fn register_action_renderer(
21637 &mut self,
21638 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21639 ) -> Subscription {
21640 let id = self.next_editor_action_id.post_inc();
21641 self.editor_actions
21642 .borrow_mut()
21643 .insert(id, Box::new(listener));
21644
21645 let editor_actions = self.editor_actions.clone();
21646 Subscription::new(move || {
21647 editor_actions.borrow_mut().remove(&id);
21648 })
21649 }
21650
21651 pub fn register_action<A: Action>(
21652 &mut self,
21653 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21654 ) -> Subscription {
21655 let id = self.next_editor_action_id.post_inc();
21656 let listener = Arc::new(listener);
21657 self.editor_actions.borrow_mut().insert(
21658 id,
21659 Box::new(move |_, window, _| {
21660 let listener = listener.clone();
21661 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21662 let action = action.downcast_ref().unwrap();
21663 if phase == DispatchPhase::Bubble {
21664 listener(action, window, cx)
21665 }
21666 })
21667 }),
21668 );
21669
21670 let editor_actions = self.editor_actions.clone();
21671 Subscription::new(move || {
21672 editor_actions.borrow_mut().remove(&id);
21673 })
21674 }
21675
21676 pub fn file_header_size(&self) -> u32 {
21677 FILE_HEADER_HEIGHT
21678 }
21679
21680 pub fn restore(
21681 &mut self,
21682 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21683 window: &mut Window,
21684 cx: &mut Context<Self>,
21685 ) {
21686 let workspace = self.workspace();
21687 let project = self.project();
21688 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21689 let mut tasks = Vec::new();
21690 for (buffer_id, changes) in revert_changes {
21691 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21692 buffer.update(cx, |buffer, cx| {
21693 buffer.edit(
21694 changes
21695 .into_iter()
21696 .map(|(range, text)| (range, text.to_string())),
21697 None,
21698 cx,
21699 );
21700 });
21701
21702 if let Some(project) =
21703 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21704 {
21705 project.update(cx, |project, cx| {
21706 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21707 })
21708 }
21709 }
21710 }
21711 tasks
21712 });
21713 cx.spawn_in(window, async move |_, cx| {
21714 for (buffer, task) in save_tasks {
21715 let result = task.await;
21716 if result.is_err() {
21717 let Some(path) = buffer
21718 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21719 .ok()
21720 else {
21721 continue;
21722 };
21723 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21724 let Some(task) = cx
21725 .update_window_entity(workspace, |workspace, window, cx| {
21726 workspace
21727 .open_path_preview(path, None, false, false, false, window, cx)
21728 })
21729 .ok()
21730 else {
21731 continue;
21732 };
21733 task.await.log_err();
21734 }
21735 }
21736 }
21737 })
21738 .detach();
21739 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21740 selections.refresh()
21741 });
21742 }
21743
21744 pub fn to_pixel_point(
21745 &self,
21746 source: multi_buffer::Anchor,
21747 editor_snapshot: &EditorSnapshot,
21748 window: &mut Window,
21749 ) -> Option<gpui::Point<Pixels>> {
21750 let source_point = source.to_display_point(editor_snapshot);
21751 self.display_to_pixel_point(source_point, editor_snapshot, window)
21752 }
21753
21754 pub fn display_to_pixel_point(
21755 &self,
21756 source: DisplayPoint,
21757 editor_snapshot: &EditorSnapshot,
21758 window: &mut Window,
21759 ) -> Option<gpui::Point<Pixels>> {
21760 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21761 let text_layout_details = self.text_layout_details(window);
21762 let scroll_top = text_layout_details
21763 .scroll_anchor
21764 .scroll_position(editor_snapshot)
21765 .y;
21766
21767 if source.row().as_f64() < scroll_top.floor() {
21768 return None;
21769 }
21770 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21771 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21772 Some(gpui::Point::new(source_x, source_y))
21773 }
21774
21775 pub fn has_visible_completions_menu(&self) -> bool {
21776 !self.edit_prediction_preview_is_active()
21777 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21778 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21779 })
21780 }
21781
21782 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21783 if self.mode.is_minimap() {
21784 return;
21785 }
21786 self.addons
21787 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21788 }
21789
21790 pub fn unregister_addon<T: Addon>(&mut self) {
21791 self.addons.remove(&std::any::TypeId::of::<T>());
21792 }
21793
21794 pub fn addon<T: Addon>(&self) -> Option<&T> {
21795 let type_id = std::any::TypeId::of::<T>();
21796 self.addons
21797 .get(&type_id)
21798 .and_then(|item| item.to_any().downcast_ref::<T>())
21799 }
21800
21801 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21802 let type_id = std::any::TypeId::of::<T>();
21803 self.addons
21804 .get_mut(&type_id)
21805 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21806 }
21807
21808 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21809 let text_layout_details = self.text_layout_details(window);
21810 let style = &text_layout_details.editor_style;
21811 let font_id = window.text_system().resolve_font(&style.text.font());
21812 let font_size = style.text.font_size.to_pixels(window.rem_size());
21813 let line_height = style.text.line_height_in_pixels(window.rem_size());
21814 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21815 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21816
21817 CharacterDimensions {
21818 em_width,
21819 em_advance,
21820 line_height,
21821 }
21822 }
21823
21824 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21825 self.load_diff_task.clone()
21826 }
21827
21828 fn read_metadata_from_db(
21829 &mut self,
21830 item_id: u64,
21831 workspace_id: WorkspaceId,
21832 window: &mut Window,
21833 cx: &mut Context<Editor>,
21834 ) {
21835 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21836 && !self.mode.is_minimap()
21837 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21838 {
21839 let buffer_snapshot = OnceCell::new();
21840
21841 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21842 && !folds.is_empty()
21843 {
21844 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21845 self.fold_ranges(
21846 folds
21847 .into_iter()
21848 .map(|(start, end)| {
21849 snapshot.clip_offset(start, Bias::Left)
21850 ..snapshot.clip_offset(end, Bias::Right)
21851 })
21852 .collect(),
21853 false,
21854 window,
21855 cx,
21856 );
21857 }
21858
21859 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21860 && !selections.is_empty()
21861 {
21862 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21863 // skip adding the initial selection to selection history
21864 self.selection_history.mode = SelectionHistoryMode::Skipping;
21865 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21866 s.select_ranges(selections.into_iter().map(|(start, end)| {
21867 snapshot.clip_offset(start, Bias::Left)
21868 ..snapshot.clip_offset(end, Bias::Right)
21869 }));
21870 });
21871 self.selection_history.mode = SelectionHistoryMode::Normal;
21872 };
21873 }
21874
21875 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21876 }
21877
21878 fn update_lsp_data(
21879 &mut self,
21880 ignore_cache: bool,
21881 for_buffer: Option<BufferId>,
21882 window: &mut Window,
21883 cx: &mut Context<'_, Self>,
21884 ) {
21885 self.pull_diagnostics(for_buffer, window, cx);
21886 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21887 }
21888}
21889
21890fn edit_for_markdown_paste<'a>(
21891 buffer: &MultiBufferSnapshot,
21892 range: Range<usize>,
21893 to_insert: &'a str,
21894 url: Option<url::Url>,
21895) -> (Range<usize>, Cow<'a, str>) {
21896 if url.is_none() {
21897 return (range, Cow::Borrowed(to_insert));
21898 };
21899
21900 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
21901
21902 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
21903 Cow::Borrowed(to_insert)
21904 } else {
21905 Cow::Owned(format!("[{old_text}]({to_insert})"))
21906 };
21907 (range, new_text)
21908}
21909
21910fn vim_enabled(cx: &App) -> bool {
21911 vim_mode_setting::VimModeSetting::try_get(cx)
21912 .map(|vim_mode| vim_mode.0)
21913 .unwrap_or(false)
21914}
21915
21916fn process_completion_for_edit(
21917 completion: &Completion,
21918 intent: CompletionIntent,
21919 buffer: &Entity<Buffer>,
21920 cursor_position: &text::Anchor,
21921 cx: &mut Context<Editor>,
21922) -> CompletionEdit {
21923 let buffer = buffer.read(cx);
21924 let buffer_snapshot = buffer.snapshot();
21925 let (snippet, new_text) = if completion.is_snippet() {
21926 // Workaround for typescript language server issues so that methods don't expand within
21927 // strings and functions with type expressions. The previous point is used because the query
21928 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21929 let mut snippet_source = completion.new_text.clone();
21930 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21931 previous_point.column = previous_point.column.saturating_sub(1);
21932 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21933 && scope.prefers_label_for_snippet_in_completion()
21934 && let Some(label) = completion.label()
21935 && matches!(
21936 completion.kind(),
21937 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21938 )
21939 {
21940 snippet_source = label;
21941 }
21942 match Snippet::parse(&snippet_source).log_err() {
21943 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21944 None => (None, completion.new_text.clone()),
21945 }
21946 } else {
21947 (None, completion.new_text.clone())
21948 };
21949
21950 let mut range_to_replace = {
21951 let replace_range = &completion.replace_range;
21952 if let CompletionSource::Lsp {
21953 insert_range: Some(insert_range),
21954 ..
21955 } = &completion.source
21956 {
21957 debug_assert_eq!(
21958 insert_range.start, replace_range.start,
21959 "insert_range and replace_range should start at the same position"
21960 );
21961 debug_assert!(
21962 insert_range
21963 .start
21964 .cmp(cursor_position, &buffer_snapshot)
21965 .is_le(),
21966 "insert_range should start before or at cursor position"
21967 );
21968 debug_assert!(
21969 replace_range
21970 .start
21971 .cmp(cursor_position, &buffer_snapshot)
21972 .is_le(),
21973 "replace_range should start before or at cursor position"
21974 );
21975
21976 let should_replace = match intent {
21977 CompletionIntent::CompleteWithInsert => false,
21978 CompletionIntent::CompleteWithReplace => true,
21979 CompletionIntent::Complete | CompletionIntent::Compose => {
21980 let insert_mode =
21981 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21982 .completions
21983 .lsp_insert_mode;
21984 match insert_mode {
21985 LspInsertMode::Insert => false,
21986 LspInsertMode::Replace => true,
21987 LspInsertMode::ReplaceSubsequence => {
21988 let mut text_to_replace = buffer.chars_for_range(
21989 buffer.anchor_before(replace_range.start)
21990 ..buffer.anchor_after(replace_range.end),
21991 );
21992 let mut current_needle = text_to_replace.next();
21993 for haystack_ch in completion.label.text.chars() {
21994 if let Some(needle_ch) = current_needle
21995 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21996 {
21997 current_needle = text_to_replace.next();
21998 }
21999 }
22000 current_needle.is_none()
22001 }
22002 LspInsertMode::ReplaceSuffix => {
22003 if replace_range
22004 .end
22005 .cmp(cursor_position, &buffer_snapshot)
22006 .is_gt()
22007 {
22008 let range_after_cursor = *cursor_position..replace_range.end;
22009 let text_after_cursor = buffer
22010 .text_for_range(
22011 buffer.anchor_before(range_after_cursor.start)
22012 ..buffer.anchor_after(range_after_cursor.end),
22013 )
22014 .collect::<String>()
22015 .to_ascii_lowercase();
22016 completion
22017 .label
22018 .text
22019 .to_ascii_lowercase()
22020 .ends_with(&text_after_cursor)
22021 } else {
22022 true
22023 }
22024 }
22025 }
22026 }
22027 };
22028
22029 if should_replace {
22030 replace_range.clone()
22031 } else {
22032 insert_range.clone()
22033 }
22034 } else {
22035 replace_range.clone()
22036 }
22037 };
22038
22039 if range_to_replace
22040 .end
22041 .cmp(cursor_position, &buffer_snapshot)
22042 .is_lt()
22043 {
22044 range_to_replace.end = *cursor_position;
22045 }
22046
22047 CompletionEdit {
22048 new_text,
22049 replace_range: range_to_replace.to_offset(buffer),
22050 snippet,
22051 }
22052}
22053
22054struct CompletionEdit {
22055 new_text: String,
22056 replace_range: Range<usize>,
22057 snippet: Option<Snippet>,
22058}
22059
22060fn insert_extra_newline_brackets(
22061 buffer: &MultiBufferSnapshot,
22062 range: Range<usize>,
22063 language: &language::LanguageScope,
22064) -> bool {
22065 let leading_whitespace_len = buffer
22066 .reversed_chars_at(range.start)
22067 .take_while(|c| c.is_whitespace() && *c != '\n')
22068 .map(|c| c.len_utf8())
22069 .sum::<usize>();
22070 let trailing_whitespace_len = buffer
22071 .chars_at(range.end)
22072 .take_while(|c| c.is_whitespace() && *c != '\n')
22073 .map(|c| c.len_utf8())
22074 .sum::<usize>();
22075 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22076
22077 language.brackets().any(|(pair, enabled)| {
22078 let pair_start = pair.start.trim_end();
22079 let pair_end = pair.end.trim_start();
22080
22081 enabled
22082 && pair.newline
22083 && buffer.contains_str_at(range.end, pair_end)
22084 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22085 })
22086}
22087
22088fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22089 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22090 [(buffer, range, _)] => (*buffer, range.clone()),
22091 _ => return false,
22092 };
22093 let pair = {
22094 let mut result: Option<BracketMatch> = None;
22095
22096 for pair in buffer
22097 .all_bracket_ranges(range.clone())
22098 .filter(move |pair| {
22099 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22100 })
22101 {
22102 let len = pair.close_range.end - pair.open_range.start;
22103
22104 if let Some(existing) = &result {
22105 let existing_len = existing.close_range.end - existing.open_range.start;
22106 if len > existing_len {
22107 continue;
22108 }
22109 }
22110
22111 result = Some(pair);
22112 }
22113
22114 result
22115 };
22116 let Some(pair) = pair else {
22117 return false;
22118 };
22119 pair.newline_only
22120 && buffer
22121 .chars_for_range(pair.open_range.end..range.start)
22122 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22123 .all(|c| c.is_whitespace() && c != '\n')
22124}
22125
22126fn update_uncommitted_diff_for_buffer(
22127 editor: Entity<Editor>,
22128 project: &Entity<Project>,
22129 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22130 buffer: Entity<MultiBuffer>,
22131 cx: &mut App,
22132) -> Task<()> {
22133 let mut tasks = Vec::new();
22134 project.update(cx, |project, cx| {
22135 for buffer in buffers {
22136 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22137 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22138 }
22139 }
22140 });
22141 cx.spawn(async move |cx| {
22142 let diffs = future::join_all(tasks).await;
22143 if editor
22144 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22145 .unwrap_or(false)
22146 {
22147 return;
22148 }
22149
22150 buffer
22151 .update(cx, |buffer, cx| {
22152 for diff in diffs.into_iter().flatten() {
22153 buffer.add_diff(diff, cx);
22154 }
22155 })
22156 .ok();
22157 })
22158}
22159
22160fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22161 let tab_size = tab_size.get() as usize;
22162 let mut width = offset;
22163
22164 for ch in text.chars() {
22165 width += if ch == '\t' {
22166 tab_size - (width % tab_size)
22167 } else {
22168 1
22169 };
22170 }
22171
22172 width - offset
22173}
22174
22175#[cfg(test)]
22176mod tests {
22177 use super::*;
22178
22179 #[test]
22180 fn test_string_size_with_expanded_tabs() {
22181 let nz = |val| NonZeroU32::new(val).unwrap();
22182 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22183 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22184 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22185 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22186 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22187 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22188 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22189 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22190 }
22191}
22192
22193/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22194struct WordBreakingTokenizer<'a> {
22195 input: &'a str,
22196}
22197
22198impl<'a> WordBreakingTokenizer<'a> {
22199 fn new(input: &'a str) -> Self {
22200 Self { input }
22201 }
22202}
22203
22204fn is_char_ideographic(ch: char) -> bool {
22205 use unicode_script::Script::*;
22206 use unicode_script::UnicodeScript;
22207 matches!(ch.script(), Han | Tangut | Yi)
22208}
22209
22210fn is_grapheme_ideographic(text: &str) -> bool {
22211 text.chars().any(is_char_ideographic)
22212}
22213
22214fn is_grapheme_whitespace(text: &str) -> bool {
22215 text.chars().any(|x| x.is_whitespace())
22216}
22217
22218fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22219 text.chars()
22220 .next()
22221 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22222}
22223
22224#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22225enum WordBreakToken<'a> {
22226 Word { token: &'a str, grapheme_len: usize },
22227 InlineWhitespace { token: &'a str, grapheme_len: usize },
22228 Newline,
22229}
22230
22231impl<'a> Iterator for WordBreakingTokenizer<'a> {
22232 /// Yields a span, the count of graphemes in the token, and whether it was
22233 /// whitespace. Note that it also breaks at word boundaries.
22234 type Item = WordBreakToken<'a>;
22235
22236 fn next(&mut self) -> Option<Self::Item> {
22237 use unicode_segmentation::UnicodeSegmentation;
22238 if self.input.is_empty() {
22239 return None;
22240 }
22241
22242 let mut iter = self.input.graphemes(true).peekable();
22243 let mut offset = 0;
22244 let mut grapheme_len = 0;
22245 if let Some(first_grapheme) = iter.next() {
22246 let is_newline = first_grapheme == "\n";
22247 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22248 offset += first_grapheme.len();
22249 grapheme_len += 1;
22250 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22251 if let Some(grapheme) = iter.peek().copied()
22252 && should_stay_with_preceding_ideograph(grapheme)
22253 {
22254 offset += grapheme.len();
22255 grapheme_len += 1;
22256 }
22257 } else {
22258 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22259 let mut next_word_bound = words.peek().copied();
22260 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22261 next_word_bound = words.next();
22262 }
22263 while let Some(grapheme) = iter.peek().copied() {
22264 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22265 break;
22266 };
22267 if is_grapheme_whitespace(grapheme) != is_whitespace
22268 || (grapheme == "\n") != is_newline
22269 {
22270 break;
22271 };
22272 offset += grapheme.len();
22273 grapheme_len += 1;
22274 iter.next();
22275 }
22276 }
22277 let token = &self.input[..offset];
22278 self.input = &self.input[offset..];
22279 if token == "\n" {
22280 Some(WordBreakToken::Newline)
22281 } else if is_whitespace {
22282 Some(WordBreakToken::InlineWhitespace {
22283 token,
22284 grapheme_len,
22285 })
22286 } else {
22287 Some(WordBreakToken::Word {
22288 token,
22289 grapheme_len,
22290 })
22291 }
22292 } else {
22293 None
22294 }
22295 }
22296}
22297
22298#[test]
22299fn test_word_breaking_tokenizer() {
22300 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22301 ("", &[]),
22302 (" ", &[whitespace(" ", 2)]),
22303 ("Ʒ", &[word("Ʒ", 1)]),
22304 ("Ǽ", &[word("Ǽ", 1)]),
22305 ("⋑", &[word("⋑", 1)]),
22306 ("⋑⋑", &[word("⋑⋑", 2)]),
22307 (
22308 "原理,进而",
22309 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22310 ),
22311 (
22312 "hello world",
22313 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22314 ),
22315 (
22316 "hello, world",
22317 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22318 ),
22319 (
22320 " hello world",
22321 &[
22322 whitespace(" ", 2),
22323 word("hello", 5),
22324 whitespace(" ", 1),
22325 word("world", 5),
22326 ],
22327 ),
22328 (
22329 "这是什么 \n 钢笔",
22330 &[
22331 word("这", 1),
22332 word("是", 1),
22333 word("什", 1),
22334 word("么", 1),
22335 whitespace(" ", 1),
22336 newline(),
22337 whitespace(" ", 1),
22338 word("钢", 1),
22339 word("笔", 1),
22340 ],
22341 ),
22342 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22343 ];
22344
22345 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22346 WordBreakToken::Word {
22347 token,
22348 grapheme_len,
22349 }
22350 }
22351
22352 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22353 WordBreakToken::InlineWhitespace {
22354 token,
22355 grapheme_len,
22356 }
22357 }
22358
22359 fn newline() -> WordBreakToken<'static> {
22360 WordBreakToken::Newline
22361 }
22362
22363 for (input, result) in tests {
22364 assert_eq!(
22365 WordBreakingTokenizer::new(input)
22366 .collect::<Vec<_>>()
22367 .as_slice(),
22368 *result,
22369 );
22370 }
22371}
22372
22373fn wrap_with_prefix(
22374 first_line_prefix: String,
22375 subsequent_lines_prefix: String,
22376 unwrapped_text: String,
22377 wrap_column: usize,
22378 tab_size: NonZeroU32,
22379 preserve_existing_whitespace: bool,
22380) -> String {
22381 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22382 let subsequent_lines_prefix_len =
22383 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22384 let mut wrapped_text = String::new();
22385 let mut current_line = first_line_prefix;
22386 let mut is_first_line = true;
22387
22388 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22389 let mut current_line_len = first_line_prefix_len;
22390 let mut in_whitespace = false;
22391 for token in tokenizer {
22392 let have_preceding_whitespace = in_whitespace;
22393 match token {
22394 WordBreakToken::Word {
22395 token,
22396 grapheme_len,
22397 } => {
22398 in_whitespace = false;
22399 let current_prefix_len = if is_first_line {
22400 first_line_prefix_len
22401 } else {
22402 subsequent_lines_prefix_len
22403 };
22404 if current_line_len + grapheme_len > wrap_column
22405 && current_line_len != current_prefix_len
22406 {
22407 wrapped_text.push_str(current_line.trim_end());
22408 wrapped_text.push('\n');
22409 is_first_line = false;
22410 current_line = subsequent_lines_prefix.clone();
22411 current_line_len = subsequent_lines_prefix_len;
22412 }
22413 current_line.push_str(token);
22414 current_line_len += grapheme_len;
22415 }
22416 WordBreakToken::InlineWhitespace {
22417 mut token,
22418 mut grapheme_len,
22419 } => {
22420 in_whitespace = true;
22421 if have_preceding_whitespace && !preserve_existing_whitespace {
22422 continue;
22423 }
22424 if !preserve_existing_whitespace {
22425 // Keep a single whitespace grapheme as-is
22426 if let Some(first) =
22427 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22428 {
22429 token = first;
22430 } else {
22431 token = " ";
22432 }
22433 grapheme_len = 1;
22434 }
22435 let current_prefix_len = if is_first_line {
22436 first_line_prefix_len
22437 } else {
22438 subsequent_lines_prefix_len
22439 };
22440 if current_line_len + grapheme_len > wrap_column {
22441 wrapped_text.push_str(current_line.trim_end());
22442 wrapped_text.push('\n');
22443 is_first_line = false;
22444 current_line = subsequent_lines_prefix.clone();
22445 current_line_len = subsequent_lines_prefix_len;
22446 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22447 current_line.push_str(token);
22448 current_line_len += grapheme_len;
22449 }
22450 }
22451 WordBreakToken::Newline => {
22452 in_whitespace = true;
22453 let current_prefix_len = if is_first_line {
22454 first_line_prefix_len
22455 } else {
22456 subsequent_lines_prefix_len
22457 };
22458 if preserve_existing_whitespace {
22459 wrapped_text.push_str(current_line.trim_end());
22460 wrapped_text.push('\n');
22461 is_first_line = false;
22462 current_line = subsequent_lines_prefix.clone();
22463 current_line_len = subsequent_lines_prefix_len;
22464 } else if have_preceding_whitespace {
22465 continue;
22466 } else if current_line_len + 1 > wrap_column
22467 && current_line_len != current_prefix_len
22468 {
22469 wrapped_text.push_str(current_line.trim_end());
22470 wrapped_text.push('\n');
22471 is_first_line = false;
22472 current_line = subsequent_lines_prefix.clone();
22473 current_line_len = subsequent_lines_prefix_len;
22474 } else if current_line_len != current_prefix_len {
22475 current_line.push(' ');
22476 current_line_len += 1;
22477 }
22478 }
22479 }
22480 }
22481
22482 if !current_line.is_empty() {
22483 wrapped_text.push_str(¤t_line);
22484 }
22485 wrapped_text
22486}
22487
22488#[test]
22489fn test_wrap_with_prefix() {
22490 assert_eq!(
22491 wrap_with_prefix(
22492 "# ".to_string(),
22493 "# ".to_string(),
22494 "abcdefg".to_string(),
22495 4,
22496 NonZeroU32::new(4).unwrap(),
22497 false,
22498 ),
22499 "# abcdefg"
22500 );
22501 assert_eq!(
22502 wrap_with_prefix(
22503 "".to_string(),
22504 "".to_string(),
22505 "\thello world".to_string(),
22506 8,
22507 NonZeroU32::new(4).unwrap(),
22508 false,
22509 ),
22510 "hello\nworld"
22511 );
22512 assert_eq!(
22513 wrap_with_prefix(
22514 "// ".to_string(),
22515 "// ".to_string(),
22516 "xx \nyy zz aa bb cc".to_string(),
22517 12,
22518 NonZeroU32::new(4).unwrap(),
22519 false,
22520 ),
22521 "// xx yy zz\n// aa bb cc"
22522 );
22523 assert_eq!(
22524 wrap_with_prefix(
22525 String::new(),
22526 String::new(),
22527 "这是什么 \n 钢笔".to_string(),
22528 3,
22529 NonZeroU32::new(4).unwrap(),
22530 false,
22531 ),
22532 "这是什\n么 钢\n笔"
22533 );
22534 assert_eq!(
22535 wrap_with_prefix(
22536 String::new(),
22537 String::new(),
22538 format!("foo{}bar", '\u{2009}'), // thin space
22539 80,
22540 NonZeroU32::new(4).unwrap(),
22541 false,
22542 ),
22543 format!("foo{}bar", '\u{2009}')
22544 );
22545}
22546
22547pub trait CollaborationHub {
22548 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22549 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22550 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22551}
22552
22553impl CollaborationHub for Entity<Project> {
22554 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22555 self.read(cx).collaborators()
22556 }
22557
22558 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22559 self.read(cx).user_store().read(cx).participant_indices()
22560 }
22561
22562 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22563 let this = self.read(cx);
22564 let user_ids = this.collaborators().values().map(|c| c.user_id);
22565 this.user_store().read(cx).participant_names(user_ids, cx)
22566 }
22567}
22568
22569pub trait SemanticsProvider {
22570 fn hover(
22571 &self,
22572 buffer: &Entity<Buffer>,
22573 position: text::Anchor,
22574 cx: &mut App,
22575 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22576
22577 fn inline_values(
22578 &self,
22579 buffer_handle: Entity<Buffer>,
22580 range: Range<text::Anchor>,
22581 cx: &mut App,
22582 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22583
22584 fn inlay_hints(
22585 &self,
22586 buffer_handle: Entity<Buffer>,
22587 range: Range<text::Anchor>,
22588 cx: &mut App,
22589 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22590
22591 fn resolve_inlay_hint(
22592 &self,
22593 hint: InlayHint,
22594 buffer_handle: Entity<Buffer>,
22595 server_id: LanguageServerId,
22596 cx: &mut App,
22597 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22598
22599 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22600
22601 fn document_highlights(
22602 &self,
22603 buffer: &Entity<Buffer>,
22604 position: text::Anchor,
22605 cx: &mut App,
22606 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22607
22608 fn definitions(
22609 &self,
22610 buffer: &Entity<Buffer>,
22611 position: text::Anchor,
22612 kind: GotoDefinitionKind,
22613 cx: &mut App,
22614 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22615
22616 fn range_for_rename(
22617 &self,
22618 buffer: &Entity<Buffer>,
22619 position: text::Anchor,
22620 cx: &mut App,
22621 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22622
22623 fn perform_rename(
22624 &self,
22625 buffer: &Entity<Buffer>,
22626 position: text::Anchor,
22627 new_name: String,
22628 cx: &mut App,
22629 ) -> Option<Task<Result<ProjectTransaction>>>;
22630}
22631
22632pub trait CompletionProvider {
22633 fn completions(
22634 &self,
22635 excerpt_id: ExcerptId,
22636 buffer: &Entity<Buffer>,
22637 buffer_position: text::Anchor,
22638 trigger: CompletionContext,
22639 window: &mut Window,
22640 cx: &mut Context<Editor>,
22641 ) -> Task<Result<Vec<CompletionResponse>>>;
22642
22643 fn resolve_completions(
22644 &self,
22645 _buffer: Entity<Buffer>,
22646 _completion_indices: Vec<usize>,
22647 _completions: Rc<RefCell<Box<[Completion]>>>,
22648 _cx: &mut Context<Editor>,
22649 ) -> Task<Result<bool>> {
22650 Task::ready(Ok(false))
22651 }
22652
22653 fn apply_additional_edits_for_completion(
22654 &self,
22655 _buffer: Entity<Buffer>,
22656 _completions: Rc<RefCell<Box<[Completion]>>>,
22657 _completion_index: usize,
22658 _push_to_history: bool,
22659 _cx: &mut Context<Editor>,
22660 ) -> Task<Result<Option<language::Transaction>>> {
22661 Task::ready(Ok(None))
22662 }
22663
22664 fn is_completion_trigger(
22665 &self,
22666 buffer: &Entity<Buffer>,
22667 position: language::Anchor,
22668 text: &str,
22669 trigger_in_words: bool,
22670 menu_is_open: bool,
22671 cx: &mut Context<Editor>,
22672 ) -> bool;
22673
22674 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22675
22676 fn sort_completions(&self) -> bool {
22677 true
22678 }
22679
22680 fn filter_completions(&self) -> bool {
22681 true
22682 }
22683}
22684
22685pub trait CodeActionProvider {
22686 fn id(&self) -> Arc<str>;
22687
22688 fn code_actions(
22689 &self,
22690 buffer: &Entity<Buffer>,
22691 range: Range<text::Anchor>,
22692 window: &mut Window,
22693 cx: &mut App,
22694 ) -> Task<Result<Vec<CodeAction>>>;
22695
22696 fn apply_code_action(
22697 &self,
22698 buffer_handle: Entity<Buffer>,
22699 action: CodeAction,
22700 excerpt_id: ExcerptId,
22701 push_to_history: bool,
22702 window: &mut Window,
22703 cx: &mut App,
22704 ) -> Task<Result<ProjectTransaction>>;
22705}
22706
22707impl CodeActionProvider for Entity<Project> {
22708 fn id(&self) -> Arc<str> {
22709 "project".into()
22710 }
22711
22712 fn code_actions(
22713 &self,
22714 buffer: &Entity<Buffer>,
22715 range: Range<text::Anchor>,
22716 _window: &mut Window,
22717 cx: &mut App,
22718 ) -> Task<Result<Vec<CodeAction>>> {
22719 self.update(cx, |project, cx| {
22720 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22721 let code_actions = project.code_actions(buffer, range, None, cx);
22722 cx.background_spawn(async move {
22723 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22724 Ok(code_lens_actions
22725 .context("code lens fetch")?
22726 .into_iter()
22727 .flatten()
22728 .chain(
22729 code_actions
22730 .context("code action fetch")?
22731 .into_iter()
22732 .flatten(),
22733 )
22734 .collect())
22735 })
22736 })
22737 }
22738
22739 fn apply_code_action(
22740 &self,
22741 buffer_handle: Entity<Buffer>,
22742 action: CodeAction,
22743 _excerpt_id: ExcerptId,
22744 push_to_history: bool,
22745 _window: &mut Window,
22746 cx: &mut App,
22747 ) -> Task<Result<ProjectTransaction>> {
22748 self.update(cx, |project, cx| {
22749 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22750 })
22751 }
22752}
22753
22754fn snippet_completions(
22755 project: &Project,
22756 buffer: &Entity<Buffer>,
22757 buffer_position: text::Anchor,
22758 cx: &mut App,
22759) -> Task<Result<CompletionResponse>> {
22760 let languages = buffer.read(cx).languages_at(buffer_position);
22761 let snippet_store = project.snippets().read(cx);
22762
22763 let scopes: Vec<_> = languages
22764 .iter()
22765 .filter_map(|language| {
22766 let language_name = language.lsp_id();
22767 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22768
22769 if snippets.is_empty() {
22770 None
22771 } else {
22772 Some((language.default_scope(), snippets))
22773 }
22774 })
22775 .collect();
22776
22777 if scopes.is_empty() {
22778 return Task::ready(Ok(CompletionResponse {
22779 completions: vec![],
22780 display_options: CompletionDisplayOptions::default(),
22781 is_incomplete: false,
22782 }));
22783 }
22784
22785 let snapshot = buffer.read(cx).text_snapshot();
22786 let chars: String = snapshot
22787 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22788 .collect();
22789 let executor = cx.background_executor().clone();
22790
22791 cx.background_spawn(async move {
22792 let mut is_incomplete = false;
22793 let mut completions: Vec<Completion> = Vec::new();
22794 for (scope, snippets) in scopes.into_iter() {
22795 let classifier =
22796 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22797 let mut last_word = chars
22798 .chars()
22799 .take_while(|c| classifier.is_word(*c))
22800 .collect::<String>();
22801 last_word = last_word.chars().rev().collect();
22802
22803 if last_word.is_empty() {
22804 return Ok(CompletionResponse {
22805 completions: vec![],
22806 display_options: CompletionDisplayOptions::default(),
22807 is_incomplete: true,
22808 });
22809 }
22810
22811 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22812 let to_lsp = |point: &text::Anchor| {
22813 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22814 point_to_lsp(end)
22815 };
22816 let lsp_end = to_lsp(&buffer_position);
22817
22818 let candidates = snippets
22819 .iter()
22820 .enumerate()
22821 .flat_map(|(ix, snippet)| {
22822 snippet
22823 .prefix
22824 .iter()
22825 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22826 })
22827 .collect::<Vec<StringMatchCandidate>>();
22828
22829 const MAX_RESULTS: usize = 100;
22830 let mut matches = fuzzy::match_strings(
22831 &candidates,
22832 &last_word,
22833 last_word.chars().any(|c| c.is_uppercase()),
22834 true,
22835 MAX_RESULTS,
22836 &Default::default(),
22837 executor.clone(),
22838 )
22839 .await;
22840
22841 if matches.len() >= MAX_RESULTS {
22842 is_incomplete = true;
22843 }
22844
22845 // Remove all candidates where the query's start does not match the start of any word in the candidate
22846 if let Some(query_start) = last_word.chars().next() {
22847 matches.retain(|string_match| {
22848 split_words(&string_match.string).any(|word| {
22849 // Check that the first codepoint of the word as lowercase matches the first
22850 // codepoint of the query as lowercase
22851 word.chars()
22852 .flat_map(|codepoint| codepoint.to_lowercase())
22853 .zip(query_start.to_lowercase())
22854 .all(|(word_cp, query_cp)| word_cp == query_cp)
22855 })
22856 });
22857 }
22858
22859 let matched_strings = matches
22860 .into_iter()
22861 .map(|m| m.string)
22862 .collect::<HashSet<_>>();
22863
22864 completions.extend(snippets.iter().filter_map(|snippet| {
22865 let matching_prefix = snippet
22866 .prefix
22867 .iter()
22868 .find(|prefix| matched_strings.contains(*prefix))?;
22869 let start = as_offset - last_word.len();
22870 let start = snapshot.anchor_before(start);
22871 let range = start..buffer_position;
22872 let lsp_start = to_lsp(&start);
22873 let lsp_range = lsp::Range {
22874 start: lsp_start,
22875 end: lsp_end,
22876 };
22877 Some(Completion {
22878 replace_range: range,
22879 new_text: snippet.body.clone(),
22880 source: CompletionSource::Lsp {
22881 insert_range: None,
22882 server_id: LanguageServerId(usize::MAX),
22883 resolved: true,
22884 lsp_completion: Box::new(lsp::CompletionItem {
22885 label: snippet.prefix.first().unwrap().clone(),
22886 kind: Some(CompletionItemKind::SNIPPET),
22887 label_details: snippet.description.as_ref().map(|description| {
22888 lsp::CompletionItemLabelDetails {
22889 detail: Some(description.clone()),
22890 description: None,
22891 }
22892 }),
22893 insert_text_format: Some(InsertTextFormat::SNIPPET),
22894 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22895 lsp::InsertReplaceEdit {
22896 new_text: snippet.body.clone(),
22897 insert: lsp_range,
22898 replace: lsp_range,
22899 },
22900 )),
22901 filter_text: Some(snippet.body.clone()),
22902 sort_text: Some(char::MAX.to_string()),
22903 ..lsp::CompletionItem::default()
22904 }),
22905 lsp_defaults: None,
22906 },
22907 label: CodeLabel {
22908 text: matching_prefix.clone(),
22909 runs: Vec::new(),
22910 filter_range: 0..matching_prefix.len(),
22911 },
22912 icon_path: None,
22913 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22914 single_line: snippet.name.clone().into(),
22915 plain_text: snippet
22916 .description
22917 .clone()
22918 .map(|description| description.into()),
22919 }),
22920 insert_text_mode: None,
22921 confirm: None,
22922 })
22923 }))
22924 }
22925
22926 Ok(CompletionResponse {
22927 completions,
22928 display_options: CompletionDisplayOptions::default(),
22929 is_incomplete,
22930 })
22931 })
22932}
22933
22934impl CompletionProvider for Entity<Project> {
22935 fn completions(
22936 &self,
22937 _excerpt_id: ExcerptId,
22938 buffer: &Entity<Buffer>,
22939 buffer_position: text::Anchor,
22940 options: CompletionContext,
22941 _window: &mut Window,
22942 cx: &mut Context<Editor>,
22943 ) -> Task<Result<Vec<CompletionResponse>>> {
22944 self.update(cx, |project, cx| {
22945 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22946 let project_completions = project.completions(buffer, buffer_position, options, cx);
22947 cx.background_spawn(async move {
22948 let mut responses = project_completions.await?;
22949 let snippets = snippets.await?;
22950 if !snippets.completions.is_empty() {
22951 responses.push(snippets);
22952 }
22953 Ok(responses)
22954 })
22955 })
22956 }
22957
22958 fn resolve_completions(
22959 &self,
22960 buffer: Entity<Buffer>,
22961 completion_indices: Vec<usize>,
22962 completions: Rc<RefCell<Box<[Completion]>>>,
22963 cx: &mut Context<Editor>,
22964 ) -> Task<Result<bool>> {
22965 self.update(cx, |project, cx| {
22966 project.lsp_store().update(cx, |lsp_store, cx| {
22967 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22968 })
22969 })
22970 }
22971
22972 fn apply_additional_edits_for_completion(
22973 &self,
22974 buffer: Entity<Buffer>,
22975 completions: Rc<RefCell<Box<[Completion]>>>,
22976 completion_index: usize,
22977 push_to_history: bool,
22978 cx: &mut Context<Editor>,
22979 ) -> Task<Result<Option<language::Transaction>>> {
22980 self.update(cx, |project, cx| {
22981 project.lsp_store().update(cx, |lsp_store, cx| {
22982 lsp_store.apply_additional_edits_for_completion(
22983 buffer,
22984 completions,
22985 completion_index,
22986 push_to_history,
22987 cx,
22988 )
22989 })
22990 })
22991 }
22992
22993 fn is_completion_trigger(
22994 &self,
22995 buffer: &Entity<Buffer>,
22996 position: language::Anchor,
22997 text: &str,
22998 trigger_in_words: bool,
22999 menu_is_open: bool,
23000 cx: &mut Context<Editor>,
23001 ) -> bool {
23002 let mut chars = text.chars();
23003 let char = if let Some(char) = chars.next() {
23004 char
23005 } else {
23006 return false;
23007 };
23008 if chars.next().is_some() {
23009 return false;
23010 }
23011
23012 let buffer = buffer.read(cx);
23013 let snapshot = buffer.snapshot();
23014 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23015 return false;
23016 }
23017 let classifier = snapshot
23018 .char_classifier_at(position)
23019 .scope_context(Some(CharScopeContext::Completion));
23020 if trigger_in_words && classifier.is_word(char) {
23021 return true;
23022 }
23023
23024 buffer.completion_triggers().contains(text)
23025 }
23026}
23027
23028impl SemanticsProvider for Entity<Project> {
23029 fn hover(
23030 &self,
23031 buffer: &Entity<Buffer>,
23032 position: text::Anchor,
23033 cx: &mut App,
23034 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23035 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23036 }
23037
23038 fn document_highlights(
23039 &self,
23040 buffer: &Entity<Buffer>,
23041 position: text::Anchor,
23042 cx: &mut App,
23043 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23044 Some(self.update(cx, |project, cx| {
23045 project.document_highlights(buffer, position, cx)
23046 }))
23047 }
23048
23049 fn definitions(
23050 &self,
23051 buffer: &Entity<Buffer>,
23052 position: text::Anchor,
23053 kind: GotoDefinitionKind,
23054 cx: &mut App,
23055 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23056 Some(self.update(cx, |project, cx| match kind {
23057 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23058 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23059 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23060 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23061 }))
23062 }
23063
23064 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23065 self.update(cx, |project, cx| {
23066 if project
23067 .active_debug_session(cx)
23068 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23069 {
23070 return true;
23071 }
23072
23073 buffer.update(cx, |buffer, cx| {
23074 project.any_language_server_supports_inlay_hints(buffer, cx)
23075 })
23076 })
23077 }
23078
23079 fn inline_values(
23080 &self,
23081 buffer_handle: Entity<Buffer>,
23082 range: Range<text::Anchor>,
23083 cx: &mut App,
23084 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23085 self.update(cx, |project, cx| {
23086 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23087
23088 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23089 })
23090 }
23091
23092 fn inlay_hints(
23093 &self,
23094 buffer_handle: Entity<Buffer>,
23095 range: Range<text::Anchor>,
23096 cx: &mut App,
23097 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23098 Some(self.update(cx, |project, cx| {
23099 project.inlay_hints(buffer_handle, range, cx)
23100 }))
23101 }
23102
23103 fn resolve_inlay_hint(
23104 &self,
23105 hint: InlayHint,
23106 buffer_handle: Entity<Buffer>,
23107 server_id: LanguageServerId,
23108 cx: &mut App,
23109 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23110 Some(self.update(cx, |project, cx| {
23111 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23112 }))
23113 }
23114
23115 fn range_for_rename(
23116 &self,
23117 buffer: &Entity<Buffer>,
23118 position: text::Anchor,
23119 cx: &mut App,
23120 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23121 Some(self.update(cx, |project, cx| {
23122 let buffer = buffer.clone();
23123 let task = project.prepare_rename(buffer.clone(), position, cx);
23124 cx.spawn(async move |_, cx| {
23125 Ok(match task.await? {
23126 PrepareRenameResponse::Success(range) => Some(range),
23127 PrepareRenameResponse::InvalidPosition => None,
23128 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23129 // Fallback on using TreeSitter info to determine identifier range
23130 buffer.read_with(cx, |buffer, _| {
23131 let snapshot = buffer.snapshot();
23132 let (range, kind) = snapshot.surrounding_word(position, None);
23133 if kind != Some(CharKind::Word) {
23134 return None;
23135 }
23136 Some(
23137 snapshot.anchor_before(range.start)
23138 ..snapshot.anchor_after(range.end),
23139 )
23140 })?
23141 }
23142 })
23143 })
23144 }))
23145 }
23146
23147 fn perform_rename(
23148 &self,
23149 buffer: &Entity<Buffer>,
23150 position: text::Anchor,
23151 new_name: String,
23152 cx: &mut App,
23153 ) -> Option<Task<Result<ProjectTransaction>>> {
23154 Some(self.update(cx, |project, cx| {
23155 project.perform_rename(buffer.clone(), position, new_name, cx)
23156 }))
23157 }
23158}
23159
23160fn inlay_hint_settings(
23161 location: Anchor,
23162 snapshot: &MultiBufferSnapshot,
23163 cx: &mut Context<Editor>,
23164) -> InlayHintSettings {
23165 let file = snapshot.file_at(location);
23166 let language = snapshot.language_at(location).map(|l| l.name());
23167 language_settings(language, file, cx).inlay_hints
23168}
23169
23170fn consume_contiguous_rows(
23171 contiguous_row_selections: &mut Vec<Selection<Point>>,
23172 selection: &Selection<Point>,
23173 display_map: &DisplaySnapshot,
23174 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23175) -> (MultiBufferRow, MultiBufferRow) {
23176 contiguous_row_selections.push(selection.clone());
23177 let start_row = starting_row(selection, display_map);
23178 let mut end_row = ending_row(selection, display_map);
23179
23180 while let Some(next_selection) = selections.peek() {
23181 if next_selection.start.row <= end_row.0 {
23182 end_row = ending_row(next_selection, display_map);
23183 contiguous_row_selections.push(selections.next().unwrap().clone());
23184 } else {
23185 break;
23186 }
23187 }
23188 (start_row, end_row)
23189}
23190
23191fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23192 if selection.start.column > 0 {
23193 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23194 } else {
23195 MultiBufferRow(selection.start.row)
23196 }
23197}
23198
23199fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23200 if next_selection.end.column > 0 || next_selection.is_empty() {
23201 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23202 } else {
23203 MultiBufferRow(next_selection.end.row)
23204 }
23205}
23206
23207impl EditorSnapshot {
23208 pub fn remote_selections_in_range<'a>(
23209 &'a self,
23210 range: &'a Range<Anchor>,
23211 collaboration_hub: &dyn CollaborationHub,
23212 cx: &'a App,
23213 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23214 let participant_names = collaboration_hub.user_names(cx);
23215 let participant_indices = collaboration_hub.user_participant_indices(cx);
23216 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23217 let collaborators_by_replica_id = collaborators_by_peer_id
23218 .values()
23219 .map(|collaborator| (collaborator.replica_id, collaborator))
23220 .collect::<HashMap<_, _>>();
23221 self.buffer_snapshot
23222 .selections_in_range(range, false)
23223 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23224 if replica_id == AGENT_REPLICA_ID {
23225 Some(RemoteSelection {
23226 replica_id,
23227 selection,
23228 cursor_shape,
23229 line_mode,
23230 collaborator_id: CollaboratorId::Agent,
23231 user_name: Some("Agent".into()),
23232 color: cx.theme().players().agent(),
23233 })
23234 } else {
23235 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23236 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23237 let user_name = participant_names.get(&collaborator.user_id).cloned();
23238 Some(RemoteSelection {
23239 replica_id,
23240 selection,
23241 cursor_shape,
23242 line_mode,
23243 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23244 user_name,
23245 color: if let Some(index) = participant_index {
23246 cx.theme().players().color_for_participant(index.0)
23247 } else {
23248 cx.theme().players().absent()
23249 },
23250 })
23251 }
23252 })
23253 }
23254
23255 pub fn hunks_for_ranges(
23256 &self,
23257 ranges: impl IntoIterator<Item = Range<Point>>,
23258 ) -> Vec<MultiBufferDiffHunk> {
23259 let mut hunks = Vec::new();
23260 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23261 HashMap::default();
23262 for query_range in ranges {
23263 let query_rows =
23264 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23265 for hunk in self.buffer_snapshot.diff_hunks_in_range(
23266 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23267 ) {
23268 // Include deleted hunks that are adjacent to the query range, because
23269 // otherwise they would be missed.
23270 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23271 if hunk.status().is_deleted() {
23272 intersects_range |= hunk.row_range.start == query_rows.end;
23273 intersects_range |= hunk.row_range.end == query_rows.start;
23274 }
23275 if intersects_range {
23276 if !processed_buffer_rows
23277 .entry(hunk.buffer_id)
23278 .or_default()
23279 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23280 {
23281 continue;
23282 }
23283 hunks.push(hunk);
23284 }
23285 }
23286 }
23287
23288 hunks
23289 }
23290
23291 fn display_diff_hunks_for_rows<'a>(
23292 &'a self,
23293 display_rows: Range<DisplayRow>,
23294 folded_buffers: &'a HashSet<BufferId>,
23295 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23296 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23297 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23298
23299 self.buffer_snapshot
23300 .diff_hunks_in_range(buffer_start..buffer_end)
23301 .filter_map(|hunk| {
23302 if folded_buffers.contains(&hunk.buffer_id) {
23303 return None;
23304 }
23305
23306 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23307 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23308
23309 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23310 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23311
23312 let display_hunk = if hunk_display_start.column() != 0 {
23313 DisplayDiffHunk::Folded {
23314 display_row: hunk_display_start.row(),
23315 }
23316 } else {
23317 let mut end_row = hunk_display_end.row();
23318 if hunk_display_end.column() > 0 {
23319 end_row.0 += 1;
23320 }
23321 let is_created_file = hunk.is_created_file();
23322 DisplayDiffHunk::Unfolded {
23323 status: hunk.status(),
23324 diff_base_byte_range: hunk.diff_base_byte_range,
23325 display_row_range: hunk_display_start.row()..end_row,
23326 multi_buffer_range: Anchor::range_in_buffer(
23327 hunk.excerpt_id,
23328 hunk.buffer_id,
23329 hunk.buffer_range,
23330 ),
23331 is_created_file,
23332 }
23333 };
23334
23335 Some(display_hunk)
23336 })
23337 }
23338
23339 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23340 self.display_snapshot.buffer_snapshot.language_at(position)
23341 }
23342
23343 pub fn is_focused(&self) -> bool {
23344 self.is_focused
23345 }
23346
23347 pub fn placeholder_text(&self) -> Option<String> {
23348 self.placeholder_display_snapshot
23349 .as_ref()
23350 .map(|display_map| display_map.text())
23351 }
23352
23353 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23354 self.scroll_anchor.scroll_position(&self.display_snapshot)
23355 }
23356
23357 fn gutter_dimensions(
23358 &self,
23359 font_id: FontId,
23360 font_size: Pixels,
23361 max_line_number_width: Pixels,
23362 cx: &App,
23363 ) -> Option<GutterDimensions> {
23364 if !self.show_gutter {
23365 return None;
23366 }
23367
23368 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23369 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23370
23371 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23372 matches!(
23373 ProjectSettings::get_global(cx).git.git_gutter,
23374 GitGutterSetting::TrackedFiles
23375 )
23376 });
23377 let gutter_settings = EditorSettings::get_global(cx).gutter;
23378 let show_line_numbers = self
23379 .show_line_numbers
23380 .unwrap_or(gutter_settings.line_numbers);
23381 let line_gutter_width = if show_line_numbers {
23382 // Avoid flicker-like gutter resizes when the line number gains another digit by
23383 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23384 let min_width_for_number_on_gutter =
23385 ch_advance * gutter_settings.min_line_number_digits as f32;
23386 max_line_number_width.max(min_width_for_number_on_gutter)
23387 } else {
23388 0.0.into()
23389 };
23390
23391 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23392 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23393
23394 let git_blame_entries_width =
23395 self.git_blame_gutter_max_author_length
23396 .map(|max_author_length| {
23397 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23398 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23399
23400 /// The number of characters to dedicate to gaps and margins.
23401 const SPACING_WIDTH: usize = 4;
23402
23403 let max_char_count = max_author_length.min(renderer.max_author_length())
23404 + ::git::SHORT_SHA_LENGTH
23405 + MAX_RELATIVE_TIMESTAMP.len()
23406 + SPACING_WIDTH;
23407
23408 ch_advance * max_char_count
23409 });
23410
23411 let is_singleton = self.buffer_snapshot.is_singleton();
23412
23413 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23414 left_padding += if !is_singleton {
23415 ch_width * 4.0
23416 } else if show_runnables || show_breakpoints {
23417 ch_width * 3.0
23418 } else if show_git_gutter && show_line_numbers {
23419 ch_width * 2.0
23420 } else if show_git_gutter || show_line_numbers {
23421 ch_width
23422 } else {
23423 px(0.)
23424 };
23425
23426 let shows_folds = is_singleton && gutter_settings.folds;
23427
23428 let right_padding = if shows_folds && show_line_numbers {
23429 ch_width * 4.0
23430 } else if shows_folds || (!is_singleton && show_line_numbers) {
23431 ch_width * 3.0
23432 } else if show_line_numbers {
23433 ch_width
23434 } else {
23435 px(0.)
23436 };
23437
23438 Some(GutterDimensions {
23439 left_padding,
23440 right_padding,
23441 width: line_gutter_width + left_padding + right_padding,
23442 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23443 git_blame_entries_width,
23444 })
23445 }
23446
23447 pub fn render_crease_toggle(
23448 &self,
23449 buffer_row: MultiBufferRow,
23450 row_contains_cursor: bool,
23451 editor: Entity<Editor>,
23452 window: &mut Window,
23453 cx: &mut App,
23454 ) -> Option<AnyElement> {
23455 let folded = self.is_line_folded(buffer_row);
23456 let mut is_foldable = false;
23457
23458 if let Some(crease) = self
23459 .crease_snapshot
23460 .query_row(buffer_row, &self.buffer_snapshot)
23461 {
23462 is_foldable = true;
23463 match crease {
23464 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23465 if let Some(render_toggle) = render_toggle {
23466 let toggle_callback =
23467 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23468 if folded {
23469 editor.update(cx, |editor, cx| {
23470 editor.fold_at(buffer_row, window, cx)
23471 });
23472 } else {
23473 editor.update(cx, |editor, cx| {
23474 editor.unfold_at(buffer_row, window, cx)
23475 });
23476 }
23477 });
23478 return Some((render_toggle)(
23479 buffer_row,
23480 folded,
23481 toggle_callback,
23482 window,
23483 cx,
23484 ));
23485 }
23486 }
23487 }
23488 }
23489
23490 is_foldable |= self.starts_indent(buffer_row);
23491
23492 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23493 Some(
23494 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23495 .toggle_state(folded)
23496 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23497 if folded {
23498 this.unfold_at(buffer_row, window, cx);
23499 } else {
23500 this.fold_at(buffer_row, window, cx);
23501 }
23502 }))
23503 .into_any_element(),
23504 )
23505 } else {
23506 None
23507 }
23508 }
23509
23510 pub fn render_crease_trailer(
23511 &self,
23512 buffer_row: MultiBufferRow,
23513 window: &mut Window,
23514 cx: &mut App,
23515 ) -> Option<AnyElement> {
23516 let folded = self.is_line_folded(buffer_row);
23517 if let Crease::Inline { render_trailer, .. } = self
23518 .crease_snapshot
23519 .query_row(buffer_row, &self.buffer_snapshot)?
23520 {
23521 let render_trailer = render_trailer.as_ref()?;
23522 Some(render_trailer(buffer_row, folded, window, cx))
23523 } else {
23524 None
23525 }
23526 }
23527}
23528
23529impl Deref for EditorSnapshot {
23530 type Target = DisplaySnapshot;
23531
23532 fn deref(&self) -> &Self::Target {
23533 &self.display_snapshot
23534 }
23535}
23536
23537#[derive(Clone, Debug, PartialEq, Eq)]
23538pub enum EditorEvent {
23539 InputIgnored {
23540 text: Arc<str>,
23541 },
23542 InputHandled {
23543 utf16_range_to_replace: Option<Range<isize>>,
23544 text: Arc<str>,
23545 },
23546 ExcerptsAdded {
23547 buffer: Entity<Buffer>,
23548 predecessor: ExcerptId,
23549 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23550 },
23551 ExcerptsRemoved {
23552 ids: Vec<ExcerptId>,
23553 removed_buffer_ids: Vec<BufferId>,
23554 },
23555 BufferFoldToggled {
23556 ids: Vec<ExcerptId>,
23557 folded: bool,
23558 },
23559 ExcerptsEdited {
23560 ids: Vec<ExcerptId>,
23561 },
23562 ExcerptsExpanded {
23563 ids: Vec<ExcerptId>,
23564 },
23565 BufferEdited,
23566 Edited {
23567 transaction_id: clock::Lamport,
23568 },
23569 Reparsed(BufferId),
23570 Focused,
23571 FocusedIn,
23572 Blurred,
23573 DirtyChanged,
23574 Saved,
23575 TitleChanged,
23576 SelectionsChanged {
23577 local: bool,
23578 },
23579 ScrollPositionChanged {
23580 local: bool,
23581 autoscroll: bool,
23582 },
23583 TransactionUndone {
23584 transaction_id: clock::Lamport,
23585 },
23586 TransactionBegun {
23587 transaction_id: clock::Lamport,
23588 },
23589 CursorShapeChanged,
23590 BreadcrumbsChanged,
23591 PushedToNavHistory {
23592 anchor: Anchor,
23593 is_deactivate: bool,
23594 },
23595}
23596
23597impl EventEmitter<EditorEvent> for Editor {}
23598
23599impl Focusable for Editor {
23600 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23601 self.focus_handle.clone()
23602 }
23603}
23604
23605impl Render for Editor {
23606 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23607 let settings = ThemeSettings::get_global(cx);
23608
23609 let mut text_style = match self.mode {
23610 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23611 color: cx.theme().colors().editor_foreground,
23612 font_family: settings.ui_font.family.clone(),
23613 font_features: settings.ui_font.features.clone(),
23614 font_fallbacks: settings.ui_font.fallbacks.clone(),
23615 font_size: rems(0.875).into(),
23616 font_weight: settings.ui_font.weight,
23617 line_height: relative(settings.buffer_line_height.value()),
23618 ..Default::default()
23619 },
23620 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23621 color: cx.theme().colors().editor_foreground,
23622 font_family: settings.buffer_font.family.clone(),
23623 font_features: settings.buffer_font.features.clone(),
23624 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23625 font_size: settings.buffer_font_size(cx).into(),
23626 font_weight: settings.buffer_font.weight,
23627 line_height: relative(settings.buffer_line_height.value()),
23628 ..Default::default()
23629 },
23630 };
23631 if let Some(text_style_refinement) = &self.text_style_refinement {
23632 text_style.refine(text_style_refinement)
23633 }
23634
23635 let background = match self.mode {
23636 EditorMode::SingleLine => cx.theme().system().transparent,
23637 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23638 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23639 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23640 };
23641
23642 EditorElement::new(
23643 &cx.entity(),
23644 EditorStyle {
23645 background,
23646 border: cx.theme().colors().border,
23647 local_player: cx.theme().players().local(),
23648 text: text_style,
23649 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23650 syntax: cx.theme().syntax().clone(),
23651 status: cx.theme().status().clone(),
23652 inlay_hints_style: make_inlay_hints_style(cx),
23653 edit_prediction_styles: make_suggestion_styles(cx),
23654 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23655 show_underlines: self.diagnostics_enabled(),
23656 },
23657 )
23658 }
23659}
23660
23661impl EntityInputHandler for Editor {
23662 fn text_for_range(
23663 &mut self,
23664 range_utf16: Range<usize>,
23665 adjusted_range: &mut Option<Range<usize>>,
23666 _: &mut Window,
23667 cx: &mut Context<Self>,
23668 ) -> Option<String> {
23669 let snapshot = self.buffer.read(cx).read(cx);
23670 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23671 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23672 if (start.0..end.0) != range_utf16 {
23673 adjusted_range.replace(start.0..end.0);
23674 }
23675 Some(snapshot.text_for_range(start..end).collect())
23676 }
23677
23678 fn selected_text_range(
23679 &mut self,
23680 ignore_disabled_input: bool,
23681 _: &mut Window,
23682 cx: &mut Context<Self>,
23683 ) -> Option<UTF16Selection> {
23684 // Prevent the IME menu from appearing when holding down an alphabetic key
23685 // while input is disabled.
23686 if !ignore_disabled_input && !self.input_enabled {
23687 return None;
23688 }
23689
23690 let selection = self.selections.newest::<OffsetUtf16>(cx);
23691 let range = selection.range();
23692
23693 Some(UTF16Selection {
23694 range: range.start.0..range.end.0,
23695 reversed: selection.reversed,
23696 })
23697 }
23698
23699 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23700 let snapshot = self.buffer.read(cx).read(cx);
23701 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23702 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23703 }
23704
23705 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23706 self.clear_highlights::<InputComposition>(cx);
23707 self.ime_transaction.take();
23708 }
23709
23710 fn replace_text_in_range(
23711 &mut self,
23712 range_utf16: Option<Range<usize>>,
23713 text: &str,
23714 window: &mut Window,
23715 cx: &mut Context<Self>,
23716 ) {
23717 if !self.input_enabled {
23718 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23719 return;
23720 }
23721
23722 self.transact(window, cx, |this, window, cx| {
23723 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23724 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23725 Some(this.selection_replacement_ranges(range_utf16, cx))
23726 } else {
23727 this.marked_text_ranges(cx)
23728 };
23729
23730 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23731 let newest_selection_id = this.selections.newest_anchor().id;
23732 this.selections
23733 .all::<OffsetUtf16>(cx)
23734 .iter()
23735 .zip(ranges_to_replace.iter())
23736 .find_map(|(selection, range)| {
23737 if selection.id == newest_selection_id {
23738 Some(
23739 (range.start.0 as isize - selection.head().0 as isize)
23740 ..(range.end.0 as isize - selection.head().0 as isize),
23741 )
23742 } else {
23743 None
23744 }
23745 })
23746 });
23747
23748 cx.emit(EditorEvent::InputHandled {
23749 utf16_range_to_replace: range_to_replace,
23750 text: text.into(),
23751 });
23752
23753 if let Some(new_selected_ranges) = new_selected_ranges {
23754 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23755 selections.select_ranges(new_selected_ranges)
23756 });
23757 this.backspace(&Default::default(), window, cx);
23758 }
23759
23760 this.handle_input(text, window, cx);
23761 });
23762
23763 if let Some(transaction) = self.ime_transaction {
23764 self.buffer.update(cx, |buffer, cx| {
23765 buffer.group_until_transaction(transaction, cx);
23766 });
23767 }
23768
23769 self.unmark_text(window, cx);
23770 }
23771
23772 fn replace_and_mark_text_in_range(
23773 &mut self,
23774 range_utf16: Option<Range<usize>>,
23775 text: &str,
23776 new_selected_range_utf16: Option<Range<usize>>,
23777 window: &mut Window,
23778 cx: &mut Context<Self>,
23779 ) {
23780 if !self.input_enabled {
23781 return;
23782 }
23783
23784 let transaction = self.transact(window, cx, |this, window, cx| {
23785 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23786 let snapshot = this.buffer.read(cx).read(cx);
23787 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23788 for marked_range in &mut marked_ranges {
23789 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23790 marked_range.start.0 += relative_range_utf16.start;
23791 marked_range.start =
23792 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23793 marked_range.end =
23794 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23795 }
23796 }
23797 Some(marked_ranges)
23798 } else if let Some(range_utf16) = range_utf16 {
23799 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23800 Some(this.selection_replacement_ranges(range_utf16, cx))
23801 } else {
23802 None
23803 };
23804
23805 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23806 let newest_selection_id = this.selections.newest_anchor().id;
23807 this.selections
23808 .all::<OffsetUtf16>(cx)
23809 .iter()
23810 .zip(ranges_to_replace.iter())
23811 .find_map(|(selection, range)| {
23812 if selection.id == newest_selection_id {
23813 Some(
23814 (range.start.0 as isize - selection.head().0 as isize)
23815 ..(range.end.0 as isize - selection.head().0 as isize),
23816 )
23817 } else {
23818 None
23819 }
23820 })
23821 });
23822
23823 cx.emit(EditorEvent::InputHandled {
23824 utf16_range_to_replace: range_to_replace,
23825 text: text.into(),
23826 });
23827
23828 if let Some(ranges) = ranges_to_replace {
23829 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23830 s.select_ranges(ranges)
23831 });
23832 }
23833
23834 let marked_ranges = {
23835 let snapshot = this.buffer.read(cx).read(cx);
23836 this.selections
23837 .disjoint_anchors_arc()
23838 .iter()
23839 .map(|selection| {
23840 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23841 })
23842 .collect::<Vec<_>>()
23843 };
23844
23845 if text.is_empty() {
23846 this.unmark_text(window, cx);
23847 } else {
23848 this.highlight_text::<InputComposition>(
23849 marked_ranges.clone(),
23850 HighlightStyle {
23851 underline: Some(UnderlineStyle {
23852 thickness: px(1.),
23853 color: None,
23854 wavy: false,
23855 }),
23856 ..Default::default()
23857 },
23858 cx,
23859 );
23860 }
23861
23862 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23863 let use_autoclose = this.use_autoclose;
23864 let use_auto_surround = this.use_auto_surround;
23865 this.set_use_autoclose(false);
23866 this.set_use_auto_surround(false);
23867 this.handle_input(text, window, cx);
23868 this.set_use_autoclose(use_autoclose);
23869 this.set_use_auto_surround(use_auto_surround);
23870
23871 if let Some(new_selected_range) = new_selected_range_utf16 {
23872 let snapshot = this.buffer.read(cx).read(cx);
23873 let new_selected_ranges = marked_ranges
23874 .into_iter()
23875 .map(|marked_range| {
23876 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23877 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23878 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23879 snapshot.clip_offset_utf16(new_start, Bias::Left)
23880 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23881 })
23882 .collect::<Vec<_>>();
23883
23884 drop(snapshot);
23885 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23886 selections.select_ranges(new_selected_ranges)
23887 });
23888 }
23889 });
23890
23891 self.ime_transaction = self.ime_transaction.or(transaction);
23892 if let Some(transaction) = self.ime_transaction {
23893 self.buffer.update(cx, |buffer, cx| {
23894 buffer.group_until_transaction(transaction, cx);
23895 });
23896 }
23897
23898 if self.text_highlights::<InputComposition>(cx).is_none() {
23899 self.ime_transaction.take();
23900 }
23901 }
23902
23903 fn bounds_for_range(
23904 &mut self,
23905 range_utf16: Range<usize>,
23906 element_bounds: gpui::Bounds<Pixels>,
23907 window: &mut Window,
23908 cx: &mut Context<Self>,
23909 ) -> Option<gpui::Bounds<Pixels>> {
23910 let text_layout_details = self.text_layout_details(window);
23911 let CharacterDimensions {
23912 em_width,
23913 em_advance,
23914 line_height,
23915 } = self.character_dimensions(window);
23916
23917 let snapshot = self.snapshot(window, cx);
23918 let scroll_position = snapshot.scroll_position();
23919 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
23920
23921 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23922 let x = Pixels::from(
23923 ScrollOffset::from(
23924 snapshot.x_for_display_point(start, &text_layout_details)
23925 + self.gutter_dimensions.full_width(),
23926 ) - scroll_left,
23927 );
23928 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
23929
23930 Some(Bounds {
23931 origin: element_bounds.origin + point(x, y),
23932 size: size(em_width, line_height),
23933 })
23934 }
23935
23936 fn character_index_for_point(
23937 &mut self,
23938 point: gpui::Point<Pixels>,
23939 _window: &mut Window,
23940 _cx: &mut Context<Self>,
23941 ) -> Option<usize> {
23942 let position_map = self.last_position_map.as_ref()?;
23943 if !position_map.text_hitbox.contains(&point) {
23944 return None;
23945 }
23946 let display_point = position_map.point_for_position(point).previous_valid;
23947 let anchor = position_map
23948 .snapshot
23949 .display_point_to_anchor(display_point, Bias::Left);
23950 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23951 Some(utf16_offset.0)
23952 }
23953}
23954
23955trait SelectionExt {
23956 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23957 fn spanned_rows(
23958 &self,
23959 include_end_if_at_line_start: bool,
23960 map: &DisplaySnapshot,
23961 ) -> Range<MultiBufferRow>;
23962}
23963
23964impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23965 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23966 let start = self
23967 .start
23968 .to_point(&map.buffer_snapshot)
23969 .to_display_point(map);
23970 let end = self
23971 .end
23972 .to_point(&map.buffer_snapshot)
23973 .to_display_point(map);
23974 if self.reversed {
23975 end..start
23976 } else {
23977 start..end
23978 }
23979 }
23980
23981 fn spanned_rows(
23982 &self,
23983 include_end_if_at_line_start: bool,
23984 map: &DisplaySnapshot,
23985 ) -> Range<MultiBufferRow> {
23986 let start = self.start.to_point(&map.buffer_snapshot);
23987 let mut end = self.end.to_point(&map.buffer_snapshot);
23988 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23989 end.row -= 1;
23990 }
23991
23992 let buffer_start = map.prev_line_boundary(start).0;
23993 let buffer_end = map.next_line_boundary(end).0;
23994 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23995 }
23996}
23997
23998impl<T: InvalidationRegion> InvalidationStack<T> {
23999 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24000 where
24001 S: Clone + ToOffset,
24002 {
24003 while let Some(region) = self.last() {
24004 let all_selections_inside_invalidation_ranges =
24005 if selections.len() == region.ranges().len() {
24006 selections
24007 .iter()
24008 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24009 .all(|(selection, invalidation_range)| {
24010 let head = selection.head().to_offset(buffer);
24011 invalidation_range.start <= head && invalidation_range.end >= head
24012 })
24013 } else {
24014 false
24015 };
24016
24017 if all_selections_inside_invalidation_ranges {
24018 break;
24019 } else {
24020 self.pop();
24021 }
24022 }
24023 }
24024}
24025
24026impl<T> Default for InvalidationStack<T> {
24027 fn default() -> Self {
24028 Self(Default::default())
24029 }
24030}
24031
24032impl<T> Deref for InvalidationStack<T> {
24033 type Target = Vec<T>;
24034
24035 fn deref(&self) -> &Self::Target {
24036 &self.0
24037 }
24038}
24039
24040impl<T> DerefMut for InvalidationStack<T> {
24041 fn deref_mut(&mut self) -> &mut Self::Target {
24042 &mut self.0
24043 }
24044}
24045
24046impl InvalidationRegion for SnippetState {
24047 fn ranges(&self) -> &[Range<Anchor>] {
24048 &self.ranges[self.active_index]
24049 }
24050}
24051
24052fn edit_prediction_edit_text(
24053 current_snapshot: &BufferSnapshot,
24054 edits: &[(Range<Anchor>, String)],
24055 edit_preview: &EditPreview,
24056 include_deletions: bool,
24057 cx: &App,
24058) -> HighlightedText {
24059 let edits = edits
24060 .iter()
24061 .map(|(anchor, text)| {
24062 (
24063 anchor.start.text_anchor..anchor.end.text_anchor,
24064 text.clone(),
24065 )
24066 })
24067 .collect::<Vec<_>>();
24068
24069 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24070}
24071
24072fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24073 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24074 // Just show the raw edit text with basic styling
24075 let mut text = String::new();
24076 let mut highlights = Vec::new();
24077
24078 let insertion_highlight_style = HighlightStyle {
24079 color: Some(cx.theme().colors().text),
24080 ..Default::default()
24081 };
24082
24083 for (_, edit_text) in edits {
24084 let start_offset = text.len();
24085 text.push_str(edit_text);
24086 let end_offset = text.len();
24087
24088 if start_offset < end_offset {
24089 highlights.push((start_offset..end_offset, insertion_highlight_style));
24090 }
24091 }
24092
24093 HighlightedText {
24094 text: text.into(),
24095 highlights,
24096 }
24097}
24098
24099pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24100 match severity {
24101 lsp::DiagnosticSeverity::ERROR => colors.error,
24102 lsp::DiagnosticSeverity::WARNING => colors.warning,
24103 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24104 lsp::DiagnosticSeverity::HINT => colors.info,
24105 _ => colors.ignored,
24106 }
24107}
24108
24109pub fn styled_runs_for_code_label<'a>(
24110 label: &'a CodeLabel,
24111 syntax_theme: &'a theme::SyntaxTheme,
24112) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24113 let fade_out = HighlightStyle {
24114 fade_out: Some(0.35),
24115 ..Default::default()
24116 };
24117
24118 let mut prev_end = label.filter_range.end;
24119 label
24120 .runs
24121 .iter()
24122 .enumerate()
24123 .flat_map(move |(ix, (range, highlight_id))| {
24124 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24125 style
24126 } else {
24127 return Default::default();
24128 };
24129 let muted_style = style.highlight(fade_out);
24130
24131 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24132 if range.start >= label.filter_range.end {
24133 if range.start > prev_end {
24134 runs.push((prev_end..range.start, fade_out));
24135 }
24136 runs.push((range.clone(), muted_style));
24137 } else if range.end <= label.filter_range.end {
24138 runs.push((range.clone(), style));
24139 } else {
24140 runs.push((range.start..label.filter_range.end, style));
24141 runs.push((label.filter_range.end..range.end, muted_style));
24142 }
24143 prev_end = cmp::max(prev_end, range.end);
24144
24145 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24146 runs.push((prev_end..label.text.len(), fade_out));
24147 }
24148
24149 runs
24150 })
24151}
24152
24153pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24154 let mut prev_index = 0;
24155 let mut prev_codepoint: Option<char> = None;
24156 text.char_indices()
24157 .chain([(text.len(), '\0')])
24158 .filter_map(move |(index, codepoint)| {
24159 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24160 let is_boundary = index == text.len()
24161 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24162 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24163 if is_boundary {
24164 let chunk = &text[prev_index..index];
24165 prev_index = index;
24166 Some(chunk)
24167 } else {
24168 None
24169 }
24170 })
24171}
24172
24173pub trait RangeToAnchorExt: Sized {
24174 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24175
24176 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24177 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
24178 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24179 }
24180}
24181
24182impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24183 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24184 let start_offset = self.start.to_offset(snapshot);
24185 let end_offset = self.end.to_offset(snapshot);
24186 if start_offset == end_offset {
24187 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24188 } else {
24189 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24190 }
24191 }
24192}
24193
24194pub trait RowExt {
24195 fn as_f64(&self) -> f64;
24196
24197 fn next_row(&self) -> Self;
24198
24199 fn previous_row(&self) -> Self;
24200
24201 fn minus(&self, other: Self) -> u32;
24202}
24203
24204impl RowExt for DisplayRow {
24205 fn as_f64(&self) -> f64 {
24206 self.0 as _
24207 }
24208
24209 fn next_row(&self) -> Self {
24210 Self(self.0 + 1)
24211 }
24212
24213 fn previous_row(&self) -> Self {
24214 Self(self.0.saturating_sub(1))
24215 }
24216
24217 fn minus(&self, other: Self) -> u32 {
24218 self.0 - other.0
24219 }
24220}
24221
24222impl RowExt for MultiBufferRow {
24223 fn as_f64(&self) -> f64 {
24224 self.0 as _
24225 }
24226
24227 fn next_row(&self) -> Self {
24228 Self(self.0 + 1)
24229 }
24230
24231 fn previous_row(&self) -> Self {
24232 Self(self.0.saturating_sub(1))
24233 }
24234
24235 fn minus(&self, other: Self) -> u32 {
24236 self.0 - other.0
24237 }
24238}
24239
24240trait RowRangeExt {
24241 type Row;
24242
24243 fn len(&self) -> usize;
24244
24245 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24246}
24247
24248impl RowRangeExt for Range<MultiBufferRow> {
24249 type Row = MultiBufferRow;
24250
24251 fn len(&self) -> usize {
24252 (self.end.0 - self.start.0) as usize
24253 }
24254
24255 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24256 (self.start.0..self.end.0).map(MultiBufferRow)
24257 }
24258}
24259
24260impl RowRangeExt for Range<DisplayRow> {
24261 type Row = DisplayRow;
24262
24263 fn len(&self) -> usize {
24264 (self.end.0 - self.start.0) as usize
24265 }
24266
24267 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24268 (self.start.0..self.end.0).map(DisplayRow)
24269 }
24270}
24271
24272/// If select range has more than one line, we
24273/// just point the cursor to range.start.
24274fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24275 if range.start.row == range.end.row {
24276 range
24277 } else {
24278 range.start..range.start
24279 }
24280}
24281pub struct KillRing(ClipboardItem);
24282impl Global for KillRing {}
24283
24284const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24285
24286enum BreakpointPromptEditAction {
24287 Log,
24288 Condition,
24289 HitCondition,
24290}
24291
24292struct BreakpointPromptEditor {
24293 pub(crate) prompt: Entity<Editor>,
24294 editor: WeakEntity<Editor>,
24295 breakpoint_anchor: Anchor,
24296 breakpoint: Breakpoint,
24297 edit_action: BreakpointPromptEditAction,
24298 block_ids: HashSet<CustomBlockId>,
24299 editor_margins: Arc<Mutex<EditorMargins>>,
24300 _subscriptions: Vec<Subscription>,
24301}
24302
24303impl BreakpointPromptEditor {
24304 const MAX_LINES: u8 = 4;
24305
24306 fn new(
24307 editor: WeakEntity<Editor>,
24308 breakpoint_anchor: Anchor,
24309 breakpoint: Breakpoint,
24310 edit_action: BreakpointPromptEditAction,
24311 window: &mut Window,
24312 cx: &mut Context<Self>,
24313 ) -> Self {
24314 let base_text = match edit_action {
24315 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24316 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24317 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24318 }
24319 .map(|msg| msg.to_string())
24320 .unwrap_or_default();
24321
24322 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24323 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24324
24325 let prompt = cx.new(|cx| {
24326 let mut prompt = Editor::new(
24327 EditorMode::AutoHeight {
24328 min_lines: 1,
24329 max_lines: Some(Self::MAX_LINES as usize),
24330 },
24331 buffer,
24332 None,
24333 window,
24334 cx,
24335 );
24336 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24337 prompt.set_show_cursor_when_unfocused(false, cx);
24338 prompt.set_placeholder_text(
24339 match edit_action {
24340 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24341 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24342 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24343 },
24344 window,
24345 cx,
24346 );
24347
24348 prompt
24349 });
24350
24351 Self {
24352 prompt,
24353 editor,
24354 breakpoint_anchor,
24355 breakpoint,
24356 edit_action,
24357 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24358 block_ids: Default::default(),
24359 _subscriptions: vec![],
24360 }
24361 }
24362
24363 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24364 self.block_ids.extend(block_ids)
24365 }
24366
24367 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24368 if let Some(editor) = self.editor.upgrade() {
24369 let message = self
24370 .prompt
24371 .read(cx)
24372 .buffer
24373 .read(cx)
24374 .as_singleton()
24375 .expect("A multi buffer in breakpoint prompt isn't possible")
24376 .read(cx)
24377 .as_rope()
24378 .to_string();
24379
24380 editor.update(cx, |editor, cx| {
24381 editor.edit_breakpoint_at_anchor(
24382 self.breakpoint_anchor,
24383 self.breakpoint.clone(),
24384 match self.edit_action {
24385 BreakpointPromptEditAction::Log => {
24386 BreakpointEditAction::EditLogMessage(message.into())
24387 }
24388 BreakpointPromptEditAction::Condition => {
24389 BreakpointEditAction::EditCondition(message.into())
24390 }
24391 BreakpointPromptEditAction::HitCondition => {
24392 BreakpointEditAction::EditHitCondition(message.into())
24393 }
24394 },
24395 cx,
24396 );
24397
24398 editor.remove_blocks(self.block_ids.clone(), None, cx);
24399 cx.focus_self(window);
24400 });
24401 }
24402 }
24403
24404 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24405 self.editor
24406 .update(cx, |editor, cx| {
24407 editor.remove_blocks(self.block_ids.clone(), None, cx);
24408 window.focus(&editor.focus_handle);
24409 })
24410 .log_err();
24411 }
24412
24413 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24414 let settings = ThemeSettings::get_global(cx);
24415 let text_style = TextStyle {
24416 color: if self.prompt.read(cx).read_only(cx) {
24417 cx.theme().colors().text_disabled
24418 } else {
24419 cx.theme().colors().text
24420 },
24421 font_family: settings.buffer_font.family.clone(),
24422 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24423 font_size: settings.buffer_font_size(cx).into(),
24424 font_weight: settings.buffer_font.weight,
24425 line_height: relative(settings.buffer_line_height.value()),
24426 ..Default::default()
24427 };
24428 EditorElement::new(
24429 &self.prompt,
24430 EditorStyle {
24431 background: cx.theme().colors().editor_background,
24432 local_player: cx.theme().players().local(),
24433 text: text_style,
24434 ..Default::default()
24435 },
24436 )
24437 }
24438}
24439
24440impl Render for BreakpointPromptEditor {
24441 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24442 let editor_margins = *self.editor_margins.lock();
24443 let gutter_dimensions = editor_margins.gutter;
24444 h_flex()
24445 .key_context("Editor")
24446 .bg(cx.theme().colors().editor_background)
24447 .border_y_1()
24448 .border_color(cx.theme().status().info_border)
24449 .size_full()
24450 .py(window.line_height() / 2.5)
24451 .on_action(cx.listener(Self::confirm))
24452 .on_action(cx.listener(Self::cancel))
24453 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24454 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24455 }
24456}
24457
24458impl Focusable for BreakpointPromptEditor {
24459 fn focus_handle(&self, cx: &App) -> FocusHandle {
24460 self.prompt.focus_handle(cx)
24461 }
24462}
24463
24464fn all_edits_insertions_or_deletions(
24465 edits: &Vec<(Range<Anchor>, String)>,
24466 snapshot: &MultiBufferSnapshot,
24467) -> bool {
24468 let mut all_insertions = true;
24469 let mut all_deletions = true;
24470
24471 for (range, new_text) in edits.iter() {
24472 let range_is_empty = range.to_offset(snapshot).is_empty();
24473 let text_is_empty = new_text.is_empty();
24474
24475 if range_is_empty != text_is_empty {
24476 if range_is_empty {
24477 all_deletions = false;
24478 } else {
24479 all_insertions = false;
24480 }
24481 } else {
24482 return false;
24483 }
24484
24485 if !all_insertions && !all_deletions {
24486 return false;
24487 }
24488 }
24489 all_insertions || all_deletions
24490}
24491
24492struct MissingEditPredictionKeybindingTooltip;
24493
24494impl Render for MissingEditPredictionKeybindingTooltip {
24495 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24496 ui::tooltip_container(cx, |container, cx| {
24497 container
24498 .flex_shrink_0()
24499 .max_w_80()
24500 .min_h(rems_from_px(124.))
24501 .justify_between()
24502 .child(
24503 v_flex()
24504 .flex_1()
24505 .text_ui_sm(cx)
24506 .child(Label::new("Conflict with Accept Keybinding"))
24507 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24508 )
24509 .child(
24510 h_flex()
24511 .pb_1()
24512 .gap_1()
24513 .items_end()
24514 .w_full()
24515 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24516 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24517 }))
24518 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24519 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24520 })),
24521 )
24522 })
24523 }
24524}
24525
24526#[derive(Debug, Clone, Copy, PartialEq)]
24527pub struct LineHighlight {
24528 pub background: Background,
24529 pub border: Option<gpui::Hsla>,
24530 pub include_gutter: bool,
24531 pub type_id: Option<TypeId>,
24532}
24533
24534struct LineManipulationResult {
24535 pub new_text: String,
24536 pub line_count_before: usize,
24537 pub line_count_after: usize,
24538}
24539
24540fn render_diff_hunk_controls(
24541 row: u32,
24542 status: &DiffHunkStatus,
24543 hunk_range: Range<Anchor>,
24544 is_created_file: bool,
24545 line_height: Pixels,
24546 editor: &Entity<Editor>,
24547 _window: &mut Window,
24548 cx: &mut App,
24549) -> AnyElement {
24550 h_flex()
24551 .h(line_height)
24552 .mr_1()
24553 .gap_1()
24554 .px_0p5()
24555 .pb_1()
24556 .border_x_1()
24557 .border_b_1()
24558 .border_color(cx.theme().colors().border_variant)
24559 .rounded_b_lg()
24560 .bg(cx.theme().colors().editor_background)
24561 .gap_1()
24562 .block_mouse_except_scroll()
24563 .shadow_md()
24564 .child(if status.has_secondary_hunk() {
24565 Button::new(("stage", row as u64), "Stage")
24566 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24567 .tooltip({
24568 let focus_handle = editor.focus_handle(cx);
24569 move |window, cx| {
24570 Tooltip::for_action_in(
24571 "Stage Hunk",
24572 &::git::ToggleStaged,
24573 &focus_handle,
24574 window,
24575 cx,
24576 )
24577 }
24578 })
24579 .on_click({
24580 let editor = editor.clone();
24581 move |_event, _window, cx| {
24582 editor.update(cx, |editor, cx| {
24583 editor.stage_or_unstage_diff_hunks(
24584 true,
24585 vec![hunk_range.start..hunk_range.start],
24586 cx,
24587 );
24588 });
24589 }
24590 })
24591 } else {
24592 Button::new(("unstage", row as u64), "Unstage")
24593 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24594 .tooltip({
24595 let focus_handle = editor.focus_handle(cx);
24596 move |window, cx| {
24597 Tooltip::for_action_in(
24598 "Unstage Hunk",
24599 &::git::ToggleStaged,
24600 &focus_handle,
24601 window,
24602 cx,
24603 )
24604 }
24605 })
24606 .on_click({
24607 let editor = editor.clone();
24608 move |_event, _window, cx| {
24609 editor.update(cx, |editor, cx| {
24610 editor.stage_or_unstage_diff_hunks(
24611 false,
24612 vec![hunk_range.start..hunk_range.start],
24613 cx,
24614 );
24615 });
24616 }
24617 })
24618 })
24619 .child(
24620 Button::new(("restore", row as u64), "Restore")
24621 .tooltip({
24622 let focus_handle = editor.focus_handle(cx);
24623 move |window, cx| {
24624 Tooltip::for_action_in(
24625 "Restore Hunk",
24626 &::git::Restore,
24627 &focus_handle,
24628 window,
24629 cx,
24630 )
24631 }
24632 })
24633 .on_click({
24634 let editor = editor.clone();
24635 move |_event, window, cx| {
24636 editor.update(cx, |editor, cx| {
24637 let snapshot = editor.snapshot(window, cx);
24638 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24639 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24640 });
24641 }
24642 })
24643 .disabled(is_created_file),
24644 )
24645 .when(
24646 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24647 |el| {
24648 el.child(
24649 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24650 .shape(IconButtonShape::Square)
24651 .icon_size(IconSize::Small)
24652 // .disabled(!has_multiple_hunks)
24653 .tooltip({
24654 let focus_handle = editor.focus_handle(cx);
24655 move |window, cx| {
24656 Tooltip::for_action_in(
24657 "Next Hunk",
24658 &GoToHunk,
24659 &focus_handle,
24660 window,
24661 cx,
24662 )
24663 }
24664 })
24665 .on_click({
24666 let editor = editor.clone();
24667 move |_event, window, cx| {
24668 editor.update(cx, |editor, cx| {
24669 let snapshot = editor.snapshot(window, cx);
24670 let position =
24671 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24672 editor.go_to_hunk_before_or_after_position(
24673 &snapshot,
24674 position,
24675 Direction::Next,
24676 window,
24677 cx,
24678 );
24679 editor.expand_selected_diff_hunks(cx);
24680 });
24681 }
24682 }),
24683 )
24684 .child(
24685 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24686 .shape(IconButtonShape::Square)
24687 .icon_size(IconSize::Small)
24688 // .disabled(!has_multiple_hunks)
24689 .tooltip({
24690 let focus_handle = editor.focus_handle(cx);
24691 move |window, cx| {
24692 Tooltip::for_action_in(
24693 "Previous Hunk",
24694 &GoToPreviousHunk,
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 let snapshot = editor.snapshot(window, cx);
24706 let point =
24707 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24708 editor.go_to_hunk_before_or_after_position(
24709 &snapshot,
24710 point,
24711 Direction::Prev,
24712 window,
24713 cx,
24714 );
24715 editor.expand_selected_diff_hunks(cx);
24716 });
24717 }
24718 }),
24719 )
24720 },
24721 )
24722 .into_any_element()
24723}
24724
24725pub fn multibuffer_context_lines(cx: &App) -> u32 {
24726 EditorSettings::try_get(cx)
24727 .map(|settings| settings.excerpt_context_lines)
24728 .unwrap_or(2)
24729 .min(32)
24730}