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 editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction::Direction;
56pub use editor_settings::{
57 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
58 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap, ShowScrollbar,
59};
60pub use editor_settings_controls::*;
61pub use element::{
62 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
71 RowInfo, ToOffset, ToPoint,
72};
73pub use proposed_changes_editor::{
74 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
75};
76pub use text::Bias;
77
78use ::git::{
79 Restore,
80 blame::{BlameEntry, ParsedCommitMessage},
81};
82use aho_corasick::AhoCorasick;
83use anyhow::{Context as _, Result, anyhow};
84use blink_manager::BlinkManager;
85use buffer_diff::DiffHunkStatus;
86use client::{Collaborator, ParticipantIndex};
87use clock::{AGENT_REPLICA_ID, ReplicaId};
88use code_context_menus::{
89 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
90 CompletionsMenu, ContextMenuOrigin,
91};
92use collections::{BTreeMap, HashMap, HashSet, VecDeque};
93use convert_case::{Case, Casing};
94use dap::TelemetrySpawnLocation;
95use display_map::*;
96use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
97use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
98use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
99use futures::{
100 FutureExt, StreamExt as _,
101 future::{self, Shared, join},
102 stream::FuturesUnordered,
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
112 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
113 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
114 div, point, prelude::*, pulsating_between, px, relative, size,
115};
116use highlight_matching_bracket::refresh_matching_bracket_highlights;
117use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
121use itertools::{Either, Itertools};
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CodeLabel, CursorShape, DiagnosticEntry,
125 DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize,
126 Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal, TextObject,
127 TransactionId, TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionIntent, CompletionResponse,
151 CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, Location, LocationLink,
152 PrepareRenameResponse, Project, ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
153 debugger::breakpoint_store::Breakpoint,
154 debugger::{
155 breakpoint_store::{
156 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
157 BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter},
164 project_settings::{GitGutterSetting, ProjectSettings},
165};
166use rand::{seq::SliceRandom, thread_rng};
167use rpc::{ErrorCode, ErrorExt, proto::PeerId};
168use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
169use selections_collection::{
170 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
171};
172use serde::{Deserialize, Serialize};
173use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::TypeId,
178 borrow::Cow,
179 cell::OnceCell,
180 cell::RefCell,
181 cmp::{self, Ordering, Reverse},
182 iter::Peekable,
183 mem,
184 num::NonZeroU32,
185 ops::Not,
186 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
187 path::{Path, PathBuf},
188 rc::Rc,
189 sync::Arc,
190 time::{Duration, Instant},
191};
192use sum_tree::TreeMap;
193use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
194use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
195use theme::{
196 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
197 observe_buffer_font_size_adjustment,
198};
199use ui::{
200 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
201 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
202};
203use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
204use workspace::{
205 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
206 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
207 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
208 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
209 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
210 searchable::SearchEvent,
211};
212
213use crate::{
214 code_context_menus::CompletionsMenuSource,
215 editor_settings::MultiCursorModifier,
216 hover_links::{find_url, find_url_from_range},
217 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
218};
219
220pub const FILE_HEADER_HEIGHT: u32 = 2;
221pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
222pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
223const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
224const MAX_LINE_LEN: usize = 1024;
225const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
226const MAX_SELECTION_HISTORY_LEN: usize = 1024;
227pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
228#[doc(hidden)]
229pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
230const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
231
232pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
234pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
235
236pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
237pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
238pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
239
240pub type RenderDiffHunkControlsFn = Arc<
241 dyn Fn(
242 u32,
243 &DiffHunkStatus,
244 Range<Anchor>,
245 bool,
246 Pixels,
247 &Entity<Editor>,
248 &mut Window,
249 &mut App,
250 ) -> AnyElement,
251>;
252
253enum ReportEditorEvent {
254 Saved { auto_saved: bool },
255 EditorOpened,
256 ZetaTosClicked,
257 Closed,
258}
259
260impl ReportEditorEvent {
261 pub fn event_type(&self) -> &'static str {
262 match self {
263 Self::Saved { .. } => "Editor Saved",
264 Self::EditorOpened => "Editor Opened",
265 Self::ZetaTosClicked => "Edit Prediction Provider ToS Clicked",
266 Self::Closed => "Editor Closed",
267 }
268 }
269}
270
271struct InlineValueCache {
272 enabled: bool,
273 inlays: Vec<InlayId>,
274 refresh_task: Task<Option<()>>,
275}
276
277impl InlineValueCache {
278 fn new(enabled: bool) -> Self {
279 Self {
280 enabled,
281 inlays: Vec::new(),
282 refresh_task: Task::ready(None),
283 }
284 }
285}
286
287#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
288pub enum InlayId {
289 EditPrediction(usize),
290 DebuggerValue(usize),
291 // LSP
292 Hint(usize),
293 Color(usize),
294}
295
296impl InlayId {
297 fn id(&self) -> usize {
298 match self {
299 Self::EditPrediction(id) => *id,
300 Self::DebuggerValue(id) => *id,
301 Self::Hint(id) => *id,
302 Self::Color(id) => *id,
303 }
304 }
305}
306
307pub enum ActiveDebugLine {}
308pub enum DebugStackFrameLine {}
309enum DocumentHighlightRead {}
310enum DocumentHighlightWrite {}
311enum InputComposition {}
312pub enum PendingInput {}
313enum SelectedTextHighlight {}
314
315pub enum ConflictsOuter {}
316pub enum ConflictsOurs {}
317pub enum ConflictsTheirs {}
318pub enum ConflictsOursMarker {}
319pub enum ConflictsTheirsMarker {}
320
321#[derive(Debug, Copy, Clone, PartialEq, Eq)]
322pub enum Navigated {
323 Yes,
324 No,
325}
326
327impl Navigated {
328 pub fn from_bool(yes: bool) -> Navigated {
329 if yes { Navigated::Yes } else { Navigated::No }
330 }
331}
332
333#[derive(Debug, Clone, PartialEq, Eq)]
334enum DisplayDiffHunk {
335 Folded {
336 display_row: DisplayRow,
337 },
338 Unfolded {
339 is_created_file: bool,
340 diff_base_byte_range: Range<usize>,
341 display_row_range: Range<DisplayRow>,
342 multi_buffer_range: Range<Anchor>,
343 status: DiffHunkStatus,
344 },
345}
346
347pub enum HideMouseCursorOrigin {
348 TypingAction,
349 MovementAction,
350}
351
352pub fn init_settings(cx: &mut App) {
353 EditorSettings::register(cx);
354}
355
356pub fn init(cx: &mut App) {
357 init_settings(cx);
358
359 cx.set_global(GlobalBlameRenderer(Arc::new(())));
360
361 workspace::register_project_item::<Editor>(cx);
362 workspace::FollowableViewRegistry::register::<Editor>(cx);
363 workspace::register_serializable_item::<Editor>(cx);
364
365 cx.observe_new(
366 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
367 workspace.register_action(Editor::new_file);
368 workspace.register_action(Editor::new_file_vertical);
369 workspace.register_action(Editor::new_file_horizontal);
370 workspace.register_action(Editor::cancel_language_server_work);
371 workspace.register_action(Editor::toggle_focus);
372 },
373 )
374 .detach();
375
376 cx.on_action(move |_: &workspace::NewFile, cx| {
377 let app_state = workspace::AppState::global(cx);
378 if let Some(app_state) = app_state.upgrade() {
379 workspace::open_new(
380 Default::default(),
381 app_state,
382 cx,
383 |workspace, window, cx| {
384 Editor::new_file(workspace, &Default::default(), window, cx)
385 },
386 )
387 .detach();
388 }
389 });
390 cx.on_action(move |_: &workspace::NewWindow, cx| {
391 let app_state = workspace::AppState::global(cx);
392 if let Some(app_state) = app_state.upgrade() {
393 workspace::open_new(
394 Default::default(),
395 app_state,
396 cx,
397 |workspace, window, cx| {
398 cx.activate(true);
399 Editor::new_file(workspace, &Default::default(), window, cx)
400 },
401 )
402 .detach();
403 }
404 });
405}
406
407pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
408 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
409}
410
411pub trait DiagnosticRenderer {
412 fn render_group(
413 &self,
414 diagnostic_group: Vec<DiagnosticEntry<Point>>,
415 buffer_id: BufferId,
416 snapshot: EditorSnapshot,
417 editor: WeakEntity<Editor>,
418 cx: &mut App,
419 ) -> Vec<BlockProperties<Anchor>>;
420
421 fn render_hover(
422 &self,
423 diagnostic_group: Vec<DiagnosticEntry<Point>>,
424 range: Range<Point>,
425 buffer_id: BufferId,
426 cx: &mut App,
427 ) -> Option<Entity<markdown::Markdown>>;
428
429 fn open_link(
430 &self,
431 editor: &mut Editor,
432 link: SharedString,
433 window: &mut Window,
434 cx: &mut Context<Editor>,
435 );
436}
437
438pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
439
440impl GlobalDiagnosticRenderer {
441 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
442 cx.try_global::<Self>().map(|g| g.0.clone())
443 }
444}
445
446impl gpui::Global for GlobalDiagnosticRenderer {}
447pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
448 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
449}
450
451pub struct SearchWithinRange;
452
453trait InvalidationRegion {
454 fn ranges(&self) -> &[Range<Anchor>];
455}
456
457#[derive(Clone, Debug, PartialEq)]
458pub enum SelectPhase {
459 Begin {
460 position: DisplayPoint,
461 add: bool,
462 click_count: usize,
463 },
464 BeginColumnar {
465 position: DisplayPoint,
466 reset: bool,
467 mode: ColumnarMode,
468 goal_column: u32,
469 },
470 Extend {
471 position: DisplayPoint,
472 click_count: usize,
473 },
474 Update {
475 position: DisplayPoint,
476 goal_column: u32,
477 scroll_delta: gpui::Point<f32>,
478 },
479 End,
480}
481
482#[derive(Clone, Debug, PartialEq)]
483pub enum ColumnarMode {
484 FromMouse,
485 FromSelection,
486}
487
488#[derive(Clone, Debug)]
489pub enum SelectMode {
490 Character,
491 Word(Range<Anchor>),
492 Line(Range<Anchor>),
493 All,
494}
495
496#[derive(Clone, PartialEq, Eq, Debug)]
497pub enum EditorMode {
498 SingleLine,
499 AutoHeight {
500 min_lines: usize,
501 max_lines: Option<usize>,
502 },
503 Full {
504 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
505 scale_ui_elements_with_buffer_font_size: bool,
506 /// When set to `true`, the editor will render a background for the active line.
507 show_active_line_background: bool,
508 /// When set to `true`, the editor's height will be determined by its content.
509 sized_by_content: bool,
510 },
511 Minimap {
512 parent: WeakEntity<Editor>,
513 },
514}
515
516impl EditorMode {
517 pub fn full() -> Self {
518 Self::Full {
519 scale_ui_elements_with_buffer_font_size: true,
520 show_active_line_background: true,
521 sized_by_content: false,
522 }
523 }
524
525 #[inline]
526 pub fn is_full(&self) -> bool {
527 matches!(self, Self::Full { .. })
528 }
529
530 #[inline]
531 pub fn is_single_line(&self) -> bool {
532 matches!(self, Self::SingleLine { .. })
533 }
534
535 #[inline]
536 fn is_minimap(&self) -> bool {
537 matches!(self, Self::Minimap { .. })
538 }
539}
540
541#[derive(Copy, Clone, Debug)]
542pub enum SoftWrap {
543 /// Prefer not to wrap at all.
544 ///
545 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
546 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
547 GitDiff,
548 /// Prefer a single line generally, unless an overly long line is encountered.
549 None,
550 /// Soft wrap lines that exceed the editor width.
551 EditorWidth,
552 /// Soft wrap lines at the preferred line length.
553 Column(u32),
554 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
555 Bounded(u32),
556}
557
558#[derive(Clone)]
559pub struct EditorStyle {
560 pub background: Hsla,
561 pub border: Hsla,
562 pub local_player: PlayerColor,
563 pub text: TextStyle,
564 pub scrollbar_width: Pixels,
565 pub syntax: Arc<SyntaxTheme>,
566 pub status: StatusColors,
567 pub inlay_hints_style: HighlightStyle,
568 pub edit_prediction_styles: EditPredictionStyles,
569 pub unnecessary_code_fade: f32,
570 pub show_underlines: bool,
571}
572
573impl Default for EditorStyle {
574 fn default() -> Self {
575 Self {
576 background: Hsla::default(),
577 border: Hsla::default(),
578 local_player: PlayerColor::default(),
579 text: TextStyle::default(),
580 scrollbar_width: Pixels::default(),
581 syntax: Default::default(),
582 // HACK: Status colors don't have a real default.
583 // We should look into removing the status colors from the editor
584 // style and retrieve them directly from the theme.
585 status: StatusColors::dark(),
586 inlay_hints_style: HighlightStyle::default(),
587 edit_prediction_styles: EditPredictionStyles {
588 insertion: HighlightStyle::default(),
589 whitespace: HighlightStyle::default(),
590 },
591 unnecessary_code_fade: Default::default(),
592 show_underlines: true,
593 }
594 }
595}
596
597pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
598 let show_background = language_settings::language_settings(None, None, cx)
599 .inlay_hints
600 .show_background;
601
602 HighlightStyle {
603 color: Some(cx.theme().status().hint),
604 background_color: show_background.then(|| cx.theme().status().hint_background),
605 ..HighlightStyle::default()
606 }
607}
608
609pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
610 EditPredictionStyles {
611 insertion: HighlightStyle {
612 color: Some(cx.theme().status().predictive),
613 ..HighlightStyle::default()
614 },
615 whitespace: HighlightStyle {
616 background_color: Some(cx.theme().status().created_background),
617 ..HighlightStyle::default()
618 },
619 }
620}
621
622type CompletionId = usize;
623
624pub(crate) enum EditDisplayMode {
625 TabAccept,
626 DiffPopover,
627 Inline,
628}
629
630enum EditPrediction {
631 Edit {
632 edits: Vec<(Range<Anchor>, String)>,
633 edit_preview: Option<EditPreview>,
634 display_mode: EditDisplayMode,
635 snapshot: BufferSnapshot,
636 },
637 Move {
638 target: Anchor,
639 snapshot: BufferSnapshot,
640 },
641}
642
643struct EditPredictionState {
644 inlay_ids: Vec<InlayId>,
645 completion: EditPrediction,
646 completion_id: Option<SharedString>,
647 invalidation_range: Range<Anchor>,
648}
649
650enum EditPredictionSettings {
651 Disabled,
652 Enabled {
653 show_in_menu: bool,
654 preview_requires_modifier: bool,
655 },
656}
657
658enum EditPredictionHighlight {}
659
660#[derive(Debug, Clone)]
661struct InlineDiagnostic {
662 message: SharedString,
663 group_id: usize,
664 is_primary: bool,
665 start: Point,
666 severity: lsp::DiagnosticSeverity,
667}
668
669pub enum MenuEditPredictionsPolicy {
670 Never,
671 ByProvider,
672}
673
674pub enum EditPredictionPreview {
675 /// Modifier is not pressed
676 Inactive { released_too_fast: bool },
677 /// Modifier pressed
678 Active {
679 since: Instant,
680 previous_scroll_position: Option<ScrollAnchor>,
681 },
682}
683
684impl EditPredictionPreview {
685 pub fn released_too_fast(&self) -> bool {
686 match self {
687 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
688 EditPredictionPreview::Active { .. } => false,
689 }
690 }
691
692 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
693 if let EditPredictionPreview::Active {
694 previous_scroll_position,
695 ..
696 } = self
697 {
698 *previous_scroll_position = scroll_position;
699 }
700 }
701}
702
703pub struct ContextMenuOptions {
704 pub min_entries_visible: usize,
705 pub max_entries_visible: usize,
706 pub placement: Option<ContextMenuPlacement>,
707}
708
709#[derive(Debug, Clone, PartialEq, Eq)]
710pub enum ContextMenuPlacement {
711 Above,
712 Below,
713}
714
715#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
716struct EditorActionId(usize);
717
718impl EditorActionId {
719 pub fn post_inc(&mut self) -> Self {
720 let answer = self.0;
721
722 *self = Self(answer + 1);
723
724 Self(answer)
725 }
726}
727
728// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
729// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
730
731type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
732type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
733
734#[derive(Default)]
735struct ScrollbarMarkerState {
736 scrollbar_size: Size<Pixels>,
737 dirty: bool,
738 markers: Arc<[PaintQuad]>,
739 pending_refresh: Option<Task<Result<()>>>,
740}
741
742impl ScrollbarMarkerState {
743 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
744 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
745 }
746}
747
748#[derive(Clone, Copy, PartialEq, Eq)]
749pub enum MinimapVisibility {
750 Disabled,
751 Enabled {
752 /// The configuration currently present in the users settings.
753 setting_configuration: bool,
754 /// Whether to override the currently set visibility from the users setting.
755 toggle_override: bool,
756 },
757}
758
759impl MinimapVisibility {
760 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
761 if mode.is_full() {
762 Self::Enabled {
763 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
764 toggle_override: false,
765 }
766 } else {
767 Self::Disabled
768 }
769 }
770
771 fn hidden(&self) -> Self {
772 match *self {
773 Self::Enabled {
774 setting_configuration,
775 ..
776 } => Self::Enabled {
777 setting_configuration,
778 toggle_override: setting_configuration,
779 },
780 Self::Disabled => Self::Disabled,
781 }
782 }
783
784 fn disabled(&self) -> bool {
785 matches!(*self, Self::Disabled)
786 }
787
788 fn settings_visibility(&self) -> bool {
789 match *self {
790 Self::Enabled {
791 setting_configuration,
792 ..
793 } => setting_configuration,
794 _ => false,
795 }
796 }
797
798 fn visible(&self) -> bool {
799 match *self {
800 Self::Enabled {
801 setting_configuration,
802 toggle_override,
803 } => setting_configuration ^ toggle_override,
804 _ => false,
805 }
806 }
807
808 fn toggle_visibility(&self) -> Self {
809 match *self {
810 Self::Enabled {
811 toggle_override,
812 setting_configuration,
813 } => Self::Enabled {
814 setting_configuration,
815 toggle_override: !toggle_override,
816 },
817 Self::Disabled => Self::Disabled,
818 }
819 }
820}
821
822#[derive(Clone, Debug)]
823struct RunnableTasks {
824 templates: Vec<(TaskSourceKind, TaskTemplate)>,
825 offset: multi_buffer::Anchor,
826 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
827 column: u32,
828 // Values of all named captures, including those starting with '_'
829 extra_variables: HashMap<String, String>,
830 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
831 context_range: Range<BufferOffset>,
832}
833
834impl RunnableTasks {
835 fn resolve<'a>(
836 &'a self,
837 cx: &'a task::TaskContext,
838 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
839 self.templates.iter().filter_map(|(kind, template)| {
840 template
841 .resolve_task(&kind.to_id_base(), cx)
842 .map(|task| (kind.clone(), task))
843 })
844 }
845}
846
847#[derive(Clone)]
848pub struct ResolvedTasks {
849 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
850 position: Anchor,
851}
852
853#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
854struct BufferOffset(usize);
855
856// Addons allow storing per-editor state in other crates (e.g. Vim)
857pub trait Addon: 'static {
858 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
859
860 fn render_buffer_header_controls(
861 &self,
862 _: &ExcerptInfo,
863 _: &Window,
864 _: &App,
865 ) -> Option<AnyElement> {
866 None
867 }
868
869 fn to_any(&self) -> &dyn std::any::Any;
870
871 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
872 None
873 }
874}
875
876struct ChangeLocation {
877 current: Option<Vec<Anchor>>,
878 original: Vec<Anchor>,
879}
880impl ChangeLocation {
881 fn locations(&self) -> &[Anchor] {
882 self.current.as_ref().unwrap_or(&self.original)
883 }
884}
885
886/// A set of caret positions, registered when the editor was edited.
887pub struct ChangeList {
888 changes: Vec<ChangeLocation>,
889 /// Currently "selected" change.
890 position: Option<usize>,
891}
892
893impl ChangeList {
894 pub fn new() -> Self {
895 Self {
896 changes: Vec::new(),
897 position: None,
898 }
899 }
900
901 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
902 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
903 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
904 if self.changes.is_empty() {
905 return None;
906 }
907
908 let prev = self.position.unwrap_or(self.changes.len());
909 let next = if direction == Direction::Prev {
910 prev.saturating_sub(count)
911 } else {
912 (prev + count).min(self.changes.len() - 1)
913 };
914 self.position = Some(next);
915 self.changes.get(next).map(|change| change.locations())
916 }
917
918 /// Adds a new change to the list, resetting the change list position.
919 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
920 self.position.take();
921 if let Some(last) = self.changes.last_mut()
922 && group
923 {
924 last.current = Some(new_positions)
925 } else {
926 self.changes.push(ChangeLocation {
927 original: new_positions,
928 current: None,
929 });
930 }
931 }
932
933 pub fn last(&self) -> Option<&[Anchor]> {
934 self.changes.last().map(|change| change.locations())
935 }
936
937 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
938 self.changes.last().map(|change| change.original.as_slice())
939 }
940
941 pub fn invert_last_group(&mut self) {
942 if let Some(last) = self.changes.last_mut()
943 && let Some(current) = last.current.as_mut()
944 {
945 mem::swap(&mut last.original, current);
946 }
947 }
948}
949
950#[derive(Clone)]
951struct InlineBlamePopoverState {
952 scroll_handle: ScrollHandle,
953 commit_message: Option<ParsedCommitMessage>,
954 markdown: Entity<Markdown>,
955}
956
957struct InlineBlamePopover {
958 position: gpui::Point<Pixels>,
959 hide_task: Option<Task<()>>,
960 popover_bounds: Option<Bounds<Pixels>>,
961 popover_state: InlineBlamePopoverState,
962 keyboard_grace: bool,
963}
964
965enum SelectionDragState {
966 /// State when no drag related activity is detected.
967 None,
968 /// State when the mouse is down on a selection that is about to be dragged.
969 ReadyToDrag {
970 selection: Selection<Anchor>,
971 click_position: gpui::Point<Pixels>,
972 mouse_down_time: Instant,
973 },
974 /// State when the mouse is dragging the selection in the editor.
975 Dragging {
976 selection: Selection<Anchor>,
977 drop_cursor: Selection<Anchor>,
978 hide_drop_cursor: bool,
979 },
980}
981
982enum ColumnarSelectionState {
983 FromMouse {
984 selection_tail: Anchor,
985 display_point: Option<DisplayPoint>,
986 },
987 FromSelection {
988 selection_tail: Anchor,
989 },
990}
991
992/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
993/// a breakpoint on them.
994#[derive(Clone, Copy, Debug, PartialEq, Eq)]
995struct PhantomBreakpointIndicator {
996 display_row: DisplayRow,
997 /// There's a small debounce between hovering over the line and showing the indicator.
998 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
999 is_active: bool,
1000 collides_with_existing_breakpoint: bool,
1001}
1002
1003/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1004///
1005/// See the [module level documentation](self) for more information.
1006pub struct Editor {
1007 focus_handle: FocusHandle,
1008 last_focused_descendant: Option<WeakFocusHandle>,
1009 /// The text buffer being edited
1010 buffer: Entity<MultiBuffer>,
1011 /// Map of how text in the buffer should be displayed.
1012 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1013 pub display_map: Entity<DisplayMap>,
1014 pub selections: SelectionsCollection,
1015 pub scroll_manager: ScrollManager,
1016 /// When inline assist editors are linked, they all render cursors because
1017 /// typing enters text into each of them, even the ones that aren't focused.
1018 pub(crate) show_cursor_when_unfocused: bool,
1019 columnar_selection_state: Option<ColumnarSelectionState>,
1020 add_selections_state: Option<AddSelectionsState>,
1021 select_next_state: Option<SelectNextState>,
1022 select_prev_state: Option<SelectNextState>,
1023 selection_history: SelectionHistory,
1024 defer_selection_effects: bool,
1025 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1026 autoclose_regions: Vec<AutocloseRegion>,
1027 snippet_stack: InvalidationStack<SnippetState>,
1028 select_syntax_node_history: SelectSyntaxNodeHistory,
1029 ime_transaction: Option<TransactionId>,
1030 pub diagnostics_max_severity: DiagnosticSeverity,
1031 active_diagnostics: ActiveDiagnostic,
1032 show_inline_diagnostics: bool,
1033 inline_diagnostics_update: Task<()>,
1034 inline_diagnostics_enabled: bool,
1035 diagnostics_enabled: bool,
1036 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1037 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1038 hard_wrap: Option<usize>,
1039 project: Option<Entity<Project>>,
1040 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1041 completion_provider: Option<Rc<dyn CompletionProvider>>,
1042 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1043 blink_manager: Entity<BlinkManager>,
1044 show_cursor_names: bool,
1045 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1046 pub show_local_selections: bool,
1047 mode: EditorMode,
1048 show_breadcrumbs: bool,
1049 show_gutter: bool,
1050 show_scrollbars: ScrollbarAxes,
1051 minimap_visibility: MinimapVisibility,
1052 offset_content: bool,
1053 disable_expand_excerpt_buttons: bool,
1054 show_line_numbers: Option<bool>,
1055 use_relative_line_numbers: Option<bool>,
1056 show_git_diff_gutter: Option<bool>,
1057 show_code_actions: Option<bool>,
1058 show_runnables: Option<bool>,
1059 show_breakpoints: Option<bool>,
1060 show_wrap_guides: Option<bool>,
1061 show_indent_guides: Option<bool>,
1062 placeholder_text: Option<Arc<str>>,
1063 highlight_order: usize,
1064 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1065 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1066 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1067 scrollbar_marker_state: ScrollbarMarkerState,
1068 active_indent_guides_state: ActiveIndentGuidesState,
1069 nav_history: Option<ItemNavHistory>,
1070 context_menu: RefCell<Option<CodeContextMenu>>,
1071 context_menu_options: Option<ContextMenuOptions>,
1072 mouse_context_menu: Option<MouseContextMenu>,
1073 completion_tasks: Vec<(CompletionId, Task<()>)>,
1074 inline_blame_popover: Option<InlineBlamePopover>,
1075 inline_blame_popover_show_task: Option<Task<()>>,
1076 signature_help_state: SignatureHelpState,
1077 auto_signature_help: Option<bool>,
1078 find_all_references_task_sources: Vec<Anchor>,
1079 next_completion_id: CompletionId,
1080 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1081 code_actions_task: Option<Task<Result<()>>>,
1082 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1083 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1084 document_highlights_task: Option<Task<()>>,
1085 linked_editing_range_task: Option<Task<Option<()>>>,
1086 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1087 pending_rename: Option<RenameState>,
1088 searchable: bool,
1089 cursor_shape: CursorShape,
1090 current_line_highlight: Option<CurrentLineHighlight>,
1091 collapse_matches: bool,
1092 autoindent_mode: Option<AutoindentMode>,
1093 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1094 input_enabled: bool,
1095 use_modal_editing: bool,
1096 read_only: bool,
1097 leader_id: Option<CollaboratorId>,
1098 remote_id: Option<ViewId>,
1099 pub hover_state: HoverState,
1100 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1101 gutter_hovered: bool,
1102 hovered_link_state: Option<HoveredLinkState>,
1103 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1104 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1105 active_edit_prediction: Option<EditPredictionState>,
1106 /// Used to prevent flickering as the user types while the menu is open
1107 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1108 edit_prediction_settings: EditPredictionSettings,
1109 edit_predictions_hidden_for_vim_mode: bool,
1110 show_edit_predictions_override: Option<bool>,
1111 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1112 edit_prediction_preview: EditPredictionPreview,
1113 edit_prediction_indent_conflict: bool,
1114 edit_prediction_requires_modifier_in_indent_conflict: bool,
1115 inlay_hint_cache: InlayHintCache,
1116 next_inlay_id: usize,
1117 _subscriptions: Vec<Subscription>,
1118 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1119 gutter_dimensions: GutterDimensions,
1120 style: Option<EditorStyle>,
1121 text_style_refinement: Option<TextStyleRefinement>,
1122 next_editor_action_id: EditorActionId,
1123 editor_actions: Rc<
1124 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1125 >,
1126 use_autoclose: bool,
1127 use_auto_surround: bool,
1128 auto_replace_emoji_shortcode: bool,
1129 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1130 show_git_blame_gutter: bool,
1131 show_git_blame_inline: bool,
1132 show_git_blame_inline_delay_task: Option<Task<()>>,
1133 git_blame_inline_enabled: bool,
1134 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1135 serialize_dirty_buffers: bool,
1136 show_selection_menu: Option<bool>,
1137 blame: Option<Entity<GitBlame>>,
1138 blame_subscription: Option<Subscription>,
1139 custom_context_menu: Option<
1140 Box<
1141 dyn 'static
1142 + Fn(
1143 &mut Self,
1144 DisplayPoint,
1145 &mut Window,
1146 &mut Context<Self>,
1147 ) -> Option<Entity<ui::ContextMenu>>,
1148 >,
1149 >,
1150 last_bounds: Option<Bounds<Pixels>>,
1151 last_position_map: Option<Rc<PositionMap>>,
1152 expect_bounds_change: Option<Bounds<Pixels>>,
1153 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1154 tasks_update_task: Option<Task<()>>,
1155 breakpoint_store: Option<Entity<BreakpointStore>>,
1156 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1157 hovered_diff_hunk_row: Option<DisplayRow>,
1158 pull_diagnostics_task: Task<()>,
1159 in_project_search: bool,
1160 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1161 breadcrumb_header: Option<String>,
1162 focused_block: Option<FocusedBlock>,
1163 next_scroll_position: NextScrollCursorCenterTopBottom,
1164 addons: HashMap<TypeId, Box<dyn Addon>>,
1165 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1166 load_diff_task: Option<Shared<Task<()>>>,
1167 /// Whether we are temporarily displaying a diff other than git's
1168 temporary_diff_override: bool,
1169 selection_mark_mode: bool,
1170 toggle_fold_multiple_buffers: Task<()>,
1171 _scroll_cursor_center_top_bottom_task: Task<()>,
1172 serialize_selections: Task<()>,
1173 serialize_folds: Task<()>,
1174 mouse_cursor_hidden: bool,
1175 minimap: Option<Entity<Self>>,
1176 hide_mouse_mode: HideMouseMode,
1177 pub change_list: ChangeList,
1178 inline_value_cache: InlineValueCache,
1179 selection_drag_state: SelectionDragState,
1180 next_color_inlay_id: usize,
1181 colors: Option<LspColorData>,
1182 folding_newlines: Task<()>,
1183}
1184
1185#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1186enum NextScrollCursorCenterTopBottom {
1187 #[default]
1188 Center,
1189 Top,
1190 Bottom,
1191}
1192
1193impl NextScrollCursorCenterTopBottom {
1194 fn next(&self) -> Self {
1195 match self {
1196 Self::Center => Self::Top,
1197 Self::Top => Self::Bottom,
1198 Self::Bottom => Self::Center,
1199 }
1200 }
1201}
1202
1203#[derive(Clone)]
1204pub struct EditorSnapshot {
1205 pub mode: EditorMode,
1206 show_gutter: bool,
1207 show_line_numbers: Option<bool>,
1208 show_git_diff_gutter: Option<bool>,
1209 show_code_actions: Option<bool>,
1210 show_runnables: Option<bool>,
1211 show_breakpoints: Option<bool>,
1212 git_blame_gutter_max_author_length: Option<usize>,
1213 pub display_snapshot: DisplaySnapshot,
1214 pub placeholder_text: Option<Arc<str>>,
1215 is_focused: bool,
1216 scroll_anchor: ScrollAnchor,
1217 ongoing_scroll: OngoingScroll,
1218 current_line_highlight: CurrentLineHighlight,
1219 gutter_hovered: bool,
1220}
1221
1222#[derive(Default, Debug, Clone, Copy)]
1223pub struct GutterDimensions {
1224 pub left_padding: Pixels,
1225 pub right_padding: Pixels,
1226 pub width: Pixels,
1227 pub margin: Pixels,
1228 pub git_blame_entries_width: Option<Pixels>,
1229}
1230
1231impl GutterDimensions {
1232 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1233 Self {
1234 margin: Self::default_gutter_margin(font_id, font_size, cx),
1235 ..Default::default()
1236 }
1237 }
1238
1239 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1240 -cx.text_system().descent(font_id, font_size)
1241 }
1242 /// The full width of the space taken up by the gutter.
1243 pub fn full_width(&self) -> Pixels {
1244 self.margin + self.width
1245 }
1246
1247 /// The width of the space reserved for the fold indicators,
1248 /// use alongside 'justify_end' and `gutter_width` to
1249 /// right align content with the line numbers
1250 pub fn fold_area_width(&self) -> Pixels {
1251 self.margin + self.right_padding
1252 }
1253}
1254
1255struct CharacterDimensions {
1256 em_width: Pixels,
1257 em_advance: Pixels,
1258 line_height: Pixels,
1259}
1260
1261#[derive(Debug)]
1262pub struct RemoteSelection {
1263 pub replica_id: ReplicaId,
1264 pub selection: Selection<Anchor>,
1265 pub cursor_shape: CursorShape,
1266 pub collaborator_id: CollaboratorId,
1267 pub line_mode: bool,
1268 pub user_name: Option<SharedString>,
1269 pub color: PlayerColor,
1270}
1271
1272#[derive(Clone, Debug)]
1273struct SelectionHistoryEntry {
1274 selections: Arc<[Selection<Anchor>]>,
1275 select_next_state: Option<SelectNextState>,
1276 select_prev_state: Option<SelectNextState>,
1277 add_selections_state: Option<AddSelectionsState>,
1278}
1279
1280#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1281enum SelectionHistoryMode {
1282 Normal,
1283 Undoing,
1284 Redoing,
1285 Skipping,
1286}
1287
1288#[derive(Clone, PartialEq, Eq, Hash)]
1289struct HoveredCursor {
1290 replica_id: u16,
1291 selection_id: usize,
1292}
1293
1294impl Default for SelectionHistoryMode {
1295 fn default() -> Self {
1296 Self::Normal
1297 }
1298}
1299
1300#[derive(Debug)]
1301/// SelectionEffects controls the side-effects of updating the selection.
1302///
1303/// The default behaviour does "what you mostly want":
1304/// - it pushes to the nav history if the cursor moved by >10 lines
1305/// - it re-triggers completion requests
1306/// - it scrolls to fit
1307///
1308/// You might want to modify these behaviours. For example when doing a "jump"
1309/// like go to definition, we always want to add to nav history; but when scrolling
1310/// in vim mode we never do.
1311///
1312/// Similarly, you might want to disable scrolling if you don't want the viewport to
1313/// move.
1314#[derive(Clone)]
1315pub struct SelectionEffects {
1316 nav_history: Option<bool>,
1317 completions: bool,
1318 scroll: Option<Autoscroll>,
1319}
1320
1321impl Default for SelectionEffects {
1322 fn default() -> Self {
1323 Self {
1324 nav_history: None,
1325 completions: true,
1326 scroll: Some(Autoscroll::fit()),
1327 }
1328 }
1329}
1330impl SelectionEffects {
1331 pub fn scroll(scroll: Autoscroll) -> Self {
1332 Self {
1333 scroll: Some(scroll),
1334 ..Default::default()
1335 }
1336 }
1337
1338 pub fn no_scroll() -> Self {
1339 Self {
1340 scroll: None,
1341 ..Default::default()
1342 }
1343 }
1344
1345 pub fn completions(self, completions: bool) -> Self {
1346 Self {
1347 completions,
1348 ..self
1349 }
1350 }
1351
1352 pub fn nav_history(self, nav_history: bool) -> Self {
1353 Self {
1354 nav_history: Some(nav_history),
1355 ..self
1356 }
1357 }
1358}
1359
1360struct DeferredSelectionEffectsState {
1361 changed: bool,
1362 effects: SelectionEffects,
1363 old_cursor_position: Anchor,
1364 history_entry: SelectionHistoryEntry,
1365}
1366
1367#[derive(Default)]
1368struct SelectionHistory {
1369 #[allow(clippy::type_complexity)]
1370 selections_by_transaction:
1371 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1372 mode: SelectionHistoryMode,
1373 undo_stack: VecDeque<SelectionHistoryEntry>,
1374 redo_stack: VecDeque<SelectionHistoryEntry>,
1375}
1376
1377impl SelectionHistory {
1378 #[track_caller]
1379 fn insert_transaction(
1380 &mut self,
1381 transaction_id: TransactionId,
1382 selections: Arc<[Selection<Anchor>]>,
1383 ) {
1384 if selections.is_empty() {
1385 log::error!(
1386 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1387 std::panic::Location::caller()
1388 );
1389 return;
1390 }
1391 self.selections_by_transaction
1392 .insert(transaction_id, (selections, None));
1393 }
1394
1395 #[allow(clippy::type_complexity)]
1396 fn transaction(
1397 &self,
1398 transaction_id: TransactionId,
1399 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1400 self.selections_by_transaction.get(&transaction_id)
1401 }
1402
1403 #[allow(clippy::type_complexity)]
1404 fn transaction_mut(
1405 &mut self,
1406 transaction_id: TransactionId,
1407 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1408 self.selections_by_transaction.get_mut(&transaction_id)
1409 }
1410
1411 fn push(&mut self, entry: SelectionHistoryEntry) {
1412 if !entry.selections.is_empty() {
1413 match self.mode {
1414 SelectionHistoryMode::Normal => {
1415 self.push_undo(entry);
1416 self.redo_stack.clear();
1417 }
1418 SelectionHistoryMode::Undoing => self.push_redo(entry),
1419 SelectionHistoryMode::Redoing => self.push_undo(entry),
1420 SelectionHistoryMode::Skipping => {}
1421 }
1422 }
1423 }
1424
1425 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1426 if self
1427 .undo_stack
1428 .back()
1429 .is_none_or(|e| e.selections != entry.selections)
1430 {
1431 self.undo_stack.push_back(entry);
1432 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1433 self.undo_stack.pop_front();
1434 }
1435 }
1436 }
1437
1438 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1439 if self
1440 .redo_stack
1441 .back()
1442 .is_none_or(|e| e.selections != entry.selections)
1443 {
1444 self.redo_stack.push_back(entry);
1445 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1446 self.redo_stack.pop_front();
1447 }
1448 }
1449 }
1450}
1451
1452#[derive(Clone, Copy)]
1453pub struct RowHighlightOptions {
1454 pub autoscroll: bool,
1455 pub include_gutter: bool,
1456}
1457
1458impl Default for RowHighlightOptions {
1459 fn default() -> Self {
1460 Self {
1461 autoscroll: Default::default(),
1462 include_gutter: true,
1463 }
1464 }
1465}
1466
1467struct RowHighlight {
1468 index: usize,
1469 range: Range<Anchor>,
1470 color: Hsla,
1471 options: RowHighlightOptions,
1472 type_id: TypeId,
1473}
1474
1475#[derive(Clone, Debug)]
1476struct AddSelectionsState {
1477 groups: Vec<AddSelectionsGroup>,
1478}
1479
1480#[derive(Clone, Debug)]
1481struct AddSelectionsGroup {
1482 above: bool,
1483 stack: Vec<usize>,
1484}
1485
1486#[derive(Clone)]
1487struct SelectNextState {
1488 query: AhoCorasick,
1489 wordwise: bool,
1490 done: bool,
1491}
1492
1493impl std::fmt::Debug for SelectNextState {
1494 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1495 f.debug_struct(std::any::type_name::<Self>())
1496 .field("wordwise", &self.wordwise)
1497 .field("done", &self.done)
1498 .finish()
1499 }
1500}
1501
1502#[derive(Debug)]
1503struct AutocloseRegion {
1504 selection_id: usize,
1505 range: Range<Anchor>,
1506 pair: BracketPair,
1507}
1508
1509#[derive(Debug)]
1510struct SnippetState {
1511 ranges: Vec<Vec<Range<Anchor>>>,
1512 active_index: usize,
1513 choices: Vec<Option<Vec<String>>>,
1514}
1515
1516#[doc(hidden)]
1517pub struct RenameState {
1518 pub range: Range<Anchor>,
1519 pub old_name: Arc<str>,
1520 pub editor: Entity<Editor>,
1521 block_id: CustomBlockId,
1522}
1523
1524struct InvalidationStack<T>(Vec<T>);
1525
1526struct RegisteredEditPredictionProvider {
1527 provider: Arc<dyn EditPredictionProviderHandle>,
1528 _subscription: Subscription,
1529}
1530
1531#[derive(Debug, PartialEq, Eq)]
1532pub struct ActiveDiagnosticGroup {
1533 pub active_range: Range<Anchor>,
1534 pub active_message: String,
1535 pub group_id: usize,
1536 pub blocks: HashSet<CustomBlockId>,
1537}
1538
1539#[derive(Debug, PartialEq, Eq)]
1540
1541pub(crate) enum ActiveDiagnostic {
1542 None,
1543 All,
1544 Group(ActiveDiagnosticGroup),
1545}
1546
1547#[derive(Serialize, Deserialize, Clone, Debug)]
1548pub struct ClipboardSelection {
1549 /// The number of bytes in this selection.
1550 pub len: usize,
1551 /// Whether this was a full-line selection.
1552 pub is_entire_line: bool,
1553 /// The indentation of the first line when this content was originally copied.
1554 pub first_line_indent: u32,
1555}
1556
1557// selections, scroll behavior, was newest selection reversed
1558type SelectSyntaxNodeHistoryState = (
1559 Box<[Selection<usize>]>,
1560 SelectSyntaxNodeScrollBehavior,
1561 bool,
1562);
1563
1564#[derive(Default)]
1565struct SelectSyntaxNodeHistory {
1566 stack: Vec<SelectSyntaxNodeHistoryState>,
1567 // disable temporarily to allow changing selections without losing the stack
1568 pub disable_clearing: bool,
1569}
1570
1571impl SelectSyntaxNodeHistory {
1572 pub fn try_clear(&mut self) {
1573 if !self.disable_clearing {
1574 self.stack.clear();
1575 }
1576 }
1577
1578 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1579 self.stack.push(selection);
1580 }
1581
1582 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1583 self.stack.pop()
1584 }
1585}
1586
1587enum SelectSyntaxNodeScrollBehavior {
1588 CursorTop,
1589 FitSelection,
1590 CursorBottom,
1591}
1592
1593#[derive(Debug)]
1594pub(crate) struct NavigationData {
1595 cursor_anchor: Anchor,
1596 cursor_position: Point,
1597 scroll_anchor: ScrollAnchor,
1598 scroll_top_row: u32,
1599}
1600
1601#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1602pub enum GotoDefinitionKind {
1603 Symbol,
1604 Declaration,
1605 Type,
1606 Implementation,
1607}
1608
1609#[derive(Debug, Clone)]
1610enum InlayHintRefreshReason {
1611 ModifiersChanged(bool),
1612 Toggle(bool),
1613 SettingsChange(InlayHintSettings),
1614 NewLinesShown,
1615 BufferEdited(HashSet<Arc<Language>>),
1616 RefreshRequested,
1617 ExcerptsRemoved(Vec<ExcerptId>),
1618}
1619
1620impl InlayHintRefreshReason {
1621 fn description(&self) -> &'static str {
1622 match self {
1623 Self::ModifiersChanged(_) => "modifiers changed",
1624 Self::Toggle(_) => "toggle",
1625 Self::SettingsChange(_) => "settings change",
1626 Self::NewLinesShown => "new lines shown",
1627 Self::BufferEdited(_) => "buffer edited",
1628 Self::RefreshRequested => "refresh requested",
1629 Self::ExcerptsRemoved(_) => "excerpts removed",
1630 }
1631 }
1632}
1633
1634pub enum FormatTarget {
1635 Buffers(HashSet<Entity<Buffer>>),
1636 Ranges(Vec<Range<MultiBufferPoint>>),
1637}
1638
1639pub(crate) struct FocusedBlock {
1640 id: BlockId,
1641 focus_handle: WeakFocusHandle,
1642}
1643
1644#[derive(Clone)]
1645enum JumpData {
1646 MultiBufferRow {
1647 row: MultiBufferRow,
1648 line_offset_from_top: u32,
1649 },
1650 MultiBufferPoint {
1651 excerpt_id: ExcerptId,
1652 position: Point,
1653 anchor: text::Anchor,
1654 line_offset_from_top: u32,
1655 },
1656}
1657
1658pub enum MultibufferSelectionMode {
1659 First,
1660 All,
1661}
1662
1663#[derive(Clone, Copy, Debug, Default)]
1664pub struct RewrapOptions {
1665 pub override_language_settings: bool,
1666 pub preserve_existing_whitespace: bool,
1667}
1668
1669impl Editor {
1670 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1671 let buffer = cx.new(|cx| Buffer::local("", cx));
1672 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1673 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1674 }
1675
1676 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1677 let buffer = cx.new(|cx| Buffer::local("", cx));
1678 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1679 Self::new(EditorMode::full(), buffer, None, window, cx)
1680 }
1681
1682 pub fn auto_height(
1683 min_lines: usize,
1684 max_lines: usize,
1685 window: &mut Window,
1686 cx: &mut Context<Self>,
1687 ) -> Self {
1688 let buffer = cx.new(|cx| Buffer::local("", cx));
1689 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1690 Self::new(
1691 EditorMode::AutoHeight {
1692 min_lines,
1693 max_lines: Some(max_lines),
1694 },
1695 buffer,
1696 None,
1697 window,
1698 cx,
1699 )
1700 }
1701
1702 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1703 /// The editor grows as tall as needed to fit its content.
1704 pub fn auto_height_unbounded(
1705 min_lines: usize,
1706 window: &mut Window,
1707 cx: &mut Context<Self>,
1708 ) -> Self {
1709 let buffer = cx.new(|cx| Buffer::local("", cx));
1710 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1711 Self::new(
1712 EditorMode::AutoHeight {
1713 min_lines,
1714 max_lines: None,
1715 },
1716 buffer,
1717 None,
1718 window,
1719 cx,
1720 )
1721 }
1722
1723 pub fn for_buffer(
1724 buffer: Entity<Buffer>,
1725 project: Option<Entity<Project>>,
1726 window: &mut Window,
1727 cx: &mut Context<Self>,
1728 ) -> Self {
1729 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1730 Self::new(EditorMode::full(), buffer, project, window, cx)
1731 }
1732
1733 pub fn for_multibuffer(
1734 buffer: Entity<MultiBuffer>,
1735 project: Option<Entity<Project>>,
1736 window: &mut Window,
1737 cx: &mut Context<Self>,
1738 ) -> Self {
1739 Self::new(EditorMode::full(), buffer, project, window, cx)
1740 }
1741
1742 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1743 let mut clone = Self::new(
1744 self.mode.clone(),
1745 self.buffer.clone(),
1746 self.project.clone(),
1747 window,
1748 cx,
1749 );
1750 self.display_map.update(cx, |display_map, cx| {
1751 let snapshot = display_map.snapshot(cx);
1752 clone.display_map.update(cx, |display_map, cx| {
1753 display_map.set_state(&snapshot, cx);
1754 });
1755 });
1756 clone.folds_did_change(cx);
1757 clone.selections.clone_state(&self.selections);
1758 clone.scroll_manager.clone_state(&self.scroll_manager);
1759 clone.searchable = self.searchable;
1760 clone.read_only = self.read_only;
1761 clone
1762 }
1763
1764 pub fn new(
1765 mode: EditorMode,
1766 buffer: Entity<MultiBuffer>,
1767 project: Option<Entity<Project>>,
1768 window: &mut Window,
1769 cx: &mut Context<Self>,
1770 ) -> Self {
1771 Editor::new_internal(mode, buffer, project, None, window, cx)
1772 }
1773
1774 fn new_internal(
1775 mode: EditorMode,
1776 buffer: Entity<MultiBuffer>,
1777 project: Option<Entity<Project>>,
1778 display_map: Option<Entity<DisplayMap>>,
1779 window: &mut Window,
1780 cx: &mut Context<Self>,
1781 ) -> Self {
1782 debug_assert!(
1783 display_map.is_none() || mode.is_minimap(),
1784 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1785 );
1786
1787 let full_mode = mode.is_full();
1788 let is_minimap = mode.is_minimap();
1789 let diagnostics_max_severity = if full_mode {
1790 EditorSettings::get_global(cx)
1791 .diagnostics_max_severity
1792 .unwrap_or(DiagnosticSeverity::Hint)
1793 } else {
1794 DiagnosticSeverity::Off
1795 };
1796 let style = window.text_style();
1797 let font_size = style.font_size.to_pixels(window.rem_size());
1798 let editor = cx.entity().downgrade();
1799 let fold_placeholder = FoldPlaceholder {
1800 constrain_width: true,
1801 render: Arc::new(move |fold_id, fold_range, cx| {
1802 let editor = editor.clone();
1803 div()
1804 .id(fold_id)
1805 .bg(cx.theme().colors().ghost_element_background)
1806 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1807 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1808 .rounded_xs()
1809 .size_full()
1810 .cursor_pointer()
1811 .child("⋯")
1812 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1813 .on_click(move |_, _window, cx| {
1814 editor
1815 .update(cx, |editor, cx| {
1816 editor.unfold_ranges(
1817 &[fold_range.start..fold_range.end],
1818 true,
1819 false,
1820 cx,
1821 );
1822 cx.stop_propagation();
1823 })
1824 .ok();
1825 })
1826 .into_any()
1827 }),
1828 merge_adjacent: true,
1829 ..FoldPlaceholder::default()
1830 };
1831 let display_map = display_map.unwrap_or_else(|| {
1832 cx.new(|cx| {
1833 DisplayMap::new(
1834 buffer.clone(),
1835 style.font(),
1836 font_size,
1837 None,
1838 FILE_HEADER_HEIGHT,
1839 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1840 fold_placeholder,
1841 diagnostics_max_severity,
1842 cx,
1843 )
1844 })
1845 });
1846
1847 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1848
1849 let blink_manager = cx.new(|cx| {
1850 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1851 if is_minimap {
1852 blink_manager.disable(cx);
1853 }
1854 blink_manager
1855 });
1856
1857 let soft_wrap_mode_override =
1858 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1859
1860 let mut project_subscriptions = Vec::new();
1861 if full_mode && let Some(project) = project.as_ref() {
1862 project_subscriptions.push(cx.subscribe_in(
1863 project,
1864 window,
1865 |editor, _, event, window, cx| match event {
1866 project::Event::RefreshCodeLens => {
1867 // we always query lens with actions, without storing them, always refreshing them
1868 }
1869 project::Event::RefreshInlayHints => {
1870 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1871 }
1872 project::Event::LanguageServerAdded(..)
1873 | project::Event::LanguageServerRemoved(..) => {
1874 if editor.tasks_update_task.is_none() {
1875 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1876 }
1877 }
1878 project::Event::SnippetEdit(id, snippet_edits) => {
1879 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1880 let focus_handle = editor.focus_handle(cx);
1881 if focus_handle.is_focused(window) {
1882 let snapshot = buffer.read(cx).snapshot();
1883 for (range, snippet) in snippet_edits {
1884 let editor_range =
1885 language::range_from_lsp(*range).to_offset(&snapshot);
1886 editor
1887 .insert_snippet(
1888 &[editor_range],
1889 snippet.clone(),
1890 window,
1891 cx,
1892 )
1893 .ok();
1894 }
1895 }
1896 }
1897 }
1898 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1899 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1900 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1901 }
1902 }
1903 _ => {}
1904 },
1905 ));
1906 if let Some(task_inventory) = project
1907 .read(cx)
1908 .task_store()
1909 .read(cx)
1910 .task_inventory()
1911 .cloned()
1912 {
1913 project_subscriptions.push(cx.observe_in(
1914 &task_inventory,
1915 window,
1916 |editor, _, window, cx| {
1917 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1918 },
1919 ));
1920 };
1921
1922 project_subscriptions.push(cx.subscribe_in(
1923 &project.read(cx).breakpoint_store(),
1924 window,
1925 |editor, _, event, window, cx| match event {
1926 BreakpointStoreEvent::ClearDebugLines => {
1927 editor.clear_row_highlights::<ActiveDebugLine>();
1928 editor.refresh_inline_values(cx);
1929 }
1930 BreakpointStoreEvent::SetDebugLine => {
1931 if editor.go_to_active_debug_line(window, cx) {
1932 cx.stop_propagation();
1933 }
1934
1935 editor.refresh_inline_values(cx);
1936 }
1937 _ => {}
1938 },
1939 ));
1940 let git_store = project.read(cx).git_store().clone();
1941 let project = project.clone();
1942 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1943 if let GitStoreEvent::RepositoryUpdated(
1944 _,
1945 RepositoryEvent::Updated {
1946 new_instance: true, ..
1947 },
1948 _,
1949 ) = event
1950 {
1951 this.load_diff_task = Some(
1952 update_uncommitted_diff_for_buffer(
1953 cx.entity(),
1954 &project,
1955 this.buffer.read(cx).all_buffers(),
1956 this.buffer.clone(),
1957 cx,
1958 )
1959 .shared(),
1960 );
1961 }
1962 }));
1963 }
1964
1965 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1966
1967 let inlay_hint_settings =
1968 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1969 let focus_handle = cx.focus_handle();
1970 if !is_minimap {
1971 cx.on_focus(&focus_handle, window, Self::handle_focus)
1972 .detach();
1973 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1974 .detach();
1975 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1976 .detach();
1977 cx.on_blur(&focus_handle, window, Self::handle_blur)
1978 .detach();
1979 cx.observe_pending_input(window, Self::observe_pending_input)
1980 .detach();
1981 }
1982
1983 let show_indent_guides =
1984 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
1985 Some(false)
1986 } else {
1987 None
1988 };
1989
1990 let breakpoint_store = match (&mode, project.as_ref()) {
1991 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1992 _ => None,
1993 };
1994
1995 let mut code_action_providers = Vec::new();
1996 let mut load_uncommitted_diff = None;
1997 if let Some(project) = project.clone() {
1998 load_uncommitted_diff = Some(
1999 update_uncommitted_diff_for_buffer(
2000 cx.entity(),
2001 &project,
2002 buffer.read(cx).all_buffers(),
2003 buffer.clone(),
2004 cx,
2005 )
2006 .shared(),
2007 );
2008 code_action_providers.push(Rc::new(project) as Rc<_>);
2009 }
2010
2011 let mut editor = Self {
2012 focus_handle,
2013 show_cursor_when_unfocused: false,
2014 last_focused_descendant: None,
2015 buffer: buffer.clone(),
2016 display_map: display_map.clone(),
2017 selections,
2018 scroll_manager: ScrollManager::new(cx),
2019 columnar_selection_state: None,
2020 add_selections_state: None,
2021 select_next_state: None,
2022 select_prev_state: None,
2023 selection_history: SelectionHistory::default(),
2024 defer_selection_effects: false,
2025 deferred_selection_effects_state: None,
2026 autoclose_regions: Vec::new(),
2027 snippet_stack: InvalidationStack::default(),
2028 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2029 ime_transaction: None,
2030 active_diagnostics: ActiveDiagnostic::None,
2031 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2032 inline_diagnostics_update: Task::ready(()),
2033 inline_diagnostics: Vec::new(),
2034 soft_wrap_mode_override,
2035 diagnostics_max_severity,
2036 hard_wrap: None,
2037 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2038 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2039 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2040 project,
2041 blink_manager: blink_manager.clone(),
2042 show_local_selections: true,
2043 show_scrollbars: ScrollbarAxes {
2044 horizontal: full_mode,
2045 vertical: full_mode,
2046 },
2047 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2048 offset_content: !matches!(mode, EditorMode::SingleLine),
2049 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2050 show_gutter: full_mode,
2051 show_line_numbers: (!full_mode).then_some(false),
2052 use_relative_line_numbers: None,
2053 disable_expand_excerpt_buttons: !full_mode,
2054 show_git_diff_gutter: None,
2055 show_code_actions: None,
2056 show_runnables: None,
2057 show_breakpoints: None,
2058 show_wrap_guides: None,
2059 show_indent_guides,
2060 placeholder_text: None,
2061 highlight_order: 0,
2062 highlighted_rows: HashMap::default(),
2063 background_highlights: TreeMap::default(),
2064 gutter_highlights: TreeMap::default(),
2065 scrollbar_marker_state: ScrollbarMarkerState::default(),
2066 active_indent_guides_state: ActiveIndentGuidesState::default(),
2067 nav_history: None,
2068 context_menu: RefCell::new(None),
2069 context_menu_options: None,
2070 mouse_context_menu: None,
2071 completion_tasks: Vec::new(),
2072 inline_blame_popover: None,
2073 inline_blame_popover_show_task: None,
2074 signature_help_state: SignatureHelpState::default(),
2075 auto_signature_help: None,
2076 find_all_references_task_sources: Vec::new(),
2077 next_completion_id: 0,
2078 next_inlay_id: 0,
2079 code_action_providers,
2080 available_code_actions: None,
2081 code_actions_task: None,
2082 quick_selection_highlight_task: None,
2083 debounced_selection_highlight_task: None,
2084 document_highlights_task: None,
2085 linked_editing_range_task: None,
2086 pending_rename: None,
2087 searchable: !is_minimap,
2088 cursor_shape: EditorSettings::get_global(cx)
2089 .cursor_shape
2090 .unwrap_or_default(),
2091 current_line_highlight: None,
2092 autoindent_mode: Some(AutoindentMode::EachLine),
2093 collapse_matches: false,
2094 workspace: None,
2095 input_enabled: !is_minimap,
2096 use_modal_editing: full_mode,
2097 read_only: is_minimap,
2098 use_autoclose: true,
2099 use_auto_surround: true,
2100 auto_replace_emoji_shortcode: false,
2101 jsx_tag_auto_close_enabled_in_any_buffer: false,
2102 leader_id: None,
2103 remote_id: None,
2104 hover_state: HoverState::default(),
2105 pending_mouse_down: None,
2106 hovered_link_state: None,
2107 edit_prediction_provider: None,
2108 active_edit_prediction: None,
2109 stale_edit_prediction_in_menu: None,
2110 edit_prediction_preview: EditPredictionPreview::Inactive {
2111 released_too_fast: false,
2112 },
2113 inline_diagnostics_enabled: full_mode,
2114 diagnostics_enabled: full_mode,
2115 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2116 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2117 gutter_hovered: false,
2118 pixel_position_of_newest_cursor: None,
2119 last_bounds: None,
2120 last_position_map: None,
2121 expect_bounds_change: None,
2122 gutter_dimensions: GutterDimensions::default(),
2123 style: None,
2124 show_cursor_names: false,
2125 hovered_cursors: HashMap::default(),
2126 next_editor_action_id: EditorActionId::default(),
2127 editor_actions: Rc::default(),
2128 edit_predictions_hidden_for_vim_mode: false,
2129 show_edit_predictions_override: None,
2130 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2131 edit_prediction_settings: EditPredictionSettings::Disabled,
2132 edit_prediction_indent_conflict: false,
2133 edit_prediction_requires_modifier_in_indent_conflict: true,
2134 custom_context_menu: None,
2135 show_git_blame_gutter: false,
2136 show_git_blame_inline: false,
2137 show_selection_menu: None,
2138 show_git_blame_inline_delay_task: None,
2139 git_blame_inline_enabled: full_mode
2140 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2141 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2142 serialize_dirty_buffers: !is_minimap
2143 && ProjectSettings::get_global(cx)
2144 .session
2145 .restore_unsaved_buffers,
2146 blame: None,
2147 blame_subscription: None,
2148 tasks: BTreeMap::default(),
2149
2150 breakpoint_store,
2151 gutter_breakpoint_indicator: (None, None),
2152 hovered_diff_hunk_row: None,
2153 _subscriptions: (!is_minimap)
2154 .then(|| {
2155 vec![
2156 cx.observe(&buffer, Self::on_buffer_changed),
2157 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2158 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2159 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2160 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2161 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2162 cx.observe_window_activation(window, |editor, window, cx| {
2163 let active = window.is_window_active();
2164 editor.blink_manager.update(cx, |blink_manager, cx| {
2165 if active {
2166 blink_manager.enable(cx);
2167 } else {
2168 blink_manager.disable(cx);
2169 }
2170 });
2171 if active {
2172 editor.show_mouse_cursor(cx);
2173 }
2174 }),
2175 ]
2176 })
2177 .unwrap_or_default(),
2178 tasks_update_task: None,
2179 pull_diagnostics_task: Task::ready(()),
2180 colors: None,
2181 next_color_inlay_id: 0,
2182 linked_edit_ranges: Default::default(),
2183 in_project_search: false,
2184 previous_search_ranges: None,
2185 breadcrumb_header: None,
2186 focused_block: None,
2187 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2188 addons: HashMap::default(),
2189 registered_buffers: HashMap::default(),
2190 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2191 selection_mark_mode: false,
2192 toggle_fold_multiple_buffers: Task::ready(()),
2193 serialize_selections: Task::ready(()),
2194 serialize_folds: Task::ready(()),
2195 text_style_refinement: None,
2196 load_diff_task: load_uncommitted_diff,
2197 temporary_diff_override: false,
2198 mouse_cursor_hidden: false,
2199 minimap: None,
2200 hide_mouse_mode: EditorSettings::get_global(cx)
2201 .hide_mouse
2202 .unwrap_or_default(),
2203 change_list: ChangeList::new(),
2204 mode,
2205 selection_drag_state: SelectionDragState::None,
2206 folding_newlines: Task::ready(()),
2207 };
2208
2209 if is_minimap {
2210 return editor;
2211 }
2212
2213 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2214 editor
2215 ._subscriptions
2216 .push(cx.observe(breakpoints, |_, _, cx| {
2217 cx.notify();
2218 }));
2219 }
2220 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2221 editor._subscriptions.extend(project_subscriptions);
2222
2223 editor._subscriptions.push(cx.subscribe_in(
2224 &cx.entity(),
2225 window,
2226 |editor, _, e: &EditorEvent, window, cx| match e {
2227 EditorEvent::ScrollPositionChanged { local, .. } => {
2228 if *local {
2229 let new_anchor = editor.scroll_manager.anchor();
2230 let snapshot = editor.snapshot(window, cx);
2231 editor.update_restoration_data(cx, move |data| {
2232 data.scroll_position = (
2233 new_anchor.top_row(&snapshot.buffer_snapshot),
2234 new_anchor.offset,
2235 );
2236 });
2237 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2238 editor.inline_blame_popover.take();
2239 }
2240 }
2241 EditorEvent::Edited { .. } => {
2242 if !vim_enabled(cx) {
2243 let (map, selections) = editor.selections.all_adjusted_display(cx);
2244 let pop_state = editor
2245 .change_list
2246 .last()
2247 .map(|previous| {
2248 previous.len() == selections.len()
2249 && previous.iter().enumerate().all(|(ix, p)| {
2250 p.to_display_point(&map).row()
2251 == selections[ix].head().row()
2252 })
2253 })
2254 .unwrap_or(false);
2255 let new_positions = selections
2256 .into_iter()
2257 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2258 .collect();
2259 editor
2260 .change_list
2261 .push_to_change_list(pop_state, new_positions);
2262 }
2263 }
2264 _ => (),
2265 },
2266 ));
2267
2268 if let Some(dap_store) = editor
2269 .project
2270 .as_ref()
2271 .map(|project| project.read(cx).dap_store())
2272 {
2273 let weak_editor = cx.weak_entity();
2274
2275 editor
2276 ._subscriptions
2277 .push(
2278 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2279 let session_entity = cx.entity();
2280 weak_editor
2281 .update(cx, |editor, cx| {
2282 editor._subscriptions.push(
2283 cx.subscribe(&session_entity, Self::on_debug_session_event),
2284 );
2285 })
2286 .ok();
2287 }),
2288 );
2289
2290 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2291 editor
2292 ._subscriptions
2293 .push(cx.subscribe(&session, Self::on_debug_session_event));
2294 }
2295 }
2296
2297 // skip adding the initial selection to selection history
2298 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2299 editor.end_selection(window, cx);
2300 editor.selection_history.mode = SelectionHistoryMode::Normal;
2301
2302 editor.scroll_manager.show_scrollbars(window, cx);
2303 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2304
2305 if full_mode {
2306 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2307 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2308
2309 if editor.git_blame_inline_enabled {
2310 editor.start_git_blame_inline(false, window, cx);
2311 }
2312
2313 editor.go_to_active_debug_line(window, cx);
2314
2315 if let Some(buffer) = buffer.read(cx).as_singleton()
2316 && let Some(project) = editor.project()
2317 {
2318 let handle = project.update(cx, |project, cx| {
2319 project.register_buffer_with_language_servers(&buffer, cx)
2320 });
2321 editor
2322 .registered_buffers
2323 .insert(buffer.read(cx).remote_id(), handle);
2324 }
2325
2326 editor.minimap =
2327 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2328 editor.colors = Some(LspColorData::new(cx));
2329 editor.update_lsp_data(false, None, window, cx);
2330 }
2331
2332 if editor.mode.is_full() {
2333 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2334 }
2335
2336 editor
2337 }
2338
2339 pub fn deploy_mouse_context_menu(
2340 &mut self,
2341 position: gpui::Point<Pixels>,
2342 context_menu: Entity<ContextMenu>,
2343 window: &mut Window,
2344 cx: &mut Context<Self>,
2345 ) {
2346 self.mouse_context_menu = Some(MouseContextMenu::new(
2347 self,
2348 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2349 context_menu,
2350 window,
2351 cx,
2352 ));
2353 }
2354
2355 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2356 self.mouse_context_menu
2357 .as_ref()
2358 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2359 }
2360
2361 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2362 if self
2363 .selections
2364 .pending
2365 .as_ref()
2366 .is_some_and(|pending_selection| {
2367 let snapshot = self.buffer().read(cx).snapshot(cx);
2368 pending_selection
2369 .selection
2370 .range()
2371 .includes(range, &snapshot)
2372 })
2373 {
2374 return true;
2375 }
2376
2377 self.selections
2378 .disjoint_in_range::<usize>(range.clone(), cx)
2379 .into_iter()
2380 .any(|selection| {
2381 // This is needed to cover a corner case, if we just check for an existing
2382 // selection in the fold range, having a cursor at the start of the fold
2383 // marks it as selected. Non-empty selections don't cause this.
2384 let length = selection.end - selection.start;
2385 length > 0
2386 })
2387 }
2388
2389 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2390 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2391 }
2392
2393 fn key_context_internal(
2394 &self,
2395 has_active_edit_prediction: bool,
2396 window: &Window,
2397 cx: &App,
2398 ) -> KeyContext {
2399 let mut key_context = KeyContext::new_with_defaults();
2400 key_context.add("Editor");
2401 let mode = match self.mode {
2402 EditorMode::SingleLine => "single_line",
2403 EditorMode::AutoHeight { .. } => "auto_height",
2404 EditorMode::Minimap { .. } => "minimap",
2405 EditorMode::Full { .. } => "full",
2406 };
2407
2408 if EditorSettings::jupyter_enabled(cx) {
2409 key_context.add("jupyter");
2410 }
2411
2412 key_context.set("mode", mode);
2413 if self.pending_rename.is_some() {
2414 key_context.add("renaming");
2415 }
2416
2417 match self.context_menu.borrow().as_ref() {
2418 Some(CodeContextMenu::Completions(menu)) => {
2419 if menu.visible() {
2420 key_context.add("menu");
2421 key_context.add("showing_completions");
2422 }
2423 }
2424 Some(CodeContextMenu::CodeActions(menu)) => {
2425 if menu.visible() {
2426 key_context.add("menu");
2427 key_context.add("showing_code_actions")
2428 }
2429 }
2430 None => {}
2431 }
2432
2433 if self.signature_help_state.has_multiple_signatures() {
2434 key_context.add("showing_signature_help");
2435 }
2436
2437 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2438 if !self.focus_handle(cx).contains_focused(window, cx)
2439 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2440 {
2441 for addon in self.addons.values() {
2442 addon.extend_key_context(&mut key_context, cx)
2443 }
2444 }
2445
2446 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2447 if let Some(extension) = singleton_buffer
2448 .read(cx)
2449 .file()
2450 .and_then(|file| file.path().extension()?.to_str())
2451 {
2452 key_context.set("extension", extension.to_string());
2453 }
2454 } else {
2455 key_context.add("multibuffer");
2456 }
2457
2458 if has_active_edit_prediction {
2459 if self.edit_prediction_in_conflict() {
2460 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2461 } else {
2462 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2463 key_context.add("copilot_suggestion");
2464 }
2465 }
2466
2467 if self.selection_mark_mode {
2468 key_context.add("selection_mode");
2469 }
2470
2471 key_context
2472 }
2473
2474 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2475 if self.mouse_cursor_hidden {
2476 self.mouse_cursor_hidden = false;
2477 cx.notify();
2478 }
2479 }
2480
2481 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2482 let hide_mouse_cursor = match origin {
2483 HideMouseCursorOrigin::TypingAction => {
2484 matches!(
2485 self.hide_mouse_mode,
2486 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2487 )
2488 }
2489 HideMouseCursorOrigin::MovementAction => {
2490 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2491 }
2492 };
2493 if self.mouse_cursor_hidden != hide_mouse_cursor {
2494 self.mouse_cursor_hidden = hide_mouse_cursor;
2495 cx.notify();
2496 }
2497 }
2498
2499 pub fn edit_prediction_in_conflict(&self) -> bool {
2500 if !self.show_edit_predictions_in_menu() {
2501 return false;
2502 }
2503
2504 let showing_completions = self
2505 .context_menu
2506 .borrow()
2507 .as_ref()
2508 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2509
2510 showing_completions
2511 || self.edit_prediction_requires_modifier()
2512 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2513 // bindings to insert tab characters.
2514 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2515 }
2516
2517 pub fn accept_edit_prediction_keybind(
2518 &self,
2519 accept_partial: bool,
2520 window: &Window,
2521 cx: &App,
2522 ) -> AcceptEditPredictionBinding {
2523 let key_context = self.key_context_internal(true, window, cx);
2524 let in_conflict = self.edit_prediction_in_conflict();
2525
2526 let bindings = if accept_partial {
2527 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2528 } else {
2529 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2530 };
2531
2532 // TODO: if the binding contains multiple keystrokes, display all of them, not
2533 // just the first one.
2534 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2535 !in_conflict
2536 || binding
2537 .keystrokes()
2538 .first()
2539 .is_some_and(|keystroke| keystroke.modifiers.modified())
2540 }))
2541 }
2542
2543 pub fn new_file(
2544 workspace: &mut Workspace,
2545 _: &workspace::NewFile,
2546 window: &mut Window,
2547 cx: &mut Context<Workspace>,
2548 ) {
2549 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2550 "Failed to create buffer",
2551 window,
2552 cx,
2553 |e, _, _| match e.error_code() {
2554 ErrorCode::RemoteUpgradeRequired => Some(format!(
2555 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2556 e.error_tag("required").unwrap_or("the latest version")
2557 )),
2558 _ => None,
2559 },
2560 );
2561 }
2562
2563 pub fn new_in_workspace(
2564 workspace: &mut Workspace,
2565 window: &mut Window,
2566 cx: &mut Context<Workspace>,
2567 ) -> Task<Result<Entity<Editor>>> {
2568 let project = workspace.project().clone();
2569 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2570
2571 cx.spawn_in(window, async move |workspace, cx| {
2572 let buffer = create.await?;
2573 workspace.update_in(cx, |workspace, window, cx| {
2574 let editor =
2575 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2576 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2577 editor
2578 })
2579 })
2580 }
2581
2582 fn new_file_vertical(
2583 workspace: &mut Workspace,
2584 _: &workspace::NewFileSplitVertical,
2585 window: &mut Window,
2586 cx: &mut Context<Workspace>,
2587 ) {
2588 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2589 }
2590
2591 fn new_file_horizontal(
2592 workspace: &mut Workspace,
2593 _: &workspace::NewFileSplitHorizontal,
2594 window: &mut Window,
2595 cx: &mut Context<Workspace>,
2596 ) {
2597 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2598 }
2599
2600 fn new_file_in_direction(
2601 workspace: &mut Workspace,
2602 direction: SplitDirection,
2603 window: &mut Window,
2604 cx: &mut Context<Workspace>,
2605 ) {
2606 let project = workspace.project().clone();
2607 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2608
2609 cx.spawn_in(window, async move |workspace, cx| {
2610 let buffer = create.await?;
2611 workspace.update_in(cx, move |workspace, window, cx| {
2612 workspace.split_item(
2613 direction,
2614 Box::new(
2615 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2616 ),
2617 window,
2618 cx,
2619 )
2620 })?;
2621 anyhow::Ok(())
2622 })
2623 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2624 match e.error_code() {
2625 ErrorCode::RemoteUpgradeRequired => Some(format!(
2626 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2627 e.error_tag("required").unwrap_or("the latest version")
2628 )),
2629 _ => None,
2630 }
2631 });
2632 }
2633
2634 pub fn leader_id(&self) -> Option<CollaboratorId> {
2635 self.leader_id
2636 }
2637
2638 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2639 &self.buffer
2640 }
2641
2642 pub fn project(&self) -> Option<&Entity<Project>> {
2643 self.project.as_ref()
2644 }
2645
2646 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2647 self.workspace.as_ref()?.0.upgrade()
2648 }
2649
2650 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2651 self.buffer().read(cx).title(cx)
2652 }
2653
2654 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2655 let git_blame_gutter_max_author_length = self
2656 .render_git_blame_gutter(cx)
2657 .then(|| {
2658 if let Some(blame) = self.blame.as_ref() {
2659 let max_author_length =
2660 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2661 Some(max_author_length)
2662 } else {
2663 None
2664 }
2665 })
2666 .flatten();
2667
2668 EditorSnapshot {
2669 mode: self.mode.clone(),
2670 show_gutter: self.show_gutter,
2671 show_line_numbers: self.show_line_numbers,
2672 show_git_diff_gutter: self.show_git_diff_gutter,
2673 show_code_actions: self.show_code_actions,
2674 show_runnables: self.show_runnables,
2675 show_breakpoints: self.show_breakpoints,
2676 git_blame_gutter_max_author_length,
2677 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2678 scroll_anchor: self.scroll_manager.anchor(),
2679 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2680 placeholder_text: self.placeholder_text.clone(),
2681 is_focused: self.focus_handle.is_focused(window),
2682 current_line_highlight: self
2683 .current_line_highlight
2684 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2685 gutter_hovered: self.gutter_hovered,
2686 }
2687 }
2688
2689 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2690 self.buffer.read(cx).language_at(point, cx)
2691 }
2692
2693 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2694 self.buffer.read(cx).read(cx).file_at(point).cloned()
2695 }
2696
2697 pub fn active_excerpt(
2698 &self,
2699 cx: &App,
2700 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2701 self.buffer
2702 .read(cx)
2703 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2704 }
2705
2706 pub fn mode(&self) -> &EditorMode {
2707 &self.mode
2708 }
2709
2710 pub fn set_mode(&mut self, mode: EditorMode) {
2711 self.mode = mode;
2712 }
2713
2714 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2715 self.collaboration_hub.as_deref()
2716 }
2717
2718 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2719 self.collaboration_hub = Some(hub);
2720 }
2721
2722 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2723 self.in_project_search = in_project_search;
2724 }
2725
2726 pub fn set_custom_context_menu(
2727 &mut self,
2728 f: impl 'static
2729 + Fn(
2730 &mut Self,
2731 DisplayPoint,
2732 &mut Window,
2733 &mut Context<Self>,
2734 ) -> Option<Entity<ui::ContextMenu>>,
2735 ) {
2736 self.custom_context_menu = Some(Box::new(f))
2737 }
2738
2739 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2740 self.completion_provider = provider;
2741 }
2742
2743 #[cfg(any(test, feature = "test-support"))]
2744 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2745 self.completion_provider.clone()
2746 }
2747
2748 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2749 self.semantics_provider.clone()
2750 }
2751
2752 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2753 self.semantics_provider = provider;
2754 }
2755
2756 pub fn set_edit_prediction_provider<T>(
2757 &mut self,
2758 provider: Option<Entity<T>>,
2759 window: &mut Window,
2760 cx: &mut Context<Self>,
2761 ) where
2762 T: EditPredictionProvider,
2763 {
2764 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2765 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2766 if this.focus_handle.is_focused(window) {
2767 this.update_visible_edit_prediction(window, cx);
2768 }
2769 }),
2770 provider: Arc::new(provider),
2771 });
2772 self.update_edit_prediction_settings(cx);
2773 self.refresh_edit_prediction(false, false, window, cx);
2774 }
2775
2776 pub fn placeholder_text(&self) -> Option<&str> {
2777 self.placeholder_text.as_deref()
2778 }
2779
2780 pub fn set_placeholder_text(
2781 &mut self,
2782 placeholder_text: impl Into<Arc<str>>,
2783 cx: &mut Context<Self>,
2784 ) {
2785 let placeholder_text = Some(placeholder_text.into());
2786 if self.placeholder_text != placeholder_text {
2787 self.placeholder_text = placeholder_text;
2788 cx.notify();
2789 }
2790 }
2791
2792 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2793 self.cursor_shape = cursor_shape;
2794
2795 // Disrupt blink for immediate user feedback that the cursor shape has changed
2796 self.blink_manager.update(cx, BlinkManager::show_cursor);
2797
2798 cx.notify();
2799 }
2800
2801 pub fn set_current_line_highlight(
2802 &mut self,
2803 current_line_highlight: Option<CurrentLineHighlight>,
2804 ) {
2805 self.current_line_highlight = current_line_highlight;
2806 }
2807
2808 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2809 self.collapse_matches = collapse_matches;
2810 }
2811
2812 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2813 let buffers = self.buffer.read(cx).all_buffers();
2814 let Some(project) = self.project.as_ref() else {
2815 return;
2816 };
2817 project.update(cx, |project, cx| {
2818 for buffer in buffers {
2819 self.registered_buffers
2820 .entry(buffer.read(cx).remote_id())
2821 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2822 }
2823 })
2824 }
2825
2826 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2827 if self.collapse_matches {
2828 return range.start..range.start;
2829 }
2830 range.clone()
2831 }
2832
2833 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2834 if self.display_map.read(cx).clip_at_line_ends != clip {
2835 self.display_map
2836 .update(cx, |map, _| map.clip_at_line_ends = clip);
2837 }
2838 }
2839
2840 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2841 self.input_enabled = input_enabled;
2842 }
2843
2844 pub fn set_edit_predictions_hidden_for_vim_mode(
2845 &mut self,
2846 hidden: bool,
2847 window: &mut Window,
2848 cx: &mut Context<Self>,
2849 ) {
2850 if hidden != self.edit_predictions_hidden_for_vim_mode {
2851 self.edit_predictions_hidden_for_vim_mode = hidden;
2852 if hidden {
2853 self.update_visible_edit_prediction(window, cx);
2854 } else {
2855 self.refresh_edit_prediction(true, false, window, cx);
2856 }
2857 }
2858 }
2859
2860 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2861 self.menu_edit_predictions_policy = value;
2862 }
2863
2864 pub fn set_autoindent(&mut self, autoindent: bool) {
2865 if autoindent {
2866 self.autoindent_mode = Some(AutoindentMode::EachLine);
2867 } else {
2868 self.autoindent_mode = None;
2869 }
2870 }
2871
2872 pub fn read_only(&self, cx: &App) -> bool {
2873 self.read_only || self.buffer.read(cx).read_only()
2874 }
2875
2876 pub fn set_read_only(&mut self, read_only: bool) {
2877 self.read_only = read_only;
2878 }
2879
2880 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2881 self.use_autoclose = autoclose;
2882 }
2883
2884 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2885 self.use_auto_surround = auto_surround;
2886 }
2887
2888 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2889 self.auto_replace_emoji_shortcode = auto_replace;
2890 }
2891
2892 pub fn toggle_edit_predictions(
2893 &mut self,
2894 _: &ToggleEditPrediction,
2895 window: &mut Window,
2896 cx: &mut Context<Self>,
2897 ) {
2898 if self.show_edit_predictions_override.is_some() {
2899 self.set_show_edit_predictions(None, window, cx);
2900 } else {
2901 let show_edit_predictions = !self.edit_predictions_enabled();
2902 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2903 }
2904 }
2905
2906 pub fn set_show_edit_predictions(
2907 &mut self,
2908 show_edit_predictions: Option<bool>,
2909 window: &mut Window,
2910 cx: &mut Context<Self>,
2911 ) {
2912 self.show_edit_predictions_override = show_edit_predictions;
2913 self.update_edit_prediction_settings(cx);
2914
2915 if let Some(false) = show_edit_predictions {
2916 self.discard_edit_prediction(false, cx);
2917 } else {
2918 self.refresh_edit_prediction(false, true, window, cx);
2919 }
2920 }
2921
2922 fn edit_predictions_disabled_in_scope(
2923 &self,
2924 buffer: &Entity<Buffer>,
2925 buffer_position: language::Anchor,
2926 cx: &App,
2927 ) -> bool {
2928 let snapshot = buffer.read(cx).snapshot();
2929 let settings = snapshot.settings_at(buffer_position, cx);
2930
2931 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2932 return false;
2933 };
2934
2935 scope.override_name().is_some_and(|scope_name| {
2936 settings
2937 .edit_predictions_disabled_in
2938 .iter()
2939 .any(|s| s == scope_name)
2940 })
2941 }
2942
2943 pub fn set_use_modal_editing(&mut self, to: bool) {
2944 self.use_modal_editing = to;
2945 }
2946
2947 pub fn use_modal_editing(&self) -> bool {
2948 self.use_modal_editing
2949 }
2950
2951 fn selections_did_change(
2952 &mut self,
2953 local: bool,
2954 old_cursor_position: &Anchor,
2955 effects: SelectionEffects,
2956 window: &mut Window,
2957 cx: &mut Context<Self>,
2958 ) {
2959 window.invalidate_character_coordinates();
2960
2961 // Copy selections to primary selection buffer
2962 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2963 if local {
2964 let selections = self.selections.all::<usize>(cx);
2965 let buffer_handle = self.buffer.read(cx).read(cx);
2966
2967 let mut text = String::new();
2968 for (index, selection) in selections.iter().enumerate() {
2969 let text_for_selection = buffer_handle
2970 .text_for_range(selection.start..selection.end)
2971 .collect::<String>();
2972
2973 text.push_str(&text_for_selection);
2974 if index != selections.len() - 1 {
2975 text.push('\n');
2976 }
2977 }
2978
2979 if !text.is_empty() {
2980 cx.write_to_primary(ClipboardItem::new_string(text));
2981 }
2982 }
2983
2984 let selection_anchors = self.selections.disjoint_anchors();
2985
2986 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2987 self.buffer.update(cx, |buffer, cx| {
2988 buffer.set_active_selections(
2989 &selection_anchors,
2990 self.selections.line_mode,
2991 self.cursor_shape,
2992 cx,
2993 )
2994 });
2995 }
2996 let display_map = self
2997 .display_map
2998 .update(cx, |display_map, cx| display_map.snapshot(cx));
2999 let buffer = &display_map.buffer_snapshot;
3000 if self.selections.count() == 1 {
3001 self.add_selections_state = None;
3002 }
3003 self.select_next_state = None;
3004 self.select_prev_state = None;
3005 self.select_syntax_node_history.try_clear();
3006 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3007 self.snippet_stack.invalidate(&selection_anchors, buffer);
3008 self.take_rename(false, window, cx);
3009
3010 let newest_selection = self.selections.newest_anchor();
3011 let new_cursor_position = newest_selection.head();
3012 let selection_start = newest_selection.start;
3013
3014 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3015 self.push_to_nav_history(
3016 *old_cursor_position,
3017 Some(new_cursor_position.to_point(buffer)),
3018 false,
3019 effects.nav_history == Some(true),
3020 cx,
3021 );
3022 }
3023
3024 if local {
3025 if let Some(buffer_id) = new_cursor_position.buffer_id
3026 && !self.registered_buffers.contains_key(&buffer_id)
3027 && let Some(project) = self.project.as_ref()
3028 {
3029 project.update(cx, |project, cx| {
3030 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3031 return;
3032 };
3033 self.registered_buffers.insert(
3034 buffer_id,
3035 project.register_buffer_with_language_servers(&buffer, cx),
3036 );
3037 })
3038 }
3039
3040 let mut context_menu = self.context_menu.borrow_mut();
3041 let completion_menu = match context_menu.as_ref() {
3042 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3043 Some(CodeContextMenu::CodeActions(_)) => {
3044 *context_menu = None;
3045 None
3046 }
3047 None => None,
3048 };
3049 let completion_position = completion_menu.map(|menu| menu.initial_position);
3050 drop(context_menu);
3051
3052 if effects.completions
3053 && let Some(completion_position) = completion_position
3054 {
3055 let start_offset = selection_start.to_offset(buffer);
3056 let position_matches = start_offset == completion_position.to_offset(buffer);
3057 let continue_showing = if position_matches {
3058 if self.snippet_stack.is_empty() {
3059 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3060 } else {
3061 // Snippet choices can be shown even when the cursor is in whitespace.
3062 // Dismissing the menu with actions like backspace is handled by
3063 // invalidation regions.
3064 true
3065 }
3066 } else {
3067 false
3068 };
3069
3070 if continue_showing {
3071 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3072 } else {
3073 self.hide_context_menu(window, cx);
3074 }
3075 }
3076
3077 hide_hover(self, cx);
3078
3079 if old_cursor_position.to_display_point(&display_map).row()
3080 != new_cursor_position.to_display_point(&display_map).row()
3081 {
3082 self.available_code_actions.take();
3083 }
3084 self.refresh_code_actions(window, cx);
3085 self.refresh_document_highlights(cx);
3086 self.refresh_selected_text_highlights(false, window, cx);
3087 refresh_matching_bracket_highlights(self, window, cx);
3088 self.update_visible_edit_prediction(window, cx);
3089 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3090 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3091 self.inline_blame_popover.take();
3092 if self.git_blame_inline_enabled {
3093 self.start_inline_blame_timer(window, cx);
3094 }
3095 }
3096
3097 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3098 cx.emit(EditorEvent::SelectionsChanged { local });
3099
3100 let selections = &self.selections.disjoint;
3101 if selections.len() == 1 {
3102 cx.emit(SearchEvent::ActiveMatchChanged)
3103 }
3104 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3105 let inmemory_selections = selections
3106 .iter()
3107 .map(|s| {
3108 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3109 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3110 })
3111 .collect();
3112 self.update_restoration_data(cx, |data| {
3113 data.selections = inmemory_selections;
3114 });
3115
3116 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3117 && let Some(workspace_id) =
3118 self.workspace.as_ref().and_then(|workspace| workspace.1)
3119 {
3120 let snapshot = self.buffer().read(cx).snapshot(cx);
3121 let selections = selections.clone();
3122 let background_executor = cx.background_executor().clone();
3123 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3124 self.serialize_selections = cx.background_spawn(async move {
3125 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3126 let db_selections = selections
3127 .iter()
3128 .map(|selection| {
3129 (
3130 selection.start.to_offset(&snapshot),
3131 selection.end.to_offset(&snapshot),
3132 )
3133 })
3134 .collect();
3135
3136 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3137 .await
3138 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3139 .log_err();
3140 });
3141 }
3142 }
3143
3144 cx.notify();
3145 }
3146
3147 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3148 use text::ToOffset as _;
3149 use text::ToPoint as _;
3150
3151 if self.mode.is_minimap()
3152 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3153 {
3154 return;
3155 }
3156
3157 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3158 return;
3159 };
3160
3161 let snapshot = singleton.read(cx).snapshot();
3162 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3163 let display_snapshot = display_map.snapshot(cx);
3164
3165 display_snapshot
3166 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3167 .map(|fold| {
3168 fold.range.start.text_anchor.to_point(&snapshot)
3169 ..fold.range.end.text_anchor.to_point(&snapshot)
3170 })
3171 .collect()
3172 });
3173 self.update_restoration_data(cx, |data| {
3174 data.folds = inmemory_folds;
3175 });
3176
3177 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3178 return;
3179 };
3180 let background_executor = cx.background_executor().clone();
3181 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3182 let db_folds = self.display_map.update(cx, |display_map, cx| {
3183 display_map
3184 .snapshot(cx)
3185 .folds_in_range(0..snapshot.len())
3186 .map(|fold| {
3187 (
3188 fold.range.start.text_anchor.to_offset(&snapshot),
3189 fold.range.end.text_anchor.to_offset(&snapshot),
3190 )
3191 })
3192 .collect()
3193 });
3194 self.serialize_folds = cx.background_spawn(async move {
3195 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3196 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3197 .await
3198 .with_context(|| {
3199 format!(
3200 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3201 )
3202 })
3203 .log_err();
3204 });
3205 }
3206
3207 pub fn sync_selections(
3208 &mut self,
3209 other: Entity<Editor>,
3210 cx: &mut Context<Self>,
3211 ) -> gpui::Subscription {
3212 let other_selections = other.read(cx).selections.disjoint.to_vec();
3213 self.selections.change_with(cx, |selections| {
3214 selections.select_anchors(other_selections);
3215 });
3216
3217 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3218 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3219 let other_selections = other.read(cx).selections.disjoint.to_vec();
3220 if other_selections.is_empty() {
3221 return;
3222 }
3223 this.selections.change_with(cx, |selections| {
3224 selections.select_anchors(other_selections);
3225 });
3226 }
3227 });
3228
3229 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3230 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3231 let these_selections = this.selections.disjoint.to_vec();
3232 if these_selections.is_empty() {
3233 return;
3234 }
3235 other.update(cx, |other_editor, cx| {
3236 other_editor.selections.change_with(cx, |selections| {
3237 selections.select_anchors(these_selections);
3238 })
3239 });
3240 }
3241 });
3242
3243 Subscription::join(other_subscription, this_subscription)
3244 }
3245
3246 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3247 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3248 /// effects of selection change occur at the end of the transaction.
3249 pub fn change_selections<R>(
3250 &mut self,
3251 effects: SelectionEffects,
3252 window: &mut Window,
3253 cx: &mut Context<Self>,
3254 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3255 ) -> R {
3256 if let Some(state) = &mut self.deferred_selection_effects_state {
3257 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3258 state.effects.completions = effects.completions;
3259 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3260 let (changed, result) = self.selections.change_with(cx, change);
3261 state.changed |= changed;
3262 return result;
3263 }
3264 let mut state = DeferredSelectionEffectsState {
3265 changed: false,
3266 effects,
3267 old_cursor_position: self.selections.newest_anchor().head(),
3268 history_entry: SelectionHistoryEntry {
3269 selections: self.selections.disjoint_anchors(),
3270 select_next_state: self.select_next_state.clone(),
3271 select_prev_state: self.select_prev_state.clone(),
3272 add_selections_state: self.add_selections_state.clone(),
3273 },
3274 };
3275 let (changed, result) = self.selections.change_with(cx, change);
3276 state.changed = state.changed || changed;
3277 if self.defer_selection_effects {
3278 self.deferred_selection_effects_state = Some(state);
3279 } else {
3280 self.apply_selection_effects(state, window, cx);
3281 }
3282 result
3283 }
3284
3285 /// Defers the effects of selection change, so that the effects of multiple calls to
3286 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3287 /// to selection history and the state of popovers based on selection position aren't
3288 /// erroneously updated.
3289 pub fn with_selection_effects_deferred<R>(
3290 &mut self,
3291 window: &mut Window,
3292 cx: &mut Context<Self>,
3293 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3294 ) -> R {
3295 let already_deferred = self.defer_selection_effects;
3296 self.defer_selection_effects = true;
3297 let result = update(self, window, cx);
3298 if !already_deferred {
3299 self.defer_selection_effects = false;
3300 if let Some(state) = self.deferred_selection_effects_state.take() {
3301 self.apply_selection_effects(state, window, cx);
3302 }
3303 }
3304 result
3305 }
3306
3307 fn apply_selection_effects(
3308 &mut self,
3309 state: DeferredSelectionEffectsState,
3310 window: &mut Window,
3311 cx: &mut Context<Self>,
3312 ) {
3313 if state.changed {
3314 self.selection_history.push(state.history_entry);
3315
3316 if let Some(autoscroll) = state.effects.scroll {
3317 self.request_autoscroll(autoscroll, cx);
3318 }
3319
3320 let old_cursor_position = &state.old_cursor_position;
3321
3322 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3323
3324 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3325 self.show_signature_help(&ShowSignatureHelp, window, cx);
3326 }
3327 }
3328 }
3329
3330 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3331 where
3332 I: IntoIterator<Item = (Range<S>, T)>,
3333 S: ToOffset,
3334 T: Into<Arc<str>>,
3335 {
3336 if self.read_only(cx) {
3337 return;
3338 }
3339
3340 self.buffer
3341 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3342 }
3343
3344 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3345 where
3346 I: IntoIterator<Item = (Range<S>, T)>,
3347 S: ToOffset,
3348 T: Into<Arc<str>>,
3349 {
3350 if self.read_only(cx) {
3351 return;
3352 }
3353
3354 self.buffer.update(cx, |buffer, cx| {
3355 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3356 });
3357 }
3358
3359 pub fn edit_with_block_indent<I, S, T>(
3360 &mut self,
3361 edits: I,
3362 original_indent_columns: Vec<Option<u32>>,
3363 cx: &mut Context<Self>,
3364 ) where
3365 I: IntoIterator<Item = (Range<S>, T)>,
3366 S: ToOffset,
3367 T: Into<Arc<str>>,
3368 {
3369 if self.read_only(cx) {
3370 return;
3371 }
3372
3373 self.buffer.update(cx, |buffer, cx| {
3374 buffer.edit(
3375 edits,
3376 Some(AutoindentMode::Block {
3377 original_indent_columns,
3378 }),
3379 cx,
3380 )
3381 });
3382 }
3383
3384 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3385 self.hide_context_menu(window, cx);
3386
3387 match phase {
3388 SelectPhase::Begin {
3389 position,
3390 add,
3391 click_count,
3392 } => self.begin_selection(position, add, click_count, window, cx),
3393 SelectPhase::BeginColumnar {
3394 position,
3395 goal_column,
3396 reset,
3397 mode,
3398 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3399 SelectPhase::Extend {
3400 position,
3401 click_count,
3402 } => self.extend_selection(position, click_count, window, cx),
3403 SelectPhase::Update {
3404 position,
3405 goal_column,
3406 scroll_delta,
3407 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3408 SelectPhase::End => self.end_selection(window, cx),
3409 }
3410 }
3411
3412 fn extend_selection(
3413 &mut self,
3414 position: DisplayPoint,
3415 click_count: usize,
3416 window: &mut Window,
3417 cx: &mut Context<Self>,
3418 ) {
3419 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3420 let tail = self.selections.newest::<usize>(cx).tail();
3421 self.begin_selection(position, false, click_count, window, cx);
3422
3423 let position = position.to_offset(&display_map, Bias::Left);
3424 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3425
3426 let mut pending_selection = self
3427 .selections
3428 .pending_anchor()
3429 .expect("extend_selection not called with pending selection");
3430 if position >= tail {
3431 pending_selection.start = tail_anchor;
3432 } else {
3433 pending_selection.end = tail_anchor;
3434 pending_selection.reversed = true;
3435 }
3436
3437 let mut pending_mode = self.selections.pending_mode().unwrap();
3438 match &mut pending_mode {
3439 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3440 _ => {}
3441 }
3442
3443 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3444 SelectionEffects::scroll(Autoscroll::fit())
3445 } else {
3446 SelectionEffects::no_scroll()
3447 };
3448
3449 self.change_selections(effects, window, cx, |s| {
3450 s.set_pending(pending_selection, pending_mode)
3451 });
3452 }
3453
3454 fn begin_selection(
3455 &mut self,
3456 position: DisplayPoint,
3457 add: bool,
3458 click_count: usize,
3459 window: &mut Window,
3460 cx: &mut Context<Self>,
3461 ) {
3462 if !self.focus_handle.is_focused(window) {
3463 self.last_focused_descendant = None;
3464 window.focus(&self.focus_handle);
3465 }
3466
3467 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3468 let buffer = &display_map.buffer_snapshot;
3469 let position = display_map.clip_point(position, Bias::Left);
3470
3471 let start;
3472 let end;
3473 let mode;
3474 let mut auto_scroll;
3475 match click_count {
3476 1 => {
3477 start = buffer.anchor_before(position.to_point(&display_map));
3478 end = start;
3479 mode = SelectMode::Character;
3480 auto_scroll = true;
3481 }
3482 2 => {
3483 let position = display_map
3484 .clip_point(position, Bias::Left)
3485 .to_offset(&display_map, Bias::Left);
3486 let (range, _) = buffer.surrounding_word(position, false);
3487 start = buffer.anchor_before(range.start);
3488 end = buffer.anchor_before(range.end);
3489 mode = SelectMode::Word(start..end);
3490 auto_scroll = true;
3491 }
3492 3 => {
3493 let position = display_map
3494 .clip_point(position, Bias::Left)
3495 .to_point(&display_map);
3496 let line_start = display_map.prev_line_boundary(position).0;
3497 let next_line_start = buffer.clip_point(
3498 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3499 Bias::Left,
3500 );
3501 start = buffer.anchor_before(line_start);
3502 end = buffer.anchor_before(next_line_start);
3503 mode = SelectMode::Line(start..end);
3504 auto_scroll = true;
3505 }
3506 _ => {
3507 start = buffer.anchor_before(0);
3508 end = buffer.anchor_before(buffer.len());
3509 mode = SelectMode::All;
3510 auto_scroll = false;
3511 }
3512 }
3513 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3514
3515 let point_to_delete: Option<usize> = {
3516 let selected_points: Vec<Selection<Point>> =
3517 self.selections.disjoint_in_range(start..end, cx);
3518
3519 if !add || click_count > 1 {
3520 None
3521 } else if !selected_points.is_empty() {
3522 Some(selected_points[0].id)
3523 } else {
3524 let clicked_point_already_selected =
3525 self.selections.disjoint.iter().find(|selection| {
3526 selection.start.to_point(buffer) == start.to_point(buffer)
3527 || selection.end.to_point(buffer) == end.to_point(buffer)
3528 });
3529
3530 clicked_point_already_selected.map(|selection| selection.id)
3531 }
3532 };
3533
3534 let selections_count = self.selections.count();
3535 let effects = if auto_scroll {
3536 SelectionEffects::default()
3537 } else {
3538 SelectionEffects::no_scroll()
3539 };
3540
3541 self.change_selections(effects, window, cx, |s| {
3542 if let Some(point_to_delete) = point_to_delete {
3543 s.delete(point_to_delete);
3544
3545 if selections_count == 1 {
3546 s.set_pending_anchor_range(start..end, mode);
3547 }
3548 } else {
3549 if !add {
3550 s.clear_disjoint();
3551 }
3552
3553 s.set_pending_anchor_range(start..end, mode);
3554 }
3555 });
3556 }
3557
3558 fn begin_columnar_selection(
3559 &mut self,
3560 position: DisplayPoint,
3561 goal_column: u32,
3562 reset: bool,
3563 mode: ColumnarMode,
3564 window: &mut Window,
3565 cx: &mut Context<Self>,
3566 ) {
3567 if !self.focus_handle.is_focused(window) {
3568 self.last_focused_descendant = None;
3569 window.focus(&self.focus_handle);
3570 }
3571
3572 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3573
3574 if reset {
3575 let pointer_position = display_map
3576 .buffer_snapshot
3577 .anchor_before(position.to_point(&display_map));
3578
3579 self.change_selections(
3580 SelectionEffects::scroll(Autoscroll::newest()),
3581 window,
3582 cx,
3583 |s| {
3584 s.clear_disjoint();
3585 s.set_pending_anchor_range(
3586 pointer_position..pointer_position,
3587 SelectMode::Character,
3588 );
3589 },
3590 );
3591 };
3592
3593 let tail = self.selections.newest::<Point>(cx).tail();
3594 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3595 self.columnar_selection_state = match mode {
3596 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3597 selection_tail: selection_anchor,
3598 display_point: if reset {
3599 if position.column() != goal_column {
3600 Some(DisplayPoint::new(position.row(), goal_column))
3601 } else {
3602 None
3603 }
3604 } else {
3605 None
3606 },
3607 }),
3608 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3609 selection_tail: selection_anchor,
3610 }),
3611 };
3612
3613 if !reset {
3614 self.select_columns(position, goal_column, &display_map, window, cx);
3615 }
3616 }
3617
3618 fn update_selection(
3619 &mut self,
3620 position: DisplayPoint,
3621 goal_column: u32,
3622 scroll_delta: gpui::Point<f32>,
3623 window: &mut Window,
3624 cx: &mut Context<Self>,
3625 ) {
3626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3627
3628 if self.columnar_selection_state.is_some() {
3629 self.select_columns(position, goal_column, &display_map, window, cx);
3630 } else if let Some(mut pending) = self.selections.pending_anchor() {
3631 let buffer = &display_map.buffer_snapshot;
3632 let head;
3633 let tail;
3634 let mode = self.selections.pending_mode().unwrap();
3635 match &mode {
3636 SelectMode::Character => {
3637 head = position.to_point(&display_map);
3638 tail = pending.tail().to_point(buffer);
3639 }
3640 SelectMode::Word(original_range) => {
3641 let offset = display_map
3642 .clip_point(position, Bias::Left)
3643 .to_offset(&display_map, Bias::Left);
3644 let original_range = original_range.to_offset(buffer);
3645
3646 let head_offset = if buffer.is_inside_word(offset, false)
3647 || original_range.contains(&offset)
3648 {
3649 let (word_range, _) = buffer.surrounding_word(offset, false);
3650 if word_range.start < original_range.start {
3651 word_range.start
3652 } else {
3653 word_range.end
3654 }
3655 } else {
3656 offset
3657 };
3658
3659 head = head_offset.to_point(buffer);
3660 if head_offset <= original_range.start {
3661 tail = original_range.end.to_point(buffer);
3662 } else {
3663 tail = original_range.start.to_point(buffer);
3664 }
3665 }
3666 SelectMode::Line(original_range) => {
3667 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3668
3669 let position = display_map
3670 .clip_point(position, Bias::Left)
3671 .to_point(&display_map);
3672 let line_start = display_map.prev_line_boundary(position).0;
3673 let next_line_start = buffer.clip_point(
3674 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3675 Bias::Left,
3676 );
3677
3678 if line_start < original_range.start {
3679 head = line_start
3680 } else {
3681 head = next_line_start
3682 }
3683
3684 if head <= original_range.start {
3685 tail = original_range.end;
3686 } else {
3687 tail = original_range.start;
3688 }
3689 }
3690 SelectMode::All => {
3691 return;
3692 }
3693 };
3694
3695 if head < tail {
3696 pending.start = buffer.anchor_before(head);
3697 pending.end = buffer.anchor_before(tail);
3698 pending.reversed = true;
3699 } else {
3700 pending.start = buffer.anchor_before(tail);
3701 pending.end = buffer.anchor_before(head);
3702 pending.reversed = false;
3703 }
3704
3705 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3706 s.set_pending(pending, mode);
3707 });
3708 } else {
3709 log::error!("update_selection dispatched with no pending selection");
3710 return;
3711 }
3712
3713 self.apply_scroll_delta(scroll_delta, window, cx);
3714 cx.notify();
3715 }
3716
3717 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3718 self.columnar_selection_state.take();
3719 if self.selections.pending_anchor().is_some() {
3720 let selections = self.selections.all::<usize>(cx);
3721 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3722 s.select(selections);
3723 s.clear_pending();
3724 });
3725 }
3726 }
3727
3728 fn select_columns(
3729 &mut self,
3730 head: DisplayPoint,
3731 goal_column: u32,
3732 display_map: &DisplaySnapshot,
3733 window: &mut Window,
3734 cx: &mut Context<Self>,
3735 ) {
3736 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3737 return;
3738 };
3739
3740 let tail = match columnar_state {
3741 ColumnarSelectionState::FromMouse {
3742 selection_tail,
3743 display_point,
3744 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3745 ColumnarSelectionState::FromSelection { selection_tail } => {
3746 selection_tail.to_display_point(display_map)
3747 }
3748 };
3749
3750 let start_row = cmp::min(tail.row(), head.row());
3751 let end_row = cmp::max(tail.row(), head.row());
3752 let start_column = cmp::min(tail.column(), goal_column);
3753 let end_column = cmp::max(tail.column(), goal_column);
3754 let reversed = start_column < tail.column();
3755
3756 let selection_ranges = (start_row.0..=end_row.0)
3757 .map(DisplayRow)
3758 .filter_map(|row| {
3759 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3760 || start_column <= display_map.line_len(row))
3761 && !display_map.is_block_line(row)
3762 {
3763 let start = display_map
3764 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3765 .to_point(display_map);
3766 let end = display_map
3767 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3768 .to_point(display_map);
3769 if reversed {
3770 Some(end..start)
3771 } else {
3772 Some(start..end)
3773 }
3774 } else {
3775 None
3776 }
3777 })
3778 .collect::<Vec<_>>();
3779
3780 let ranges = match columnar_state {
3781 ColumnarSelectionState::FromMouse { .. } => {
3782 let mut non_empty_ranges = selection_ranges
3783 .iter()
3784 .filter(|selection_range| selection_range.start != selection_range.end)
3785 .peekable();
3786 if non_empty_ranges.peek().is_some() {
3787 non_empty_ranges.cloned().collect()
3788 } else {
3789 selection_ranges
3790 }
3791 }
3792 _ => selection_ranges,
3793 };
3794
3795 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3796 s.select_ranges(ranges);
3797 });
3798 cx.notify();
3799 }
3800
3801 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3802 self.selections
3803 .all_adjusted(cx)
3804 .iter()
3805 .any(|selection| !selection.is_empty())
3806 }
3807
3808 pub fn has_pending_nonempty_selection(&self) -> bool {
3809 let pending_nonempty_selection = match self.selections.pending_anchor() {
3810 Some(Selection { start, end, .. }) => start != end,
3811 None => false,
3812 };
3813
3814 pending_nonempty_selection
3815 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3816 }
3817
3818 pub fn has_pending_selection(&self) -> bool {
3819 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3820 }
3821
3822 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3823 self.selection_mark_mode = false;
3824 self.selection_drag_state = SelectionDragState::None;
3825
3826 if self.clear_expanded_diff_hunks(cx) {
3827 cx.notify();
3828 return;
3829 }
3830 if self.dismiss_menus_and_popups(true, window, cx) {
3831 return;
3832 }
3833
3834 if self.mode.is_full()
3835 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3836 {
3837 return;
3838 }
3839
3840 cx.propagate();
3841 }
3842
3843 pub fn dismiss_menus_and_popups(
3844 &mut self,
3845 is_user_requested: bool,
3846 window: &mut Window,
3847 cx: &mut Context<Self>,
3848 ) -> bool {
3849 if self.take_rename(false, window, cx).is_some() {
3850 return true;
3851 }
3852
3853 if hide_hover(self, cx) {
3854 return true;
3855 }
3856
3857 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3858 return true;
3859 }
3860
3861 if self.hide_context_menu(window, cx).is_some() {
3862 return true;
3863 }
3864
3865 if self.mouse_context_menu.take().is_some() {
3866 return true;
3867 }
3868
3869 if is_user_requested && self.discard_edit_prediction(true, cx) {
3870 return true;
3871 }
3872
3873 if self.snippet_stack.pop().is_some() {
3874 return true;
3875 }
3876
3877 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3878 self.dismiss_diagnostics(cx);
3879 return true;
3880 }
3881
3882 false
3883 }
3884
3885 fn linked_editing_ranges_for(
3886 &self,
3887 selection: Range<text::Anchor>,
3888 cx: &App,
3889 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3890 if self.linked_edit_ranges.is_empty() {
3891 return None;
3892 }
3893 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3894 selection.end.buffer_id.and_then(|end_buffer_id| {
3895 if selection.start.buffer_id != Some(end_buffer_id) {
3896 return None;
3897 }
3898 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3899 let snapshot = buffer.read(cx).snapshot();
3900 self.linked_edit_ranges
3901 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3902 .map(|ranges| (ranges, snapshot, buffer))
3903 })?;
3904 use text::ToOffset as TO;
3905 // find offset from the start of current range to current cursor position
3906 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3907
3908 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3909 let start_difference = start_offset - start_byte_offset;
3910 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3911 let end_difference = end_offset - start_byte_offset;
3912 // Current range has associated linked ranges.
3913 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3914 for range in linked_ranges.iter() {
3915 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3916 let end_offset = start_offset + end_difference;
3917 let start_offset = start_offset + start_difference;
3918 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3919 continue;
3920 }
3921 if self.selections.disjoint_anchor_ranges().any(|s| {
3922 if s.start.buffer_id != selection.start.buffer_id
3923 || s.end.buffer_id != selection.end.buffer_id
3924 {
3925 return false;
3926 }
3927 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3928 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3929 }) {
3930 continue;
3931 }
3932 let start = buffer_snapshot.anchor_after(start_offset);
3933 let end = buffer_snapshot.anchor_after(end_offset);
3934 linked_edits
3935 .entry(buffer.clone())
3936 .or_default()
3937 .push(start..end);
3938 }
3939 Some(linked_edits)
3940 }
3941
3942 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3943 let text: Arc<str> = text.into();
3944
3945 if self.read_only(cx) {
3946 return;
3947 }
3948
3949 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3950
3951 let selections = self.selections.all_adjusted(cx);
3952 let mut bracket_inserted = false;
3953 let mut edits = Vec::new();
3954 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3955 let mut new_selections = Vec::with_capacity(selections.len());
3956 let mut new_autoclose_regions = Vec::new();
3957 let snapshot = self.buffer.read(cx).read(cx);
3958 let mut clear_linked_edit_ranges = false;
3959
3960 for (selection, autoclose_region) in
3961 self.selections_with_autoclose_regions(selections, &snapshot)
3962 {
3963 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3964 // Determine if the inserted text matches the opening or closing
3965 // bracket of any of this language's bracket pairs.
3966 let mut bracket_pair = None;
3967 let mut is_bracket_pair_start = false;
3968 let mut is_bracket_pair_end = false;
3969 if !text.is_empty() {
3970 let mut bracket_pair_matching_end = None;
3971 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3972 // and they are removing the character that triggered IME popup.
3973 for (pair, enabled) in scope.brackets() {
3974 if !pair.close && !pair.surround {
3975 continue;
3976 }
3977
3978 if enabled && pair.start.ends_with(text.as_ref()) {
3979 let prefix_len = pair.start.len() - text.len();
3980 let preceding_text_matches_prefix = prefix_len == 0
3981 || (selection.start.column >= (prefix_len as u32)
3982 && snapshot.contains_str_at(
3983 Point::new(
3984 selection.start.row,
3985 selection.start.column - (prefix_len as u32),
3986 ),
3987 &pair.start[..prefix_len],
3988 ));
3989 if preceding_text_matches_prefix {
3990 bracket_pair = Some(pair.clone());
3991 is_bracket_pair_start = true;
3992 break;
3993 }
3994 }
3995 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3996 {
3997 // take first bracket pair matching end, but don't break in case a later bracket
3998 // pair matches start
3999 bracket_pair_matching_end = Some(pair.clone());
4000 }
4001 }
4002 if let Some(end) = bracket_pair_matching_end
4003 && bracket_pair.is_none()
4004 {
4005 bracket_pair = Some(end);
4006 is_bracket_pair_end = true;
4007 }
4008 }
4009
4010 if let Some(bracket_pair) = bracket_pair {
4011 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4012 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4013 let auto_surround =
4014 self.use_auto_surround && snapshot_settings.use_auto_surround;
4015 if selection.is_empty() {
4016 if is_bracket_pair_start {
4017 // If the inserted text is a suffix of an opening bracket and the
4018 // selection is preceded by the rest of the opening bracket, then
4019 // insert the closing bracket.
4020 let following_text_allows_autoclose = snapshot
4021 .chars_at(selection.start)
4022 .next()
4023 .is_none_or(|c| scope.should_autoclose_before(c));
4024
4025 let preceding_text_allows_autoclose = selection.start.column == 0
4026 || snapshot
4027 .reversed_chars_at(selection.start)
4028 .next()
4029 .is_none_or(|c| {
4030 bracket_pair.start != bracket_pair.end
4031 || !snapshot
4032 .char_classifier_at(selection.start)
4033 .is_word(c)
4034 });
4035
4036 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4037 && bracket_pair.start.len() == 1
4038 {
4039 let target = bracket_pair.start.chars().next().unwrap();
4040 let current_line_count = snapshot
4041 .reversed_chars_at(selection.start)
4042 .take_while(|&c| c != '\n')
4043 .filter(|&c| c == target)
4044 .count();
4045 current_line_count % 2 == 1
4046 } else {
4047 false
4048 };
4049
4050 if autoclose
4051 && bracket_pair.close
4052 && following_text_allows_autoclose
4053 && preceding_text_allows_autoclose
4054 && !is_closing_quote
4055 {
4056 let anchor = snapshot.anchor_before(selection.end);
4057 new_selections.push((selection.map(|_| anchor), text.len()));
4058 new_autoclose_regions.push((
4059 anchor,
4060 text.len(),
4061 selection.id,
4062 bracket_pair.clone(),
4063 ));
4064 edits.push((
4065 selection.range(),
4066 format!("{}{}", text, bracket_pair.end).into(),
4067 ));
4068 bracket_inserted = true;
4069 continue;
4070 }
4071 }
4072
4073 if let Some(region) = autoclose_region {
4074 // If the selection is followed by an auto-inserted closing bracket,
4075 // then don't insert that closing bracket again; just move the selection
4076 // past the closing bracket.
4077 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4078 && text.as_ref() == region.pair.end.as_str()
4079 && snapshot.contains_str_at(region.range.end, text.as_ref());
4080 if should_skip {
4081 let anchor = snapshot.anchor_after(selection.end);
4082 new_selections
4083 .push((selection.map(|_| anchor), region.pair.end.len()));
4084 continue;
4085 }
4086 }
4087
4088 let always_treat_brackets_as_autoclosed = snapshot
4089 .language_settings_at(selection.start, cx)
4090 .always_treat_brackets_as_autoclosed;
4091 if always_treat_brackets_as_autoclosed
4092 && is_bracket_pair_end
4093 && snapshot.contains_str_at(selection.end, text.as_ref())
4094 {
4095 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4096 // and the inserted text is a closing bracket and the selection is followed
4097 // by the closing bracket then move the selection past the closing bracket.
4098 let anchor = snapshot.anchor_after(selection.end);
4099 new_selections.push((selection.map(|_| anchor), text.len()));
4100 continue;
4101 }
4102 }
4103 // If an opening bracket is 1 character long and is typed while
4104 // text is selected, then surround that text with the bracket pair.
4105 else if auto_surround
4106 && bracket_pair.surround
4107 && is_bracket_pair_start
4108 && bracket_pair.start.chars().count() == 1
4109 {
4110 edits.push((selection.start..selection.start, text.clone()));
4111 edits.push((
4112 selection.end..selection.end,
4113 bracket_pair.end.as_str().into(),
4114 ));
4115 bracket_inserted = true;
4116 new_selections.push((
4117 Selection {
4118 id: selection.id,
4119 start: snapshot.anchor_after(selection.start),
4120 end: snapshot.anchor_before(selection.end),
4121 reversed: selection.reversed,
4122 goal: selection.goal,
4123 },
4124 0,
4125 ));
4126 continue;
4127 }
4128 }
4129 }
4130
4131 if self.auto_replace_emoji_shortcode
4132 && selection.is_empty()
4133 && text.as_ref().ends_with(':')
4134 && let Some(possible_emoji_short_code) =
4135 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4136 && !possible_emoji_short_code.is_empty()
4137 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4138 {
4139 let emoji_shortcode_start = Point::new(
4140 selection.start.row,
4141 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4142 );
4143
4144 // Remove shortcode from buffer
4145 edits.push((
4146 emoji_shortcode_start..selection.start,
4147 "".to_string().into(),
4148 ));
4149 new_selections.push((
4150 Selection {
4151 id: selection.id,
4152 start: snapshot.anchor_after(emoji_shortcode_start),
4153 end: snapshot.anchor_before(selection.start),
4154 reversed: selection.reversed,
4155 goal: selection.goal,
4156 },
4157 0,
4158 ));
4159
4160 // Insert emoji
4161 let selection_start_anchor = snapshot.anchor_after(selection.start);
4162 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4163 edits.push((selection.start..selection.end, emoji.to_string().into()));
4164
4165 continue;
4166 }
4167
4168 // If not handling any auto-close operation, then just replace the selected
4169 // text with the given input and move the selection to the end of the
4170 // newly inserted text.
4171 let anchor = snapshot.anchor_after(selection.end);
4172 if !self.linked_edit_ranges.is_empty() {
4173 let start_anchor = snapshot.anchor_before(selection.start);
4174
4175 let is_word_char = text.chars().next().is_none_or(|char| {
4176 let classifier = snapshot
4177 .char_classifier_at(start_anchor.to_offset(&snapshot))
4178 .ignore_punctuation(true);
4179 classifier.is_word(char)
4180 });
4181
4182 if is_word_char {
4183 if let Some(ranges) = self
4184 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4185 {
4186 for (buffer, edits) in ranges {
4187 linked_edits
4188 .entry(buffer.clone())
4189 .or_default()
4190 .extend(edits.into_iter().map(|range| (range, text.clone())));
4191 }
4192 }
4193 } else {
4194 clear_linked_edit_ranges = true;
4195 }
4196 }
4197
4198 new_selections.push((selection.map(|_| anchor), 0));
4199 edits.push((selection.start..selection.end, text.clone()));
4200 }
4201
4202 drop(snapshot);
4203
4204 self.transact(window, cx, |this, window, cx| {
4205 if clear_linked_edit_ranges {
4206 this.linked_edit_ranges.clear();
4207 }
4208 let initial_buffer_versions =
4209 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4210
4211 this.buffer.update(cx, |buffer, cx| {
4212 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4213 });
4214 for (buffer, edits) in linked_edits {
4215 buffer.update(cx, |buffer, cx| {
4216 let snapshot = buffer.snapshot();
4217 let edits = edits
4218 .into_iter()
4219 .map(|(range, text)| {
4220 use text::ToPoint as TP;
4221 let end_point = TP::to_point(&range.end, &snapshot);
4222 let start_point = TP::to_point(&range.start, &snapshot);
4223 (start_point..end_point, text)
4224 })
4225 .sorted_by_key(|(range, _)| range.start);
4226 buffer.edit(edits, None, cx);
4227 })
4228 }
4229 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4230 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4231 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4232 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4233 .zip(new_selection_deltas)
4234 .map(|(selection, delta)| Selection {
4235 id: selection.id,
4236 start: selection.start + delta,
4237 end: selection.end + delta,
4238 reversed: selection.reversed,
4239 goal: SelectionGoal::None,
4240 })
4241 .collect::<Vec<_>>();
4242
4243 let mut i = 0;
4244 for (position, delta, selection_id, pair) in new_autoclose_regions {
4245 let position = position.to_offset(&map.buffer_snapshot) + delta;
4246 let start = map.buffer_snapshot.anchor_before(position);
4247 let end = map.buffer_snapshot.anchor_after(position);
4248 while let Some(existing_state) = this.autoclose_regions.get(i) {
4249 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4250 Ordering::Less => i += 1,
4251 Ordering::Greater => break,
4252 Ordering::Equal => {
4253 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4254 Ordering::Less => i += 1,
4255 Ordering::Equal => break,
4256 Ordering::Greater => break,
4257 }
4258 }
4259 }
4260 }
4261 this.autoclose_regions.insert(
4262 i,
4263 AutocloseRegion {
4264 selection_id,
4265 range: start..end,
4266 pair,
4267 },
4268 );
4269 }
4270
4271 let had_active_edit_prediction = this.has_active_edit_prediction();
4272 this.change_selections(
4273 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4274 window,
4275 cx,
4276 |s| s.select(new_selections),
4277 );
4278
4279 if !bracket_inserted
4280 && let Some(on_type_format_task) =
4281 this.trigger_on_type_formatting(text.to_string(), window, cx)
4282 {
4283 on_type_format_task.detach_and_log_err(cx);
4284 }
4285
4286 let editor_settings = EditorSettings::get_global(cx);
4287 if bracket_inserted
4288 && (editor_settings.auto_signature_help
4289 || editor_settings.show_signature_help_after_edits)
4290 {
4291 this.show_signature_help(&ShowSignatureHelp, window, cx);
4292 }
4293
4294 let trigger_in_words =
4295 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4296 if this.hard_wrap.is_some() {
4297 let latest: Range<Point> = this.selections.newest(cx).range();
4298 if latest.is_empty()
4299 && this
4300 .buffer()
4301 .read(cx)
4302 .snapshot(cx)
4303 .line_len(MultiBufferRow(latest.start.row))
4304 == latest.start.column
4305 {
4306 this.rewrap_impl(
4307 RewrapOptions {
4308 override_language_settings: true,
4309 preserve_existing_whitespace: true,
4310 },
4311 cx,
4312 )
4313 }
4314 }
4315 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4316 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4317 this.refresh_edit_prediction(true, false, window, cx);
4318 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4319 });
4320 }
4321
4322 fn find_possible_emoji_shortcode_at_position(
4323 snapshot: &MultiBufferSnapshot,
4324 position: Point,
4325 ) -> Option<String> {
4326 let mut chars = Vec::new();
4327 let mut found_colon = false;
4328 for char in snapshot.reversed_chars_at(position).take(100) {
4329 // Found a possible emoji shortcode in the middle of the buffer
4330 if found_colon {
4331 if char.is_whitespace() {
4332 chars.reverse();
4333 return Some(chars.iter().collect());
4334 }
4335 // If the previous character is not a whitespace, we are in the middle of a word
4336 // and we only want to complete the shortcode if the word is made up of other emojis
4337 let mut containing_word = String::new();
4338 for ch in snapshot
4339 .reversed_chars_at(position)
4340 .skip(chars.len() + 1)
4341 .take(100)
4342 {
4343 if ch.is_whitespace() {
4344 break;
4345 }
4346 containing_word.push(ch);
4347 }
4348 let containing_word = containing_word.chars().rev().collect::<String>();
4349 if util::word_consists_of_emojis(containing_word.as_str()) {
4350 chars.reverse();
4351 return Some(chars.iter().collect());
4352 }
4353 }
4354
4355 if char.is_whitespace() || !char.is_ascii() {
4356 return None;
4357 }
4358 if char == ':' {
4359 found_colon = true;
4360 } else {
4361 chars.push(char);
4362 }
4363 }
4364 // Found a possible emoji shortcode at the beginning of the buffer
4365 chars.reverse();
4366 Some(chars.iter().collect())
4367 }
4368
4369 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4370 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4371 self.transact(window, cx, |this, window, cx| {
4372 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4373 let selections = this.selections.all::<usize>(cx);
4374 let multi_buffer = this.buffer.read(cx);
4375 let buffer = multi_buffer.snapshot(cx);
4376 selections
4377 .iter()
4378 .map(|selection| {
4379 let start_point = selection.start.to_point(&buffer);
4380 let mut existing_indent =
4381 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4382 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4383 let start = selection.start;
4384 let end = selection.end;
4385 let selection_is_empty = start == end;
4386 let language_scope = buffer.language_scope_at(start);
4387 let (
4388 comment_delimiter,
4389 doc_delimiter,
4390 insert_extra_newline,
4391 indent_on_newline,
4392 indent_on_extra_newline,
4393 ) = if let Some(language) = &language_scope {
4394 let mut insert_extra_newline =
4395 insert_extra_newline_brackets(&buffer, start..end, language)
4396 || insert_extra_newline_tree_sitter(&buffer, start..end);
4397
4398 // Comment extension on newline is allowed only for cursor selections
4399 let comment_delimiter = maybe!({
4400 if !selection_is_empty {
4401 return None;
4402 }
4403
4404 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4405 return None;
4406 }
4407
4408 let delimiters = language.line_comment_prefixes();
4409 let max_len_of_delimiter =
4410 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4411 let (snapshot, range) =
4412 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4413
4414 let num_of_whitespaces = snapshot
4415 .chars_for_range(range.clone())
4416 .take_while(|c| c.is_whitespace())
4417 .count();
4418 let comment_candidate = snapshot
4419 .chars_for_range(range.clone())
4420 .skip(num_of_whitespaces)
4421 .take(max_len_of_delimiter)
4422 .collect::<String>();
4423 let (delimiter, trimmed_len) = delimiters
4424 .iter()
4425 .filter_map(|delimiter| {
4426 let prefix = delimiter.trim_end();
4427 if comment_candidate.starts_with(prefix) {
4428 Some((delimiter, prefix.len()))
4429 } else {
4430 None
4431 }
4432 })
4433 .max_by_key(|(_, len)| *len)?;
4434
4435 if let Some(BlockCommentConfig {
4436 start: block_start, ..
4437 }) = language.block_comment()
4438 {
4439 let block_start_trimmed = block_start.trim_end();
4440 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4441 let line_content = snapshot
4442 .chars_for_range(range)
4443 .skip(num_of_whitespaces)
4444 .take(block_start_trimmed.len())
4445 .collect::<String>();
4446
4447 if line_content.starts_with(block_start_trimmed) {
4448 return None;
4449 }
4450 }
4451 }
4452
4453 let cursor_is_placed_after_comment_marker =
4454 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4455 if cursor_is_placed_after_comment_marker {
4456 Some(delimiter.clone())
4457 } else {
4458 None
4459 }
4460 });
4461
4462 let mut indent_on_newline = IndentSize::spaces(0);
4463 let mut indent_on_extra_newline = IndentSize::spaces(0);
4464
4465 let doc_delimiter = maybe!({
4466 if !selection_is_empty {
4467 return None;
4468 }
4469
4470 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4471 return None;
4472 }
4473
4474 let BlockCommentConfig {
4475 start: start_tag,
4476 end: end_tag,
4477 prefix: delimiter,
4478 tab_size: len,
4479 } = language.documentation_comment()?;
4480 let is_within_block_comment = buffer
4481 .language_scope_at(start_point)
4482 .is_some_and(|scope| scope.override_name() == Some("comment"));
4483 if !is_within_block_comment {
4484 return None;
4485 }
4486
4487 let (snapshot, range) =
4488 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4489
4490 let num_of_whitespaces = snapshot
4491 .chars_for_range(range.clone())
4492 .take_while(|c| c.is_whitespace())
4493 .count();
4494
4495 // 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.
4496 let column = start_point.column;
4497 let cursor_is_after_start_tag = {
4498 let start_tag_len = start_tag.len();
4499 let start_tag_line = snapshot
4500 .chars_for_range(range.clone())
4501 .skip(num_of_whitespaces)
4502 .take(start_tag_len)
4503 .collect::<String>();
4504 if start_tag_line.starts_with(start_tag.as_ref()) {
4505 num_of_whitespaces + start_tag_len <= column as usize
4506 } else {
4507 false
4508 }
4509 };
4510
4511 let cursor_is_after_delimiter = {
4512 let delimiter_trim = delimiter.trim_end();
4513 let delimiter_line = snapshot
4514 .chars_for_range(range.clone())
4515 .skip(num_of_whitespaces)
4516 .take(delimiter_trim.len())
4517 .collect::<String>();
4518 if delimiter_line.starts_with(delimiter_trim) {
4519 num_of_whitespaces + delimiter_trim.len() <= column as usize
4520 } else {
4521 false
4522 }
4523 };
4524
4525 let cursor_is_before_end_tag_if_exists = {
4526 let mut char_position = 0u32;
4527 let mut end_tag_offset = None;
4528
4529 'outer: for chunk in snapshot.text_for_range(range) {
4530 if let Some(byte_pos) = chunk.find(&**end_tag) {
4531 let chars_before_match =
4532 chunk[..byte_pos].chars().count() as u32;
4533 end_tag_offset =
4534 Some(char_position + chars_before_match);
4535 break 'outer;
4536 }
4537 char_position += chunk.chars().count() as u32;
4538 }
4539
4540 if let Some(end_tag_offset) = end_tag_offset {
4541 let cursor_is_before_end_tag = column <= end_tag_offset;
4542 if cursor_is_after_start_tag {
4543 if cursor_is_before_end_tag {
4544 insert_extra_newline = true;
4545 }
4546 let cursor_is_at_start_of_end_tag =
4547 column == end_tag_offset;
4548 if cursor_is_at_start_of_end_tag {
4549 indent_on_extra_newline.len = *len;
4550 }
4551 }
4552 cursor_is_before_end_tag
4553 } else {
4554 true
4555 }
4556 };
4557
4558 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4559 && cursor_is_before_end_tag_if_exists
4560 {
4561 if cursor_is_after_start_tag {
4562 indent_on_newline.len = *len;
4563 }
4564 Some(delimiter.clone())
4565 } else {
4566 None
4567 }
4568 });
4569
4570 (
4571 comment_delimiter,
4572 doc_delimiter,
4573 insert_extra_newline,
4574 indent_on_newline,
4575 indent_on_extra_newline,
4576 )
4577 } else {
4578 (
4579 None,
4580 None,
4581 false,
4582 IndentSize::default(),
4583 IndentSize::default(),
4584 )
4585 };
4586
4587 let prevent_auto_indent = doc_delimiter.is_some();
4588 let delimiter = comment_delimiter.or(doc_delimiter);
4589
4590 let capacity_for_delimiter =
4591 delimiter.as_deref().map(str::len).unwrap_or_default();
4592 let mut new_text = String::with_capacity(
4593 1 + capacity_for_delimiter
4594 + existing_indent.len as usize
4595 + indent_on_newline.len as usize
4596 + indent_on_extra_newline.len as usize,
4597 );
4598 new_text.push('\n');
4599 new_text.extend(existing_indent.chars());
4600 new_text.extend(indent_on_newline.chars());
4601
4602 if let Some(delimiter) = &delimiter {
4603 new_text.push_str(delimiter);
4604 }
4605
4606 if insert_extra_newline {
4607 new_text.push('\n');
4608 new_text.extend(existing_indent.chars());
4609 new_text.extend(indent_on_extra_newline.chars());
4610 }
4611
4612 let anchor = buffer.anchor_after(end);
4613 let new_selection = selection.map(|_| anchor);
4614 (
4615 ((start..end, new_text), prevent_auto_indent),
4616 (insert_extra_newline, new_selection),
4617 )
4618 })
4619 .unzip()
4620 };
4621
4622 let mut auto_indent_edits = Vec::new();
4623 let mut edits = Vec::new();
4624 for (edit, prevent_auto_indent) in edits_with_flags {
4625 if prevent_auto_indent {
4626 edits.push(edit);
4627 } else {
4628 auto_indent_edits.push(edit);
4629 }
4630 }
4631 if !edits.is_empty() {
4632 this.edit(edits, cx);
4633 }
4634 if !auto_indent_edits.is_empty() {
4635 this.edit_with_autoindent(auto_indent_edits, cx);
4636 }
4637
4638 let buffer = this.buffer.read(cx).snapshot(cx);
4639 let new_selections = selection_info
4640 .into_iter()
4641 .map(|(extra_newline_inserted, new_selection)| {
4642 let mut cursor = new_selection.end.to_point(&buffer);
4643 if extra_newline_inserted {
4644 cursor.row -= 1;
4645 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4646 }
4647 new_selection.map(|_| cursor)
4648 })
4649 .collect();
4650
4651 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4652 this.refresh_edit_prediction(true, false, window, cx);
4653 });
4654 }
4655
4656 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4657 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4658
4659 let buffer = self.buffer.read(cx);
4660 let snapshot = buffer.snapshot(cx);
4661
4662 let mut edits = Vec::new();
4663 let mut rows = Vec::new();
4664
4665 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4666 let cursor = selection.head();
4667 let row = cursor.row;
4668
4669 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4670
4671 let newline = "\n".to_string();
4672 edits.push((start_of_line..start_of_line, newline));
4673
4674 rows.push(row + rows_inserted as u32);
4675 }
4676
4677 self.transact(window, cx, |editor, window, cx| {
4678 editor.edit(edits, cx);
4679
4680 editor.change_selections(Default::default(), window, cx, |s| {
4681 let mut index = 0;
4682 s.move_cursors_with(|map, _, _| {
4683 let row = rows[index];
4684 index += 1;
4685
4686 let point = Point::new(row, 0);
4687 let boundary = map.next_line_boundary(point).1;
4688 let clipped = map.clip_point(boundary, Bias::Left);
4689
4690 (clipped, SelectionGoal::None)
4691 });
4692 });
4693
4694 let mut indent_edits = Vec::new();
4695 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4696 for row in rows {
4697 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4698 for (row, indent) in indents {
4699 if indent.len == 0 {
4700 continue;
4701 }
4702
4703 let text = match indent.kind {
4704 IndentKind::Space => " ".repeat(indent.len as usize),
4705 IndentKind::Tab => "\t".repeat(indent.len as usize),
4706 };
4707 let point = Point::new(row.0, 0);
4708 indent_edits.push((point..point, text));
4709 }
4710 }
4711 editor.edit(indent_edits, cx);
4712 });
4713 }
4714
4715 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4716 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4717
4718 let buffer = self.buffer.read(cx);
4719 let snapshot = buffer.snapshot(cx);
4720
4721 let mut edits = Vec::new();
4722 let mut rows = Vec::new();
4723 let mut rows_inserted = 0;
4724
4725 for selection in self.selections.all_adjusted(cx) {
4726 let cursor = selection.head();
4727 let row = cursor.row;
4728
4729 let point = Point::new(row + 1, 0);
4730 let start_of_line = snapshot.clip_point(point, Bias::Left);
4731
4732 let newline = "\n".to_string();
4733 edits.push((start_of_line..start_of_line, newline));
4734
4735 rows_inserted += 1;
4736 rows.push(row + rows_inserted);
4737 }
4738
4739 self.transact(window, cx, |editor, window, cx| {
4740 editor.edit(edits, cx);
4741
4742 editor.change_selections(Default::default(), window, cx, |s| {
4743 let mut index = 0;
4744 s.move_cursors_with(|map, _, _| {
4745 let row = rows[index];
4746 index += 1;
4747
4748 let point = Point::new(row, 0);
4749 let boundary = map.next_line_boundary(point).1;
4750 let clipped = map.clip_point(boundary, Bias::Left);
4751
4752 (clipped, SelectionGoal::None)
4753 });
4754 });
4755
4756 let mut indent_edits = Vec::new();
4757 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4758 for row in rows {
4759 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4760 for (row, indent) in indents {
4761 if indent.len == 0 {
4762 continue;
4763 }
4764
4765 let text = match indent.kind {
4766 IndentKind::Space => " ".repeat(indent.len as usize),
4767 IndentKind::Tab => "\t".repeat(indent.len as usize),
4768 };
4769 let point = Point::new(row.0, 0);
4770 indent_edits.push((point..point, text));
4771 }
4772 }
4773 editor.edit(indent_edits, cx);
4774 });
4775 }
4776
4777 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4778 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4779 original_indent_columns: Vec::new(),
4780 });
4781 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4782 }
4783
4784 fn insert_with_autoindent_mode(
4785 &mut self,
4786 text: &str,
4787 autoindent_mode: Option<AutoindentMode>,
4788 window: &mut Window,
4789 cx: &mut Context<Self>,
4790 ) {
4791 if self.read_only(cx) {
4792 return;
4793 }
4794
4795 let text: Arc<str> = text.into();
4796 self.transact(window, cx, |this, window, cx| {
4797 let old_selections = this.selections.all_adjusted(cx);
4798 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4799 let anchors = {
4800 let snapshot = buffer.read(cx);
4801 old_selections
4802 .iter()
4803 .map(|s| {
4804 let anchor = snapshot.anchor_after(s.head());
4805 s.map(|_| anchor)
4806 })
4807 .collect::<Vec<_>>()
4808 };
4809 buffer.edit(
4810 old_selections
4811 .iter()
4812 .map(|s| (s.start..s.end, text.clone())),
4813 autoindent_mode,
4814 cx,
4815 );
4816 anchors
4817 });
4818
4819 this.change_selections(Default::default(), window, cx, |s| {
4820 s.select_anchors(selection_anchors);
4821 });
4822
4823 cx.notify();
4824 });
4825 }
4826
4827 fn trigger_completion_on_input(
4828 &mut self,
4829 text: &str,
4830 trigger_in_words: bool,
4831 window: &mut Window,
4832 cx: &mut Context<Self>,
4833 ) {
4834 let completions_source = self
4835 .context_menu
4836 .borrow()
4837 .as_ref()
4838 .and_then(|menu| match menu {
4839 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4840 CodeContextMenu::CodeActions(_) => None,
4841 });
4842
4843 match completions_source {
4844 Some(CompletionsMenuSource::Words) => {
4845 self.show_word_completions(&ShowWordCompletions, window, cx)
4846 }
4847 Some(CompletionsMenuSource::Normal)
4848 | Some(CompletionsMenuSource::SnippetChoices)
4849 | None
4850 if self.is_completion_trigger(
4851 text,
4852 trigger_in_words,
4853 completions_source.is_some(),
4854 cx,
4855 ) =>
4856 {
4857 self.show_completions(
4858 &ShowCompletions {
4859 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4860 },
4861 window,
4862 cx,
4863 )
4864 }
4865 _ => {
4866 self.hide_context_menu(window, cx);
4867 }
4868 }
4869 }
4870
4871 fn is_completion_trigger(
4872 &self,
4873 text: &str,
4874 trigger_in_words: bool,
4875 menu_is_open: bool,
4876 cx: &mut Context<Self>,
4877 ) -> bool {
4878 let position = self.selections.newest_anchor().head();
4879 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4880 return false;
4881 };
4882
4883 if let Some(completion_provider) = &self.completion_provider {
4884 completion_provider.is_completion_trigger(
4885 &buffer,
4886 position.text_anchor,
4887 text,
4888 trigger_in_words,
4889 menu_is_open,
4890 cx,
4891 )
4892 } else {
4893 false
4894 }
4895 }
4896
4897 /// If any empty selections is touching the start of its innermost containing autoclose
4898 /// region, expand it to select the brackets.
4899 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4900 let selections = self.selections.all::<usize>(cx);
4901 let buffer = self.buffer.read(cx).read(cx);
4902 let new_selections = self
4903 .selections_with_autoclose_regions(selections, &buffer)
4904 .map(|(mut selection, region)| {
4905 if !selection.is_empty() {
4906 return selection;
4907 }
4908
4909 if let Some(region) = region {
4910 let mut range = region.range.to_offset(&buffer);
4911 if selection.start == range.start && range.start >= region.pair.start.len() {
4912 range.start -= region.pair.start.len();
4913 if buffer.contains_str_at(range.start, ®ion.pair.start)
4914 && buffer.contains_str_at(range.end, ®ion.pair.end)
4915 {
4916 range.end += region.pair.end.len();
4917 selection.start = range.start;
4918 selection.end = range.end;
4919
4920 return selection;
4921 }
4922 }
4923 }
4924
4925 let always_treat_brackets_as_autoclosed = buffer
4926 .language_settings_at(selection.start, cx)
4927 .always_treat_brackets_as_autoclosed;
4928
4929 if !always_treat_brackets_as_autoclosed {
4930 return selection;
4931 }
4932
4933 if let Some(scope) = buffer.language_scope_at(selection.start) {
4934 for (pair, enabled) in scope.brackets() {
4935 if !enabled || !pair.close {
4936 continue;
4937 }
4938
4939 if buffer.contains_str_at(selection.start, &pair.end) {
4940 let pair_start_len = pair.start.len();
4941 if buffer.contains_str_at(
4942 selection.start.saturating_sub(pair_start_len),
4943 &pair.start,
4944 ) {
4945 selection.start -= pair_start_len;
4946 selection.end += pair.end.len();
4947
4948 return selection;
4949 }
4950 }
4951 }
4952 }
4953
4954 selection
4955 })
4956 .collect();
4957
4958 drop(buffer);
4959 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4960 selections.select(new_selections)
4961 });
4962 }
4963
4964 /// Iterate the given selections, and for each one, find the smallest surrounding
4965 /// autoclose region. This uses the ordering of the selections and the autoclose
4966 /// regions to avoid repeated comparisons.
4967 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4968 &'a self,
4969 selections: impl IntoIterator<Item = Selection<D>>,
4970 buffer: &'a MultiBufferSnapshot,
4971 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4972 let mut i = 0;
4973 let mut regions = self.autoclose_regions.as_slice();
4974 selections.into_iter().map(move |selection| {
4975 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4976
4977 let mut enclosing = None;
4978 while let Some(pair_state) = regions.get(i) {
4979 if pair_state.range.end.to_offset(buffer) < range.start {
4980 regions = ®ions[i + 1..];
4981 i = 0;
4982 } else if pair_state.range.start.to_offset(buffer) > range.end {
4983 break;
4984 } else {
4985 if pair_state.selection_id == selection.id {
4986 enclosing = Some(pair_state);
4987 }
4988 i += 1;
4989 }
4990 }
4991
4992 (selection, enclosing)
4993 })
4994 }
4995
4996 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
4997 fn invalidate_autoclose_regions(
4998 &mut self,
4999 mut selections: &[Selection<Anchor>],
5000 buffer: &MultiBufferSnapshot,
5001 ) {
5002 self.autoclose_regions.retain(|state| {
5003 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5004 return false;
5005 }
5006
5007 let mut i = 0;
5008 while let Some(selection) = selections.get(i) {
5009 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5010 selections = &selections[1..];
5011 continue;
5012 }
5013 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5014 break;
5015 }
5016 if selection.id == state.selection_id {
5017 return true;
5018 } else {
5019 i += 1;
5020 }
5021 }
5022 false
5023 });
5024 }
5025
5026 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5027 let offset = position.to_offset(buffer);
5028 let (word_range, kind) = buffer.surrounding_word(offset, true);
5029 if offset > word_range.start && kind == Some(CharKind::Word) {
5030 Some(
5031 buffer
5032 .text_for_range(word_range.start..offset)
5033 .collect::<String>(),
5034 )
5035 } else {
5036 None
5037 }
5038 }
5039
5040 pub fn toggle_inline_values(
5041 &mut self,
5042 _: &ToggleInlineValues,
5043 _: &mut Window,
5044 cx: &mut Context<Self>,
5045 ) {
5046 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5047
5048 self.refresh_inline_values(cx);
5049 }
5050
5051 pub fn toggle_inlay_hints(
5052 &mut self,
5053 _: &ToggleInlayHints,
5054 _: &mut Window,
5055 cx: &mut Context<Self>,
5056 ) {
5057 self.refresh_inlay_hints(
5058 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5059 cx,
5060 );
5061 }
5062
5063 pub fn inlay_hints_enabled(&self) -> bool {
5064 self.inlay_hint_cache.enabled
5065 }
5066
5067 pub fn inline_values_enabled(&self) -> bool {
5068 self.inline_value_cache.enabled
5069 }
5070
5071 #[cfg(any(test, feature = "test-support"))]
5072 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5073 self.display_map
5074 .read(cx)
5075 .current_inlays()
5076 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5077 .cloned()
5078 .collect()
5079 }
5080
5081 #[cfg(any(test, feature = "test-support"))]
5082 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5083 self.display_map
5084 .read(cx)
5085 .current_inlays()
5086 .cloned()
5087 .collect()
5088 }
5089
5090 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5091 if self.semantics_provider.is_none() || !self.mode.is_full() {
5092 return;
5093 }
5094
5095 let reason_description = reason.description();
5096 let ignore_debounce = matches!(
5097 reason,
5098 InlayHintRefreshReason::SettingsChange(_)
5099 | InlayHintRefreshReason::Toggle(_)
5100 | InlayHintRefreshReason::ExcerptsRemoved(_)
5101 | InlayHintRefreshReason::ModifiersChanged(_)
5102 );
5103 let (invalidate_cache, required_languages) = match reason {
5104 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5105 match self.inlay_hint_cache.modifiers_override(enabled) {
5106 Some(enabled) => {
5107 if enabled {
5108 (InvalidationStrategy::RefreshRequested, None)
5109 } else {
5110 self.splice_inlays(
5111 &self
5112 .visible_inlay_hints(cx)
5113 .iter()
5114 .map(|inlay| inlay.id)
5115 .collect::<Vec<InlayId>>(),
5116 Vec::new(),
5117 cx,
5118 );
5119 return;
5120 }
5121 }
5122 None => return,
5123 }
5124 }
5125 InlayHintRefreshReason::Toggle(enabled) => {
5126 if self.inlay_hint_cache.toggle(enabled) {
5127 if enabled {
5128 (InvalidationStrategy::RefreshRequested, None)
5129 } else {
5130 self.splice_inlays(
5131 &self
5132 .visible_inlay_hints(cx)
5133 .iter()
5134 .map(|inlay| inlay.id)
5135 .collect::<Vec<InlayId>>(),
5136 Vec::new(),
5137 cx,
5138 );
5139 return;
5140 }
5141 } else {
5142 return;
5143 }
5144 }
5145 InlayHintRefreshReason::SettingsChange(new_settings) => {
5146 match self.inlay_hint_cache.update_settings(
5147 &self.buffer,
5148 new_settings,
5149 self.visible_inlay_hints(cx),
5150 cx,
5151 ) {
5152 ControlFlow::Break(Some(InlaySplice {
5153 to_remove,
5154 to_insert,
5155 })) => {
5156 self.splice_inlays(&to_remove, to_insert, cx);
5157 return;
5158 }
5159 ControlFlow::Break(None) => return,
5160 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5161 }
5162 }
5163 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5164 if let Some(InlaySplice {
5165 to_remove,
5166 to_insert,
5167 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5168 {
5169 self.splice_inlays(&to_remove, to_insert, cx);
5170 }
5171 self.display_map.update(cx, |display_map, _| {
5172 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5173 });
5174 return;
5175 }
5176 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5177 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5178 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5179 }
5180 InlayHintRefreshReason::RefreshRequested => {
5181 (InvalidationStrategy::RefreshRequested, None)
5182 }
5183 };
5184
5185 if let Some(InlaySplice {
5186 to_remove,
5187 to_insert,
5188 }) = self.inlay_hint_cache.spawn_hint_refresh(
5189 reason_description,
5190 self.visible_excerpts(required_languages.as_ref(), cx),
5191 invalidate_cache,
5192 ignore_debounce,
5193 cx,
5194 ) {
5195 self.splice_inlays(&to_remove, to_insert, cx);
5196 }
5197 }
5198
5199 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5200 self.display_map
5201 .read(cx)
5202 .current_inlays()
5203 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5204 .cloned()
5205 .collect()
5206 }
5207
5208 pub fn visible_excerpts(
5209 &self,
5210 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5211 cx: &mut Context<Editor>,
5212 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5213 let Some(project) = self.project() else {
5214 return HashMap::default();
5215 };
5216 let project = project.read(cx);
5217 let multi_buffer = self.buffer().read(cx);
5218 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5219 let multi_buffer_visible_start = self
5220 .scroll_manager
5221 .anchor()
5222 .anchor
5223 .to_point(&multi_buffer_snapshot);
5224 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5225 multi_buffer_visible_start
5226 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5227 Bias::Left,
5228 );
5229 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5230 multi_buffer_snapshot
5231 .range_to_buffer_ranges(multi_buffer_visible_range)
5232 .into_iter()
5233 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5234 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5235 let buffer_file = project::File::from_dyn(buffer.file())?;
5236 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5237 let worktree_entry = buffer_worktree
5238 .read(cx)
5239 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5240 if worktree_entry.is_ignored {
5241 return None;
5242 }
5243
5244 let language = buffer.language()?;
5245 if let Some(restrict_to_languages) = restrict_to_languages
5246 && !restrict_to_languages.contains(language)
5247 {
5248 return None;
5249 }
5250 Some((
5251 excerpt_id,
5252 (
5253 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5254 buffer.version().clone(),
5255 excerpt_visible_range,
5256 ),
5257 ))
5258 })
5259 .collect()
5260 }
5261
5262 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5263 TextLayoutDetails {
5264 text_system: window.text_system().clone(),
5265 editor_style: self.style.clone().unwrap(),
5266 rem_size: window.rem_size(),
5267 scroll_anchor: self.scroll_manager.anchor(),
5268 visible_rows: self.visible_line_count(),
5269 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5270 }
5271 }
5272
5273 pub fn splice_inlays(
5274 &self,
5275 to_remove: &[InlayId],
5276 to_insert: Vec<Inlay>,
5277 cx: &mut Context<Self>,
5278 ) {
5279 self.display_map.update(cx, |display_map, cx| {
5280 display_map.splice_inlays(to_remove, to_insert, cx)
5281 });
5282 cx.notify();
5283 }
5284
5285 fn trigger_on_type_formatting(
5286 &self,
5287 input: String,
5288 window: &mut Window,
5289 cx: &mut Context<Self>,
5290 ) -> Option<Task<Result<()>>> {
5291 if input.len() != 1 {
5292 return None;
5293 }
5294
5295 let project = self.project()?;
5296 let position = self.selections.newest_anchor().head();
5297 let (buffer, buffer_position) = self
5298 .buffer
5299 .read(cx)
5300 .text_anchor_for_position(position, cx)?;
5301
5302 let settings = language_settings::language_settings(
5303 buffer
5304 .read(cx)
5305 .language_at(buffer_position)
5306 .map(|l| l.name()),
5307 buffer.read(cx).file(),
5308 cx,
5309 );
5310 if !settings.use_on_type_format {
5311 return None;
5312 }
5313
5314 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5315 // hence we do LSP request & edit on host side only — add formats to host's history.
5316 let push_to_lsp_host_history = true;
5317 // If this is not the host, append its history with new edits.
5318 let push_to_client_history = project.read(cx).is_via_collab();
5319
5320 let on_type_formatting = project.update(cx, |project, cx| {
5321 project.on_type_format(
5322 buffer.clone(),
5323 buffer_position,
5324 input,
5325 push_to_lsp_host_history,
5326 cx,
5327 )
5328 });
5329 Some(cx.spawn_in(window, async move |editor, cx| {
5330 if let Some(transaction) = on_type_formatting.await? {
5331 if push_to_client_history {
5332 buffer
5333 .update(cx, |buffer, _| {
5334 buffer.push_transaction(transaction, Instant::now());
5335 buffer.finalize_last_transaction();
5336 })
5337 .ok();
5338 }
5339 editor.update(cx, |editor, cx| {
5340 editor.refresh_document_highlights(cx);
5341 })?;
5342 }
5343 Ok(())
5344 }))
5345 }
5346
5347 pub fn show_word_completions(
5348 &mut self,
5349 _: &ShowWordCompletions,
5350 window: &mut Window,
5351 cx: &mut Context<Self>,
5352 ) {
5353 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5354 }
5355
5356 pub fn show_completions(
5357 &mut self,
5358 options: &ShowCompletions,
5359 window: &mut Window,
5360 cx: &mut Context<Self>,
5361 ) {
5362 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5363 }
5364
5365 fn open_or_update_completions_menu(
5366 &mut self,
5367 requested_source: Option<CompletionsMenuSource>,
5368 trigger: Option<&str>,
5369 window: &mut Window,
5370 cx: &mut Context<Self>,
5371 ) {
5372 if self.pending_rename.is_some() {
5373 return;
5374 }
5375
5376 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5377
5378 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5379 // inserted and selected. To handle that case, the start of the selection is used so that
5380 // the menu starts with all choices.
5381 let position = self
5382 .selections
5383 .newest_anchor()
5384 .start
5385 .bias_right(&multibuffer_snapshot);
5386 if position.diff_base_anchor.is_some() {
5387 return;
5388 }
5389 let (buffer, buffer_position) =
5390 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5391 output
5392 } else {
5393 return;
5394 };
5395 let buffer_snapshot = buffer.read(cx).snapshot();
5396
5397 let query: Option<Arc<String>> =
5398 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5399
5400 drop(multibuffer_snapshot);
5401
5402 let provider = match requested_source {
5403 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5404 Some(CompletionsMenuSource::Words) => None,
5405 Some(CompletionsMenuSource::SnippetChoices) => {
5406 log::error!("bug: SnippetChoices requested_source is not handled");
5407 None
5408 }
5409 };
5410
5411 let sort_completions = provider
5412 .as_ref()
5413 .is_some_and(|provider| provider.sort_completions());
5414
5415 let filter_completions = provider
5416 .as_ref()
5417 .is_none_or(|provider| provider.filter_completions());
5418
5419 let trigger_kind = match trigger {
5420 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5421 CompletionTriggerKind::TRIGGER_CHARACTER
5422 }
5423 _ => CompletionTriggerKind::INVOKED,
5424 };
5425 let completion_context = CompletionContext {
5426 trigger_character: trigger.and_then(|trigger| {
5427 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5428 Some(String::from(trigger))
5429 } else {
5430 None
5431 }
5432 }),
5433 trigger_kind,
5434 };
5435
5436 // Hide the current completions menu when a trigger char is typed. Without this, cached
5437 // completions from before the trigger char may be reused (#32774). Snippet choices could
5438 // involve trigger chars, so this is skipped in that case.
5439 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5440 {
5441 let menu_is_open = matches!(
5442 self.context_menu.borrow().as_ref(),
5443 Some(CodeContextMenu::Completions(_))
5444 );
5445 if menu_is_open {
5446 self.hide_context_menu(window, cx);
5447 }
5448 }
5449
5450 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5451 if filter_completions {
5452 menu.filter(query.clone(), provider.clone(), window, cx);
5453 }
5454 // When `is_incomplete` is false, no need to re-query completions when the current query
5455 // is a suffix of the initial query.
5456 if !menu.is_incomplete {
5457 // If the new query is a suffix of the old query (typing more characters) and
5458 // the previous result was complete, the existing completions can be filtered.
5459 //
5460 // Note that this is always true for snippet completions.
5461 let query_matches = match (&menu.initial_query, &query) {
5462 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5463 (None, _) => true,
5464 _ => false,
5465 };
5466 if query_matches {
5467 let position_matches = if menu.initial_position == position {
5468 true
5469 } else {
5470 let snapshot = self.buffer.read(cx).read(cx);
5471 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5472 };
5473 if position_matches {
5474 return;
5475 }
5476 }
5477 }
5478 };
5479
5480 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5481 buffer_snapshot.surrounding_word(buffer_position, false)
5482 {
5483 let word_to_exclude = buffer_snapshot
5484 .text_for_range(word_range.clone())
5485 .collect::<String>();
5486 (
5487 buffer_snapshot.anchor_before(word_range.start)
5488 ..buffer_snapshot.anchor_after(buffer_position),
5489 Some(word_to_exclude),
5490 )
5491 } else {
5492 (buffer_position..buffer_position, None)
5493 };
5494
5495 let language = buffer_snapshot
5496 .language_at(buffer_position)
5497 .map(|language| language.name());
5498
5499 let completion_settings =
5500 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5501
5502 let show_completion_documentation = buffer_snapshot
5503 .settings_at(buffer_position, cx)
5504 .show_completion_documentation;
5505
5506 // The document can be large, so stay in reasonable bounds when searching for words,
5507 // otherwise completion pop-up might be slow to appear.
5508 const WORD_LOOKUP_ROWS: u32 = 5_000;
5509 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5510 let min_word_search = buffer_snapshot.clip_point(
5511 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5512 Bias::Left,
5513 );
5514 let max_word_search = buffer_snapshot.clip_point(
5515 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5516 Bias::Right,
5517 );
5518 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5519 ..buffer_snapshot.point_to_offset(max_word_search);
5520
5521 let skip_digits = query
5522 .as_ref()
5523 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5524
5525 let (mut words, provider_responses) = match &provider {
5526 Some(provider) => {
5527 let provider_responses = provider.completions(
5528 position.excerpt_id,
5529 &buffer,
5530 buffer_position,
5531 completion_context,
5532 window,
5533 cx,
5534 );
5535
5536 let words = match completion_settings.words {
5537 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5538 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5539 .background_spawn(async move {
5540 buffer_snapshot.words_in_range(WordsQuery {
5541 fuzzy_contents: None,
5542 range: word_search_range,
5543 skip_digits,
5544 })
5545 }),
5546 };
5547
5548 (words, provider_responses)
5549 }
5550 None => (
5551 cx.background_spawn(async move {
5552 buffer_snapshot.words_in_range(WordsQuery {
5553 fuzzy_contents: None,
5554 range: word_search_range,
5555 skip_digits,
5556 })
5557 }),
5558 Task::ready(Ok(Vec::new())),
5559 ),
5560 };
5561
5562 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5563
5564 let id = post_inc(&mut self.next_completion_id);
5565 let task = cx.spawn_in(window, async move |editor, cx| {
5566 let Ok(()) = editor.update(cx, |this, _| {
5567 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5568 }) else {
5569 return;
5570 };
5571
5572 // TODO: Ideally completions from different sources would be selectively re-queried, so
5573 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5574 let mut completions = Vec::new();
5575 let mut is_incomplete = false;
5576 if let Some(provider_responses) = provider_responses.await.log_err()
5577 && !provider_responses.is_empty()
5578 {
5579 for response in provider_responses {
5580 completions.extend(response.completions);
5581 is_incomplete = is_incomplete || response.is_incomplete;
5582 }
5583 if completion_settings.words == WordsCompletionMode::Fallback {
5584 words = Task::ready(BTreeMap::default());
5585 }
5586 }
5587
5588 let mut words = words.await;
5589 if let Some(word_to_exclude) = &word_to_exclude {
5590 words.remove(word_to_exclude);
5591 }
5592 for lsp_completion in &completions {
5593 words.remove(&lsp_completion.new_text);
5594 }
5595 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5596 replace_range: word_replace_range.clone(),
5597 new_text: word.clone(),
5598 label: CodeLabel::plain(word, None),
5599 icon_path: None,
5600 documentation: None,
5601 source: CompletionSource::BufferWord {
5602 word_range,
5603 resolved: false,
5604 },
5605 insert_text_mode: Some(InsertTextMode::AS_IS),
5606 confirm: None,
5607 }));
5608
5609 let menu = if completions.is_empty() {
5610 None
5611 } else {
5612 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5613 let languages = editor
5614 .workspace
5615 .as_ref()
5616 .and_then(|(workspace, _)| workspace.upgrade())
5617 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5618 let menu = CompletionsMenu::new(
5619 id,
5620 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5621 sort_completions,
5622 show_completion_documentation,
5623 position,
5624 query.clone(),
5625 is_incomplete,
5626 buffer.clone(),
5627 completions.into(),
5628 snippet_sort_order,
5629 languages,
5630 language,
5631 cx,
5632 );
5633
5634 let query = if filter_completions { query } else { None };
5635 let matches_task = if let Some(query) = query {
5636 menu.do_async_filtering(query, cx)
5637 } else {
5638 Task::ready(menu.unfiltered_matches())
5639 };
5640 (menu, matches_task)
5641 }) else {
5642 return;
5643 };
5644
5645 let matches = matches_task.await;
5646
5647 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5648 // Newer menu already set, so exit.
5649 if let Some(CodeContextMenu::Completions(prev_menu)) =
5650 editor.context_menu.borrow().as_ref()
5651 && prev_menu.id > id
5652 {
5653 return;
5654 };
5655
5656 // Only valid to take prev_menu because it the new menu is immediately set
5657 // below, or the menu is hidden.
5658 if let Some(CodeContextMenu::Completions(prev_menu)) =
5659 editor.context_menu.borrow_mut().take()
5660 {
5661 let position_matches =
5662 if prev_menu.initial_position == menu.initial_position {
5663 true
5664 } else {
5665 let snapshot = editor.buffer.read(cx).read(cx);
5666 prev_menu.initial_position.to_offset(&snapshot)
5667 == menu.initial_position.to_offset(&snapshot)
5668 };
5669 if position_matches {
5670 // Preserve markdown cache before `set_filter_results` because it will
5671 // try to populate the documentation cache.
5672 menu.preserve_markdown_cache(prev_menu);
5673 }
5674 };
5675
5676 menu.set_filter_results(matches, provider, window, cx);
5677 }) else {
5678 return;
5679 };
5680
5681 menu.visible().then_some(menu)
5682 };
5683
5684 editor
5685 .update_in(cx, |editor, window, cx| {
5686 if editor.focus_handle.is_focused(window)
5687 && let Some(menu) = menu
5688 {
5689 *editor.context_menu.borrow_mut() =
5690 Some(CodeContextMenu::Completions(menu));
5691
5692 crate::hover_popover::hide_hover(editor, cx);
5693 if editor.show_edit_predictions_in_menu() {
5694 editor.update_visible_edit_prediction(window, cx);
5695 } else {
5696 editor.discard_edit_prediction(false, cx);
5697 }
5698
5699 cx.notify();
5700 return;
5701 }
5702
5703 if editor.completion_tasks.len() <= 1 {
5704 // If there are no more completion tasks and the last menu was empty, we should hide it.
5705 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5706 // If it was already hidden and we don't show edit predictions in the menu,
5707 // we should also show the edit prediction when available.
5708 if was_hidden && editor.show_edit_predictions_in_menu() {
5709 editor.update_visible_edit_prediction(window, cx);
5710 }
5711 }
5712 })
5713 .ok();
5714 });
5715
5716 self.completion_tasks.push((id, task));
5717 }
5718
5719 #[cfg(feature = "test-support")]
5720 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5721 let menu = self.context_menu.borrow();
5722 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5723 let completions = menu.completions.borrow();
5724 Some(completions.to_vec())
5725 } else {
5726 None
5727 }
5728 }
5729
5730 pub fn with_completions_menu_matching_id<R>(
5731 &self,
5732 id: CompletionId,
5733 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5734 ) -> R {
5735 let mut context_menu = self.context_menu.borrow_mut();
5736 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5737 return f(None);
5738 };
5739 if completions_menu.id != id {
5740 return f(None);
5741 }
5742 f(Some(completions_menu))
5743 }
5744
5745 pub fn confirm_completion(
5746 &mut self,
5747 action: &ConfirmCompletion,
5748 window: &mut Window,
5749 cx: &mut Context<Self>,
5750 ) -> Option<Task<Result<()>>> {
5751 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5752 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5753 }
5754
5755 pub fn confirm_completion_insert(
5756 &mut self,
5757 _: &ConfirmCompletionInsert,
5758 window: &mut Window,
5759 cx: &mut Context<Self>,
5760 ) -> Option<Task<Result<()>>> {
5761 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5762 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5763 }
5764
5765 pub fn confirm_completion_replace(
5766 &mut self,
5767 _: &ConfirmCompletionReplace,
5768 window: &mut Window,
5769 cx: &mut Context<Self>,
5770 ) -> Option<Task<Result<()>>> {
5771 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5772 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5773 }
5774
5775 pub fn compose_completion(
5776 &mut self,
5777 action: &ComposeCompletion,
5778 window: &mut Window,
5779 cx: &mut Context<Self>,
5780 ) -> Option<Task<Result<()>>> {
5781 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5782 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5783 }
5784
5785 fn do_completion(
5786 &mut self,
5787 item_ix: Option<usize>,
5788 intent: CompletionIntent,
5789 window: &mut Window,
5790 cx: &mut Context<Editor>,
5791 ) -> Option<Task<Result<()>>> {
5792 use language::ToOffset as _;
5793
5794 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5795 else {
5796 return None;
5797 };
5798
5799 let candidate_id = {
5800 let entries = completions_menu.entries.borrow();
5801 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5802 if self.show_edit_predictions_in_menu() {
5803 self.discard_edit_prediction(true, cx);
5804 }
5805 mat.candidate_id
5806 };
5807
5808 let completion = completions_menu
5809 .completions
5810 .borrow()
5811 .get(candidate_id)?
5812 .clone();
5813 cx.stop_propagation();
5814
5815 let buffer_handle = completions_menu.buffer.clone();
5816
5817 let CompletionEdit {
5818 new_text,
5819 snippet,
5820 replace_range,
5821 } = process_completion_for_edit(
5822 &completion,
5823 intent,
5824 &buffer_handle,
5825 &completions_menu.initial_position.text_anchor,
5826 cx,
5827 );
5828
5829 let buffer = buffer_handle.read(cx);
5830 let snapshot = self.buffer.read(cx).snapshot(cx);
5831 let newest_anchor = self.selections.newest_anchor();
5832 let replace_range_multibuffer = {
5833 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5834 let multibuffer_anchor = snapshot
5835 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5836 .unwrap()
5837 ..snapshot
5838 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5839 .unwrap();
5840 multibuffer_anchor.start.to_offset(&snapshot)
5841 ..multibuffer_anchor.end.to_offset(&snapshot)
5842 };
5843 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5844 return None;
5845 }
5846
5847 let old_text = buffer
5848 .text_for_range(replace_range.clone())
5849 .collect::<String>();
5850 let lookbehind = newest_anchor
5851 .start
5852 .text_anchor
5853 .to_offset(buffer)
5854 .saturating_sub(replace_range.start);
5855 let lookahead = replace_range
5856 .end
5857 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5858 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5859 let suffix = &old_text[lookbehind.min(old_text.len())..];
5860
5861 let selections = self.selections.all::<usize>(cx);
5862 let mut ranges = Vec::new();
5863 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5864
5865 for selection in &selections {
5866 let range = if selection.id == newest_anchor.id {
5867 replace_range_multibuffer.clone()
5868 } else {
5869 let mut range = selection.range();
5870
5871 // if prefix is present, don't duplicate it
5872 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5873 range.start = range.start.saturating_sub(lookbehind);
5874
5875 // if suffix is also present, mimic the newest cursor and replace it
5876 if selection.id != newest_anchor.id
5877 && snapshot.contains_str_at(range.end, suffix)
5878 {
5879 range.end += lookahead;
5880 }
5881 }
5882 range
5883 };
5884
5885 ranges.push(range.clone());
5886
5887 if !self.linked_edit_ranges.is_empty() {
5888 let start_anchor = snapshot.anchor_before(range.start);
5889 let end_anchor = snapshot.anchor_after(range.end);
5890 if let Some(ranges) = self
5891 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5892 {
5893 for (buffer, edits) in ranges {
5894 linked_edits
5895 .entry(buffer.clone())
5896 .or_default()
5897 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5898 }
5899 }
5900 }
5901 }
5902
5903 let common_prefix_len = old_text
5904 .chars()
5905 .zip(new_text.chars())
5906 .take_while(|(a, b)| a == b)
5907 .map(|(a, _)| a.len_utf8())
5908 .sum::<usize>();
5909
5910 cx.emit(EditorEvent::InputHandled {
5911 utf16_range_to_replace: None,
5912 text: new_text[common_prefix_len..].into(),
5913 });
5914
5915 self.transact(window, cx, |editor, window, cx| {
5916 if let Some(mut snippet) = snippet {
5917 snippet.text = new_text.to_string();
5918 editor
5919 .insert_snippet(&ranges, snippet, window, cx)
5920 .log_err();
5921 } else {
5922 editor.buffer.update(cx, |multi_buffer, cx| {
5923 let auto_indent = match completion.insert_text_mode {
5924 Some(InsertTextMode::AS_IS) => None,
5925 _ => editor.autoindent_mode.clone(),
5926 };
5927 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5928 multi_buffer.edit(edits, auto_indent, cx);
5929 });
5930 }
5931 for (buffer, edits) in linked_edits {
5932 buffer.update(cx, |buffer, cx| {
5933 let snapshot = buffer.snapshot();
5934 let edits = edits
5935 .into_iter()
5936 .map(|(range, text)| {
5937 use text::ToPoint as TP;
5938 let end_point = TP::to_point(&range.end, &snapshot);
5939 let start_point = TP::to_point(&range.start, &snapshot);
5940 (start_point..end_point, text)
5941 })
5942 .sorted_by_key(|(range, _)| range.start);
5943 buffer.edit(edits, None, cx);
5944 })
5945 }
5946
5947 editor.refresh_edit_prediction(true, false, window, cx);
5948 });
5949 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5950
5951 let show_new_completions_on_confirm = completion
5952 .confirm
5953 .as_ref()
5954 .is_some_and(|confirm| confirm(intent, window, cx));
5955 if show_new_completions_on_confirm {
5956 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5957 }
5958
5959 let provider = self.completion_provider.as_ref()?;
5960 drop(completion);
5961 let apply_edits = provider.apply_additional_edits_for_completion(
5962 buffer_handle,
5963 completions_menu.completions.clone(),
5964 candidate_id,
5965 true,
5966 cx,
5967 );
5968
5969 let editor_settings = EditorSettings::get_global(cx);
5970 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5971 // After the code completion is finished, users often want to know what signatures are needed.
5972 // so we should automatically call signature_help
5973 self.show_signature_help(&ShowSignatureHelp, window, cx);
5974 }
5975
5976 Some(cx.foreground_executor().spawn(async move {
5977 apply_edits.await?;
5978 Ok(())
5979 }))
5980 }
5981
5982 pub fn toggle_code_actions(
5983 &mut self,
5984 action: &ToggleCodeActions,
5985 window: &mut Window,
5986 cx: &mut Context<Self>,
5987 ) {
5988 let quick_launch = action.quick_launch;
5989 let mut context_menu = self.context_menu.borrow_mut();
5990 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5991 if code_actions.deployed_from == action.deployed_from {
5992 // Toggle if we're selecting the same one
5993 *context_menu = None;
5994 cx.notify();
5995 return;
5996 } else {
5997 // Otherwise, clear it and start a new one
5998 *context_menu = None;
5999 cx.notify();
6000 }
6001 }
6002 drop(context_menu);
6003 let snapshot = self.snapshot(window, cx);
6004 let deployed_from = action.deployed_from.clone();
6005 let action = action.clone();
6006 self.completion_tasks.clear();
6007 self.discard_edit_prediction(false, cx);
6008
6009 let multibuffer_point = match &action.deployed_from {
6010 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6011 DisplayPoint::new(*row, 0).to_point(&snapshot)
6012 }
6013 _ => self.selections.newest::<Point>(cx).head(),
6014 };
6015 let Some((buffer, buffer_row)) = snapshot
6016 .buffer_snapshot
6017 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6018 .and_then(|(buffer_snapshot, range)| {
6019 self.buffer()
6020 .read(cx)
6021 .buffer(buffer_snapshot.remote_id())
6022 .map(|buffer| (buffer, range.start.row))
6023 })
6024 else {
6025 return;
6026 };
6027 let buffer_id = buffer.read(cx).remote_id();
6028 let tasks = self
6029 .tasks
6030 .get(&(buffer_id, buffer_row))
6031 .map(|t| Arc::new(t.to_owned()));
6032
6033 if !self.focus_handle.is_focused(window) {
6034 return;
6035 }
6036 let project = self.project.clone();
6037
6038 let code_actions_task = match deployed_from {
6039 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6040 _ => self.code_actions(buffer_row, window, cx),
6041 };
6042
6043 let runnable_task = match deployed_from {
6044 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6045 _ => {
6046 let mut task_context_task = Task::ready(None);
6047 if let Some(tasks) = &tasks
6048 && let Some(project) = project
6049 {
6050 task_context_task =
6051 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6052 }
6053
6054 cx.spawn_in(window, {
6055 let buffer = buffer.clone();
6056 async move |editor, cx| {
6057 let task_context = task_context_task.await;
6058
6059 let resolved_tasks =
6060 tasks
6061 .zip(task_context.clone())
6062 .map(|(tasks, task_context)| ResolvedTasks {
6063 templates: tasks.resolve(&task_context).collect(),
6064 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6065 multibuffer_point.row,
6066 tasks.column,
6067 )),
6068 });
6069 let debug_scenarios = editor
6070 .update(cx, |editor, cx| {
6071 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6072 })?
6073 .await;
6074 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6075 }
6076 })
6077 }
6078 };
6079
6080 cx.spawn_in(window, async move |editor, cx| {
6081 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6082 let code_actions = code_actions_task.await;
6083 let spawn_straight_away = quick_launch
6084 && resolved_tasks
6085 .as_ref()
6086 .is_some_and(|tasks| tasks.templates.len() == 1)
6087 && code_actions
6088 .as_ref()
6089 .is_none_or(|actions| actions.is_empty())
6090 && debug_scenarios.is_empty();
6091
6092 editor.update_in(cx, |editor, window, cx| {
6093 crate::hover_popover::hide_hover(editor, cx);
6094 let actions = CodeActionContents::new(
6095 resolved_tasks,
6096 code_actions,
6097 debug_scenarios,
6098 task_context.unwrap_or_default(),
6099 );
6100
6101 // Don't show the menu if there are no actions available
6102 if actions.is_empty() {
6103 cx.notify();
6104 return Task::ready(Ok(()));
6105 }
6106
6107 *editor.context_menu.borrow_mut() =
6108 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6109 buffer,
6110 actions,
6111 selected_item: Default::default(),
6112 scroll_handle: UniformListScrollHandle::default(),
6113 deployed_from,
6114 }));
6115 cx.notify();
6116 if spawn_straight_away
6117 && let Some(task) = editor.confirm_code_action(
6118 &ConfirmCodeAction { item_ix: Some(0) },
6119 window,
6120 cx,
6121 )
6122 {
6123 return task;
6124 }
6125
6126 Task::ready(Ok(()))
6127 })
6128 })
6129 .detach_and_log_err(cx);
6130 }
6131
6132 fn debug_scenarios(
6133 &mut self,
6134 resolved_tasks: &Option<ResolvedTasks>,
6135 buffer: &Entity<Buffer>,
6136 cx: &mut App,
6137 ) -> Task<Vec<task::DebugScenario>> {
6138 maybe!({
6139 let project = self.project()?;
6140 let dap_store = project.read(cx).dap_store();
6141 let mut scenarios = vec![];
6142 let resolved_tasks = resolved_tasks.as_ref()?;
6143 let buffer = buffer.read(cx);
6144 let language = buffer.language()?;
6145 let file = buffer.file();
6146 let debug_adapter = language_settings(language.name().into(), file, cx)
6147 .debuggers
6148 .first()
6149 .map(SharedString::from)
6150 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6151
6152 dap_store.update(cx, |dap_store, cx| {
6153 for (_, task) in &resolved_tasks.templates {
6154 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6155 task.original_task().clone(),
6156 debug_adapter.clone().into(),
6157 task.display_label().to_owned().into(),
6158 cx,
6159 );
6160 scenarios.push(maybe_scenario);
6161 }
6162 });
6163 Some(cx.background_spawn(async move {
6164 futures::future::join_all(scenarios)
6165 .await
6166 .into_iter()
6167 .flatten()
6168 .collect::<Vec<_>>()
6169 }))
6170 })
6171 .unwrap_or_else(|| Task::ready(vec![]))
6172 }
6173
6174 fn code_actions(
6175 &mut self,
6176 buffer_row: u32,
6177 window: &mut Window,
6178 cx: &mut Context<Self>,
6179 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6180 let mut task = self.code_actions_task.take();
6181 cx.spawn_in(window, async move |editor, cx| {
6182 while let Some(prev_task) = task {
6183 prev_task.await.log_err();
6184 task = editor
6185 .update(cx, |this, _| this.code_actions_task.take())
6186 .ok()?;
6187 }
6188
6189 editor
6190 .update(cx, |editor, cx| {
6191 editor
6192 .available_code_actions
6193 .clone()
6194 .and_then(|(location, code_actions)| {
6195 let snapshot = location.buffer.read(cx).snapshot();
6196 let point_range = location.range.to_point(&snapshot);
6197 let point_range = point_range.start.row..=point_range.end.row;
6198 if point_range.contains(&buffer_row) {
6199 Some(code_actions)
6200 } else {
6201 None
6202 }
6203 })
6204 })
6205 .ok()
6206 .flatten()
6207 })
6208 }
6209
6210 pub fn confirm_code_action(
6211 &mut self,
6212 action: &ConfirmCodeAction,
6213 window: &mut Window,
6214 cx: &mut Context<Self>,
6215 ) -> Option<Task<Result<()>>> {
6216 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6217
6218 let actions_menu =
6219 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6220 menu
6221 } else {
6222 return None;
6223 };
6224
6225 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6226 let action = actions_menu.actions.get(action_ix)?;
6227 let title = action.label();
6228 let buffer = actions_menu.buffer;
6229 let workspace = self.workspace()?;
6230
6231 match action {
6232 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6233 workspace.update(cx, |workspace, cx| {
6234 workspace.schedule_resolved_task(
6235 task_source_kind,
6236 resolved_task,
6237 false,
6238 window,
6239 cx,
6240 );
6241
6242 Some(Task::ready(Ok(())))
6243 })
6244 }
6245 CodeActionsItem::CodeAction {
6246 excerpt_id,
6247 action,
6248 provider,
6249 } => {
6250 let apply_code_action =
6251 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6252 let workspace = workspace.downgrade();
6253 Some(cx.spawn_in(window, async move |editor, cx| {
6254 let project_transaction = apply_code_action.await?;
6255 Self::open_project_transaction(
6256 &editor,
6257 workspace,
6258 project_transaction,
6259 title,
6260 cx,
6261 )
6262 .await
6263 }))
6264 }
6265 CodeActionsItem::DebugScenario(scenario) => {
6266 let context = actions_menu.actions.context;
6267
6268 workspace.update(cx, |workspace, cx| {
6269 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6270 workspace.start_debug_session(
6271 scenario,
6272 context,
6273 Some(buffer),
6274 None,
6275 window,
6276 cx,
6277 );
6278 });
6279 Some(Task::ready(Ok(())))
6280 }
6281 }
6282 }
6283
6284 pub async fn open_project_transaction(
6285 this: &WeakEntity<Editor>,
6286 workspace: WeakEntity<Workspace>,
6287 transaction: ProjectTransaction,
6288 title: String,
6289 cx: &mut AsyncWindowContext,
6290 ) -> Result<()> {
6291 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6292 cx.update(|_, cx| {
6293 entries.sort_unstable_by_key(|(buffer, _)| {
6294 buffer.read(cx).file().map(|f| f.path().clone())
6295 });
6296 })?;
6297
6298 // If the project transaction's edits are all contained within this editor, then
6299 // avoid opening a new editor to display them.
6300
6301 if let Some((buffer, transaction)) = entries.first() {
6302 if entries.len() == 1 {
6303 let excerpt = this.update(cx, |editor, cx| {
6304 editor
6305 .buffer()
6306 .read(cx)
6307 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6308 })?;
6309 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6310 && excerpted_buffer == *buffer
6311 {
6312 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6313 let excerpt_range = excerpt_range.to_offset(buffer);
6314 buffer
6315 .edited_ranges_for_transaction::<usize>(transaction)
6316 .all(|range| {
6317 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6318 })
6319 })?;
6320
6321 if all_edits_within_excerpt {
6322 return Ok(());
6323 }
6324 }
6325 }
6326 } else {
6327 return Ok(());
6328 }
6329
6330 let mut ranges_to_highlight = Vec::new();
6331 let excerpt_buffer = cx.new(|cx| {
6332 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6333 for (buffer_handle, transaction) in &entries {
6334 let edited_ranges = buffer_handle
6335 .read(cx)
6336 .edited_ranges_for_transaction::<Point>(transaction)
6337 .collect::<Vec<_>>();
6338 let (ranges, _) = multibuffer.set_excerpts_for_path(
6339 PathKey::for_buffer(buffer_handle, cx),
6340 buffer_handle.clone(),
6341 edited_ranges,
6342 DEFAULT_MULTIBUFFER_CONTEXT,
6343 cx,
6344 );
6345
6346 ranges_to_highlight.extend(ranges);
6347 }
6348 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6349 multibuffer
6350 })?;
6351
6352 workspace.update_in(cx, |workspace, window, cx| {
6353 let project = workspace.project().clone();
6354 let editor =
6355 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6356 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6357 editor.update(cx, |editor, cx| {
6358 editor.highlight_background::<Self>(
6359 &ranges_to_highlight,
6360 |theme| theme.colors().editor_highlighted_line_background,
6361 cx,
6362 );
6363 });
6364 })?;
6365
6366 Ok(())
6367 }
6368
6369 pub fn clear_code_action_providers(&mut self) {
6370 self.code_action_providers.clear();
6371 self.available_code_actions.take();
6372 }
6373
6374 pub fn add_code_action_provider(
6375 &mut self,
6376 provider: Rc<dyn CodeActionProvider>,
6377 window: &mut Window,
6378 cx: &mut Context<Self>,
6379 ) {
6380 if self
6381 .code_action_providers
6382 .iter()
6383 .any(|existing_provider| existing_provider.id() == provider.id())
6384 {
6385 return;
6386 }
6387
6388 self.code_action_providers.push(provider);
6389 self.refresh_code_actions(window, cx);
6390 }
6391
6392 pub fn remove_code_action_provider(
6393 &mut self,
6394 id: Arc<str>,
6395 window: &mut Window,
6396 cx: &mut Context<Self>,
6397 ) {
6398 self.code_action_providers
6399 .retain(|provider| provider.id() != id);
6400 self.refresh_code_actions(window, cx);
6401 }
6402
6403 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6404 !self.code_action_providers.is_empty()
6405 && EditorSettings::get_global(cx).toolbar.code_actions
6406 }
6407
6408 pub fn has_available_code_actions(&self) -> bool {
6409 self.available_code_actions
6410 .as_ref()
6411 .is_some_and(|(_, actions)| !actions.is_empty())
6412 }
6413
6414 fn render_inline_code_actions(
6415 &self,
6416 icon_size: ui::IconSize,
6417 display_row: DisplayRow,
6418 is_active: bool,
6419 cx: &mut Context<Self>,
6420 ) -> AnyElement {
6421 let show_tooltip = !self.context_menu_visible();
6422 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6423 .icon_size(icon_size)
6424 .shape(ui::IconButtonShape::Square)
6425 .icon_color(ui::Color::Hidden)
6426 .toggle_state(is_active)
6427 .when(show_tooltip, |this| {
6428 this.tooltip({
6429 let focus_handle = self.focus_handle.clone();
6430 move |window, cx| {
6431 Tooltip::for_action_in(
6432 "Toggle Code Actions",
6433 &ToggleCodeActions {
6434 deployed_from: None,
6435 quick_launch: false,
6436 },
6437 &focus_handle,
6438 window,
6439 cx,
6440 )
6441 }
6442 })
6443 })
6444 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6445 window.focus(&editor.focus_handle(cx));
6446 editor.toggle_code_actions(
6447 &crate::actions::ToggleCodeActions {
6448 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6449 display_row,
6450 )),
6451 quick_launch: false,
6452 },
6453 window,
6454 cx,
6455 );
6456 }))
6457 .into_any_element()
6458 }
6459
6460 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6461 &self.context_menu
6462 }
6463
6464 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6465 let newest_selection = self.selections.newest_anchor().clone();
6466 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6467 let buffer = self.buffer.read(cx);
6468 if newest_selection.head().diff_base_anchor.is_some() {
6469 return None;
6470 }
6471 let (start_buffer, start) =
6472 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6473 let (end_buffer, end) =
6474 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6475 if start_buffer != end_buffer {
6476 return None;
6477 }
6478
6479 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6480 cx.background_executor()
6481 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6482 .await;
6483
6484 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6485 let providers = this.code_action_providers.clone();
6486 let tasks = this
6487 .code_action_providers
6488 .iter()
6489 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6490 .collect::<Vec<_>>();
6491 (providers, tasks)
6492 })?;
6493
6494 let mut actions = Vec::new();
6495 for (provider, provider_actions) in
6496 providers.into_iter().zip(future::join_all(tasks).await)
6497 {
6498 if let Some(provider_actions) = provider_actions.log_err() {
6499 actions.extend(provider_actions.into_iter().map(|action| {
6500 AvailableCodeAction {
6501 excerpt_id: newest_selection.start.excerpt_id,
6502 action,
6503 provider: provider.clone(),
6504 }
6505 }));
6506 }
6507 }
6508
6509 this.update(cx, |this, cx| {
6510 this.available_code_actions = if actions.is_empty() {
6511 None
6512 } else {
6513 Some((
6514 Location {
6515 buffer: start_buffer,
6516 range: start..end,
6517 },
6518 actions.into(),
6519 ))
6520 };
6521 cx.notify();
6522 })
6523 }));
6524 None
6525 }
6526
6527 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6528 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6529 self.show_git_blame_inline = false;
6530
6531 self.show_git_blame_inline_delay_task =
6532 Some(cx.spawn_in(window, async move |this, cx| {
6533 cx.background_executor().timer(delay).await;
6534
6535 this.update(cx, |this, cx| {
6536 this.show_git_blame_inline = true;
6537 cx.notify();
6538 })
6539 .log_err();
6540 }));
6541 }
6542 }
6543
6544 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6545 let snapshot = self.snapshot(window, cx);
6546 let cursor = self.selections.newest::<Point>(cx).head();
6547 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6548 else {
6549 return;
6550 };
6551
6552 let Some(blame) = self.blame.as_ref() else {
6553 return;
6554 };
6555
6556 let row_info = RowInfo {
6557 buffer_id: Some(buffer.remote_id()),
6558 buffer_row: Some(point.row),
6559 ..Default::default()
6560 };
6561 let Some(blame_entry) = blame
6562 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6563 .flatten()
6564 else {
6565 return;
6566 };
6567
6568 let anchor = self.selections.newest_anchor().head();
6569 let position = self.to_pixel_point(anchor, &snapshot, window);
6570 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6571 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6572 };
6573 }
6574
6575 fn show_blame_popover(
6576 &mut self,
6577 blame_entry: &BlameEntry,
6578 position: gpui::Point<Pixels>,
6579 ignore_timeout: bool,
6580 cx: &mut Context<Self>,
6581 ) {
6582 if let Some(state) = &mut self.inline_blame_popover {
6583 state.hide_task.take();
6584 } else {
6585 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6586 let blame_entry = blame_entry.clone();
6587 let show_task = cx.spawn(async move |editor, cx| {
6588 if !ignore_timeout {
6589 cx.background_executor()
6590 .timer(std::time::Duration::from_millis(blame_popover_delay))
6591 .await;
6592 }
6593 editor
6594 .update(cx, |editor, cx| {
6595 editor.inline_blame_popover_show_task.take();
6596 let Some(blame) = editor.blame.as_ref() else {
6597 return;
6598 };
6599 let blame = blame.read(cx);
6600 let details = blame.details_for_entry(&blame_entry);
6601 let markdown = cx.new(|cx| {
6602 Markdown::new(
6603 details
6604 .as_ref()
6605 .map(|message| message.message.clone())
6606 .unwrap_or_default(),
6607 None,
6608 None,
6609 cx,
6610 )
6611 });
6612 editor.inline_blame_popover = Some(InlineBlamePopover {
6613 position,
6614 hide_task: None,
6615 popover_bounds: None,
6616 popover_state: InlineBlamePopoverState {
6617 scroll_handle: ScrollHandle::new(),
6618 commit_message: details,
6619 markdown,
6620 },
6621 keyboard_grace: ignore_timeout,
6622 });
6623 cx.notify();
6624 })
6625 .ok();
6626 });
6627 self.inline_blame_popover_show_task = Some(show_task);
6628 }
6629 }
6630
6631 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6632 self.inline_blame_popover_show_task.take();
6633 if let Some(state) = &mut self.inline_blame_popover {
6634 let hide_task = cx.spawn(async move |editor, cx| {
6635 cx.background_executor()
6636 .timer(std::time::Duration::from_millis(100))
6637 .await;
6638 editor
6639 .update(cx, |editor, cx| {
6640 editor.inline_blame_popover.take();
6641 cx.notify();
6642 })
6643 .ok();
6644 });
6645 state.hide_task = Some(hide_task);
6646 }
6647 }
6648
6649 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6650 if self.pending_rename.is_some() {
6651 return None;
6652 }
6653
6654 let provider = self.semantics_provider.clone()?;
6655 let buffer = self.buffer.read(cx);
6656 let newest_selection = self.selections.newest_anchor().clone();
6657 let cursor_position = newest_selection.head();
6658 let (cursor_buffer, cursor_buffer_position) =
6659 buffer.text_anchor_for_position(cursor_position, cx)?;
6660 let (tail_buffer, tail_buffer_position) =
6661 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6662 if cursor_buffer != tail_buffer {
6663 return None;
6664 }
6665
6666 let snapshot = cursor_buffer.read(cx).snapshot();
6667 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6668 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6669 if start_word_range != end_word_range {
6670 self.document_highlights_task.take();
6671 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6672 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6673 return None;
6674 }
6675
6676 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6677 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6678 cx.background_executor()
6679 .timer(Duration::from_millis(debounce))
6680 .await;
6681
6682 let highlights = if let Some(highlights) = cx
6683 .update(|cx| {
6684 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6685 })
6686 .ok()
6687 .flatten()
6688 {
6689 highlights.await.log_err()
6690 } else {
6691 None
6692 };
6693
6694 if let Some(highlights) = highlights {
6695 this.update(cx, |this, cx| {
6696 if this.pending_rename.is_some() {
6697 return;
6698 }
6699
6700 let buffer_id = cursor_position.buffer_id;
6701 let buffer = this.buffer.read(cx);
6702 if buffer
6703 .text_anchor_for_position(cursor_position, cx)
6704 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6705 {
6706 return;
6707 }
6708
6709 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6710 let mut write_ranges = Vec::new();
6711 let mut read_ranges = Vec::new();
6712 for highlight in highlights {
6713 for (excerpt_id, excerpt_range) in
6714 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6715 {
6716 let start = highlight
6717 .range
6718 .start
6719 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6720 let end = highlight
6721 .range
6722 .end
6723 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6724 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6725 continue;
6726 }
6727
6728 let range = Anchor {
6729 buffer_id,
6730 excerpt_id,
6731 text_anchor: start,
6732 diff_base_anchor: None,
6733 }..Anchor {
6734 buffer_id,
6735 excerpt_id,
6736 text_anchor: end,
6737 diff_base_anchor: None,
6738 };
6739 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6740 write_ranges.push(range);
6741 } else {
6742 read_ranges.push(range);
6743 }
6744 }
6745 }
6746
6747 this.highlight_background::<DocumentHighlightRead>(
6748 &read_ranges,
6749 |theme| theme.colors().editor_document_highlight_read_background,
6750 cx,
6751 );
6752 this.highlight_background::<DocumentHighlightWrite>(
6753 &write_ranges,
6754 |theme| theme.colors().editor_document_highlight_write_background,
6755 cx,
6756 );
6757 cx.notify();
6758 })
6759 .log_err();
6760 }
6761 }));
6762 None
6763 }
6764
6765 fn prepare_highlight_query_from_selection(
6766 &mut self,
6767 cx: &mut Context<Editor>,
6768 ) -> Option<(String, Range<Anchor>)> {
6769 if matches!(self.mode, EditorMode::SingleLine) {
6770 return None;
6771 }
6772 if !EditorSettings::get_global(cx).selection_highlight {
6773 return None;
6774 }
6775 if self.selections.count() != 1 || self.selections.line_mode {
6776 return None;
6777 }
6778 let selection = self.selections.newest::<Point>(cx);
6779 if selection.is_empty() || selection.start.row != selection.end.row {
6780 return None;
6781 }
6782 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6783 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6784 let query = multi_buffer_snapshot
6785 .text_for_range(selection_anchor_range.clone())
6786 .collect::<String>();
6787 if query.trim().is_empty() {
6788 return None;
6789 }
6790 Some((query, selection_anchor_range))
6791 }
6792
6793 fn update_selection_occurrence_highlights(
6794 &mut self,
6795 query_text: String,
6796 query_range: Range<Anchor>,
6797 multi_buffer_range_to_query: Range<Point>,
6798 use_debounce: bool,
6799 window: &mut Window,
6800 cx: &mut Context<Editor>,
6801 ) -> Task<()> {
6802 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6803 cx.spawn_in(window, async move |editor, cx| {
6804 if use_debounce {
6805 cx.background_executor()
6806 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6807 .await;
6808 }
6809 let match_task = cx.background_spawn(async move {
6810 let buffer_ranges = multi_buffer_snapshot
6811 .range_to_buffer_ranges(multi_buffer_range_to_query)
6812 .into_iter()
6813 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6814 let mut match_ranges = Vec::new();
6815 let Ok(regex) = project::search::SearchQuery::text(
6816 query_text.clone(),
6817 false,
6818 false,
6819 false,
6820 Default::default(),
6821 Default::default(),
6822 false,
6823 None,
6824 ) else {
6825 return Vec::default();
6826 };
6827 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6828 match_ranges.extend(
6829 regex
6830 .search(buffer_snapshot, Some(search_range.clone()))
6831 .await
6832 .into_iter()
6833 .filter_map(|match_range| {
6834 let match_start = buffer_snapshot
6835 .anchor_after(search_range.start + match_range.start);
6836 let match_end = buffer_snapshot
6837 .anchor_before(search_range.start + match_range.end);
6838 let match_anchor_range = Anchor::range_in_buffer(
6839 excerpt_id,
6840 buffer_snapshot.remote_id(),
6841 match_start..match_end,
6842 );
6843 (match_anchor_range != query_range).then_some(match_anchor_range)
6844 }),
6845 );
6846 }
6847 match_ranges
6848 });
6849 let match_ranges = match_task.await;
6850 editor
6851 .update_in(cx, |editor, _, cx| {
6852 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6853 if !match_ranges.is_empty() {
6854 editor.highlight_background::<SelectedTextHighlight>(
6855 &match_ranges,
6856 |theme| theme.colors().editor_document_highlight_bracket_background,
6857 cx,
6858 )
6859 }
6860 })
6861 .log_err();
6862 })
6863 }
6864
6865 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6866 struct NewlineFold;
6867 let type_id = std::any::TypeId::of::<NewlineFold>();
6868 if !self.mode.is_single_line() {
6869 return;
6870 }
6871 let snapshot = self.snapshot(window, cx);
6872 if snapshot.buffer_snapshot.max_point().row == 0 {
6873 return;
6874 }
6875 let task = cx.background_spawn(async move {
6876 let new_newlines = snapshot
6877 .buffer_chars_at(0)
6878 .filter_map(|(c, i)| {
6879 if c == '\n' {
6880 Some(
6881 snapshot.buffer_snapshot.anchor_after(i)
6882 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6883 )
6884 } else {
6885 None
6886 }
6887 })
6888 .collect::<Vec<_>>();
6889 let existing_newlines = snapshot
6890 .folds_in_range(0..snapshot.buffer_snapshot.len())
6891 .filter_map(|fold| {
6892 if fold.placeholder.type_tag == Some(type_id) {
6893 Some(fold.range.start..fold.range.end)
6894 } else {
6895 None
6896 }
6897 })
6898 .collect::<Vec<_>>();
6899
6900 (new_newlines, existing_newlines)
6901 });
6902 self.folding_newlines = cx.spawn(async move |this, cx| {
6903 let (new_newlines, existing_newlines) = task.await;
6904 if new_newlines == existing_newlines {
6905 return;
6906 }
6907 let placeholder = FoldPlaceholder {
6908 render: Arc::new(move |_, _, cx| {
6909 div()
6910 .bg(cx.theme().status().hint_background)
6911 .border_b_1()
6912 .size_full()
6913 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6914 .border_color(cx.theme().status().hint)
6915 .child("\\n")
6916 .into_any()
6917 }),
6918 constrain_width: false,
6919 merge_adjacent: false,
6920 type_tag: Some(type_id),
6921 };
6922 let creases = new_newlines
6923 .into_iter()
6924 .map(|range| Crease::simple(range, placeholder.clone()))
6925 .collect();
6926 this.update(cx, |this, cx| {
6927 this.display_map.update(cx, |display_map, cx| {
6928 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6929 display_map.fold(creases, cx);
6930 });
6931 })
6932 .ok();
6933 });
6934 }
6935
6936 fn refresh_selected_text_highlights(
6937 &mut self,
6938 on_buffer_edit: bool,
6939 window: &mut Window,
6940 cx: &mut Context<Editor>,
6941 ) {
6942 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6943 else {
6944 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6945 self.quick_selection_highlight_task.take();
6946 self.debounced_selection_highlight_task.take();
6947 return;
6948 };
6949 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6950 if on_buffer_edit
6951 || self
6952 .quick_selection_highlight_task
6953 .as_ref()
6954 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6955 {
6956 let multi_buffer_visible_start = self
6957 .scroll_manager
6958 .anchor()
6959 .anchor
6960 .to_point(&multi_buffer_snapshot);
6961 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6962 multi_buffer_visible_start
6963 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6964 Bias::Left,
6965 );
6966 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6967 self.quick_selection_highlight_task = Some((
6968 query_range.clone(),
6969 self.update_selection_occurrence_highlights(
6970 query_text.clone(),
6971 query_range.clone(),
6972 multi_buffer_visible_range,
6973 false,
6974 window,
6975 cx,
6976 ),
6977 ));
6978 }
6979 if on_buffer_edit
6980 || self
6981 .debounced_selection_highlight_task
6982 .as_ref()
6983 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6984 {
6985 let multi_buffer_start = multi_buffer_snapshot
6986 .anchor_before(0)
6987 .to_point(&multi_buffer_snapshot);
6988 let multi_buffer_end = multi_buffer_snapshot
6989 .anchor_after(multi_buffer_snapshot.len())
6990 .to_point(&multi_buffer_snapshot);
6991 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6992 self.debounced_selection_highlight_task = Some((
6993 query_range.clone(),
6994 self.update_selection_occurrence_highlights(
6995 query_text,
6996 query_range,
6997 multi_buffer_full_range,
6998 true,
6999 window,
7000 cx,
7001 ),
7002 ));
7003 }
7004 }
7005
7006 pub fn refresh_edit_prediction(
7007 &mut self,
7008 debounce: bool,
7009 user_requested: bool,
7010 window: &mut Window,
7011 cx: &mut Context<Self>,
7012 ) -> Option<()> {
7013 if DisableAiSettings::get_global(cx).disable_ai {
7014 return None;
7015 }
7016
7017 let provider = self.edit_prediction_provider()?;
7018 let cursor = self.selections.newest_anchor().head();
7019 let (buffer, cursor_buffer_position) =
7020 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7021
7022 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7023 self.discard_edit_prediction(false, cx);
7024 return None;
7025 }
7026
7027 if !user_requested
7028 && (!self.should_show_edit_predictions()
7029 || !self.is_focused(window)
7030 || buffer.read(cx).is_empty())
7031 {
7032 self.discard_edit_prediction(false, cx);
7033 return None;
7034 }
7035
7036 self.update_visible_edit_prediction(window, cx);
7037 provider.refresh(
7038 self.project.clone(),
7039 buffer,
7040 cursor_buffer_position,
7041 debounce,
7042 cx,
7043 );
7044 Some(())
7045 }
7046
7047 fn show_edit_predictions_in_menu(&self) -> bool {
7048 match self.edit_prediction_settings {
7049 EditPredictionSettings::Disabled => false,
7050 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7051 }
7052 }
7053
7054 pub fn edit_predictions_enabled(&self) -> bool {
7055 match self.edit_prediction_settings {
7056 EditPredictionSettings::Disabled => false,
7057 EditPredictionSettings::Enabled { .. } => true,
7058 }
7059 }
7060
7061 fn edit_prediction_requires_modifier(&self) -> bool {
7062 match self.edit_prediction_settings {
7063 EditPredictionSettings::Disabled => false,
7064 EditPredictionSettings::Enabled {
7065 preview_requires_modifier,
7066 ..
7067 } => preview_requires_modifier,
7068 }
7069 }
7070
7071 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7072 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7073 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7074 self.discard_edit_prediction(false, cx);
7075 } else {
7076 let selection = self.selections.newest_anchor();
7077 let cursor = selection.head();
7078
7079 if let Some((buffer, cursor_buffer_position)) =
7080 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7081 {
7082 self.edit_prediction_settings =
7083 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7084 }
7085 }
7086 }
7087
7088 fn edit_prediction_settings_at_position(
7089 &self,
7090 buffer: &Entity<Buffer>,
7091 buffer_position: language::Anchor,
7092 cx: &App,
7093 ) -> EditPredictionSettings {
7094 if !self.mode.is_full()
7095 || !self.show_edit_predictions_override.unwrap_or(true)
7096 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7097 {
7098 return EditPredictionSettings::Disabled;
7099 }
7100
7101 let buffer = buffer.read(cx);
7102
7103 let file = buffer.file();
7104
7105 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7106 return EditPredictionSettings::Disabled;
7107 };
7108
7109 let by_provider = matches!(
7110 self.menu_edit_predictions_policy,
7111 MenuEditPredictionsPolicy::ByProvider
7112 );
7113
7114 let show_in_menu = by_provider
7115 && self
7116 .edit_prediction_provider
7117 .as_ref()
7118 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7119
7120 let preview_requires_modifier =
7121 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7122
7123 EditPredictionSettings::Enabled {
7124 show_in_menu,
7125 preview_requires_modifier,
7126 }
7127 }
7128
7129 fn should_show_edit_predictions(&self) -> bool {
7130 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7131 }
7132
7133 pub fn edit_prediction_preview_is_active(&self) -> bool {
7134 matches!(
7135 self.edit_prediction_preview,
7136 EditPredictionPreview::Active { .. }
7137 )
7138 }
7139
7140 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7141 let cursor = self.selections.newest_anchor().head();
7142 if let Some((buffer, cursor_position)) =
7143 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7144 {
7145 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7146 } else {
7147 false
7148 }
7149 }
7150
7151 pub fn supports_minimap(&self, cx: &App) -> bool {
7152 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7153 }
7154
7155 fn edit_predictions_enabled_in_buffer(
7156 &self,
7157 buffer: &Entity<Buffer>,
7158 buffer_position: language::Anchor,
7159 cx: &App,
7160 ) -> bool {
7161 maybe!({
7162 if self.read_only(cx) {
7163 return Some(false);
7164 }
7165 let provider = self.edit_prediction_provider()?;
7166 if !provider.is_enabled(buffer, buffer_position, cx) {
7167 return Some(false);
7168 }
7169 let buffer = buffer.read(cx);
7170 let Some(file) = buffer.file() else {
7171 return Some(true);
7172 };
7173 let settings = all_language_settings(Some(file), cx);
7174 Some(settings.edit_predictions_enabled_for_file(file, cx))
7175 })
7176 .unwrap_or(false)
7177 }
7178
7179 fn cycle_edit_prediction(
7180 &mut self,
7181 direction: Direction,
7182 window: &mut Window,
7183 cx: &mut Context<Self>,
7184 ) -> Option<()> {
7185 let provider = self.edit_prediction_provider()?;
7186 let cursor = self.selections.newest_anchor().head();
7187 let (buffer, cursor_buffer_position) =
7188 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7189 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7190 return None;
7191 }
7192
7193 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7194 self.update_visible_edit_prediction(window, cx);
7195
7196 Some(())
7197 }
7198
7199 pub fn show_edit_prediction(
7200 &mut self,
7201 _: &ShowEditPrediction,
7202 window: &mut Window,
7203 cx: &mut Context<Self>,
7204 ) {
7205 if !self.has_active_edit_prediction() {
7206 self.refresh_edit_prediction(false, true, window, cx);
7207 return;
7208 }
7209
7210 self.update_visible_edit_prediction(window, cx);
7211 }
7212
7213 pub fn display_cursor_names(
7214 &mut self,
7215 _: &DisplayCursorNames,
7216 window: &mut Window,
7217 cx: &mut Context<Self>,
7218 ) {
7219 self.show_cursor_names(window, cx);
7220 }
7221
7222 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7223 self.show_cursor_names = true;
7224 cx.notify();
7225 cx.spawn_in(window, async move |this, cx| {
7226 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7227 this.update(cx, |this, cx| {
7228 this.show_cursor_names = false;
7229 cx.notify()
7230 })
7231 .ok()
7232 })
7233 .detach();
7234 }
7235
7236 pub fn next_edit_prediction(
7237 &mut self,
7238 _: &NextEditPrediction,
7239 window: &mut Window,
7240 cx: &mut Context<Self>,
7241 ) {
7242 if self.has_active_edit_prediction() {
7243 self.cycle_edit_prediction(Direction::Next, window, cx);
7244 } else {
7245 let is_copilot_disabled = self
7246 .refresh_edit_prediction(false, true, window, cx)
7247 .is_none();
7248 if is_copilot_disabled {
7249 cx.propagate();
7250 }
7251 }
7252 }
7253
7254 pub fn previous_edit_prediction(
7255 &mut self,
7256 _: &PreviousEditPrediction,
7257 window: &mut Window,
7258 cx: &mut Context<Self>,
7259 ) {
7260 if self.has_active_edit_prediction() {
7261 self.cycle_edit_prediction(Direction::Prev, window, cx);
7262 } else {
7263 let is_copilot_disabled = self
7264 .refresh_edit_prediction(false, true, window, cx)
7265 .is_none();
7266 if is_copilot_disabled {
7267 cx.propagate();
7268 }
7269 }
7270 }
7271
7272 pub fn accept_edit_prediction(
7273 &mut self,
7274 _: &AcceptEditPrediction,
7275 window: &mut Window,
7276 cx: &mut Context<Self>,
7277 ) {
7278 if self.show_edit_predictions_in_menu() {
7279 self.hide_context_menu(window, cx);
7280 }
7281
7282 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7283 return;
7284 };
7285
7286 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7287
7288 match &active_edit_prediction.completion {
7289 EditPrediction::Move { target, .. } => {
7290 let target = *target;
7291
7292 if let Some(position_map) = &self.last_position_map {
7293 if position_map
7294 .visible_row_range
7295 .contains(&target.to_display_point(&position_map.snapshot).row())
7296 || !self.edit_prediction_requires_modifier()
7297 {
7298 self.unfold_ranges(&[target..target], true, false, cx);
7299 // Note that this is also done in vim's handler of the Tab action.
7300 self.change_selections(
7301 SelectionEffects::scroll(Autoscroll::newest()),
7302 window,
7303 cx,
7304 |selections| {
7305 selections.select_anchor_ranges([target..target]);
7306 },
7307 );
7308 self.clear_row_highlights::<EditPredictionPreview>();
7309
7310 self.edit_prediction_preview
7311 .set_previous_scroll_position(None);
7312 } else {
7313 self.edit_prediction_preview
7314 .set_previous_scroll_position(Some(
7315 position_map.snapshot.scroll_anchor,
7316 ));
7317
7318 self.highlight_rows::<EditPredictionPreview>(
7319 target..target,
7320 cx.theme().colors().editor_highlighted_line_background,
7321 RowHighlightOptions {
7322 autoscroll: true,
7323 ..Default::default()
7324 },
7325 cx,
7326 );
7327 self.request_autoscroll(Autoscroll::fit(), cx);
7328 }
7329 }
7330 }
7331 EditPrediction::Edit { edits, .. } => {
7332 if let Some(provider) = self.edit_prediction_provider() {
7333 provider.accept(cx);
7334 }
7335
7336 // Store the transaction ID and selections before applying the edit
7337 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7338
7339 let snapshot = self.buffer.read(cx).snapshot(cx);
7340 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7341
7342 self.buffer.update(cx, |buffer, cx| {
7343 buffer.edit(edits.iter().cloned(), None, cx)
7344 });
7345
7346 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7347 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7348 });
7349
7350 let selections = self.selections.disjoint_anchors();
7351 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7352 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7353 if has_new_transaction {
7354 self.selection_history
7355 .insert_transaction(transaction_id_now, selections);
7356 }
7357 }
7358
7359 self.update_visible_edit_prediction(window, cx);
7360 if self.active_edit_prediction.is_none() {
7361 self.refresh_edit_prediction(true, true, window, cx);
7362 }
7363
7364 cx.notify();
7365 }
7366 }
7367
7368 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7369 }
7370
7371 pub fn accept_partial_edit_prediction(
7372 &mut self,
7373 _: &AcceptPartialEditPrediction,
7374 window: &mut Window,
7375 cx: &mut Context<Self>,
7376 ) {
7377 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7378 return;
7379 };
7380 if self.selections.count() != 1 {
7381 return;
7382 }
7383
7384 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7385
7386 match &active_edit_prediction.completion {
7387 EditPrediction::Move { target, .. } => {
7388 let target = *target;
7389 self.change_selections(
7390 SelectionEffects::scroll(Autoscroll::newest()),
7391 window,
7392 cx,
7393 |selections| {
7394 selections.select_anchor_ranges([target..target]);
7395 },
7396 );
7397 }
7398 EditPrediction::Edit { edits, .. } => {
7399 // Find an insertion that starts at the cursor position.
7400 let snapshot = self.buffer.read(cx).snapshot(cx);
7401 let cursor_offset = self.selections.newest::<usize>(cx).head();
7402 let insertion = edits.iter().find_map(|(range, text)| {
7403 let range = range.to_offset(&snapshot);
7404 if range.is_empty() && range.start == cursor_offset {
7405 Some(text)
7406 } else {
7407 None
7408 }
7409 });
7410
7411 if let Some(text) = insertion {
7412 let mut partial_completion = text
7413 .chars()
7414 .by_ref()
7415 .take_while(|c| c.is_alphabetic())
7416 .collect::<String>();
7417 if partial_completion.is_empty() {
7418 partial_completion = text
7419 .chars()
7420 .by_ref()
7421 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7422 .collect::<String>();
7423 }
7424
7425 cx.emit(EditorEvent::InputHandled {
7426 utf16_range_to_replace: None,
7427 text: partial_completion.clone().into(),
7428 });
7429
7430 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7431
7432 self.refresh_edit_prediction(true, true, window, cx);
7433 cx.notify();
7434 } else {
7435 self.accept_edit_prediction(&Default::default(), window, cx);
7436 }
7437 }
7438 }
7439 }
7440
7441 fn discard_edit_prediction(
7442 &mut self,
7443 should_report_edit_prediction_event: bool,
7444 cx: &mut Context<Self>,
7445 ) -> bool {
7446 if should_report_edit_prediction_event {
7447 let completion_id = self
7448 .active_edit_prediction
7449 .as_ref()
7450 .and_then(|active_completion| active_completion.completion_id.clone());
7451
7452 self.report_edit_prediction_event(completion_id, false, cx);
7453 }
7454
7455 if let Some(provider) = self.edit_prediction_provider() {
7456 provider.discard(cx);
7457 }
7458
7459 self.take_active_edit_prediction(cx)
7460 }
7461
7462 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7463 let Some(provider) = self.edit_prediction_provider() else {
7464 return;
7465 };
7466
7467 let Some((_, buffer, _)) = self
7468 .buffer
7469 .read(cx)
7470 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7471 else {
7472 return;
7473 };
7474
7475 let extension = buffer
7476 .read(cx)
7477 .file()
7478 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7479
7480 let event_type = match accepted {
7481 true => "Edit Prediction Accepted",
7482 false => "Edit Prediction Discarded",
7483 };
7484 telemetry::event!(
7485 event_type,
7486 provider = provider.name(),
7487 prediction_id = id,
7488 suggestion_accepted = accepted,
7489 file_extension = extension,
7490 );
7491 }
7492
7493 pub fn has_active_edit_prediction(&self) -> bool {
7494 self.active_edit_prediction.is_some()
7495 }
7496
7497 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7498 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7499 return false;
7500 };
7501
7502 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7503 self.clear_highlights::<EditPredictionHighlight>(cx);
7504 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7505 true
7506 }
7507
7508 /// Returns true when we're displaying the edit prediction popover below the cursor
7509 /// like we are not previewing and the LSP autocomplete menu is visible
7510 /// or we are in `when_holding_modifier` mode.
7511 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7512 if self.edit_prediction_preview_is_active()
7513 || !self.show_edit_predictions_in_menu()
7514 || !self.edit_predictions_enabled()
7515 {
7516 return false;
7517 }
7518
7519 if self.has_visible_completions_menu() {
7520 return true;
7521 }
7522
7523 has_completion && self.edit_prediction_requires_modifier()
7524 }
7525
7526 fn handle_modifiers_changed(
7527 &mut self,
7528 modifiers: Modifiers,
7529 position_map: &PositionMap,
7530 window: &mut Window,
7531 cx: &mut Context<Self>,
7532 ) {
7533 if self.show_edit_predictions_in_menu() {
7534 self.update_edit_prediction_preview(&modifiers, window, cx);
7535 }
7536
7537 self.update_selection_mode(&modifiers, position_map, window, cx);
7538
7539 let mouse_position = window.mouse_position();
7540 if !position_map.text_hitbox.is_hovered(window) {
7541 return;
7542 }
7543
7544 self.update_hovered_link(
7545 position_map.point_for_position(mouse_position),
7546 &position_map.snapshot,
7547 modifiers,
7548 window,
7549 cx,
7550 )
7551 }
7552
7553 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7554 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7555 if invert {
7556 match multi_cursor_setting {
7557 MultiCursorModifier::Alt => modifiers.alt,
7558 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7559 }
7560 } else {
7561 match multi_cursor_setting {
7562 MultiCursorModifier::Alt => modifiers.secondary(),
7563 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7564 }
7565 }
7566 }
7567
7568 fn columnar_selection_mode(
7569 modifiers: &Modifiers,
7570 cx: &mut Context<Self>,
7571 ) -> Option<ColumnarMode> {
7572 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7573 if Self::multi_cursor_modifier(false, modifiers, cx) {
7574 Some(ColumnarMode::FromMouse)
7575 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7576 Some(ColumnarMode::FromSelection)
7577 } else {
7578 None
7579 }
7580 } else {
7581 None
7582 }
7583 }
7584
7585 fn update_selection_mode(
7586 &mut self,
7587 modifiers: &Modifiers,
7588 position_map: &PositionMap,
7589 window: &mut Window,
7590 cx: &mut Context<Self>,
7591 ) {
7592 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7593 return;
7594 };
7595 if self.selections.pending.is_none() {
7596 return;
7597 }
7598
7599 let mouse_position = window.mouse_position();
7600 let point_for_position = position_map.point_for_position(mouse_position);
7601 let position = point_for_position.previous_valid;
7602
7603 self.select(
7604 SelectPhase::BeginColumnar {
7605 position,
7606 reset: false,
7607 mode,
7608 goal_column: point_for_position.exact_unclipped.column(),
7609 },
7610 window,
7611 cx,
7612 );
7613 }
7614
7615 fn update_edit_prediction_preview(
7616 &mut self,
7617 modifiers: &Modifiers,
7618 window: &mut Window,
7619 cx: &mut Context<Self>,
7620 ) {
7621 let mut modifiers_held = false;
7622 if let Some(accept_keystroke) = self
7623 .accept_edit_prediction_keybind(false, window, cx)
7624 .keystroke()
7625 {
7626 modifiers_held = modifiers_held
7627 || (&accept_keystroke.modifiers == modifiers
7628 && accept_keystroke.modifiers.modified());
7629 };
7630 if let Some(accept_partial_keystroke) = self
7631 .accept_edit_prediction_keybind(true, window, cx)
7632 .keystroke()
7633 {
7634 modifiers_held = modifiers_held
7635 || (&accept_partial_keystroke.modifiers == modifiers
7636 && accept_partial_keystroke.modifiers.modified());
7637 }
7638
7639 if modifiers_held {
7640 if matches!(
7641 self.edit_prediction_preview,
7642 EditPredictionPreview::Inactive { .. }
7643 ) {
7644 self.edit_prediction_preview = EditPredictionPreview::Active {
7645 previous_scroll_position: None,
7646 since: Instant::now(),
7647 };
7648
7649 self.update_visible_edit_prediction(window, cx);
7650 cx.notify();
7651 }
7652 } else if let EditPredictionPreview::Active {
7653 previous_scroll_position,
7654 since,
7655 } = self.edit_prediction_preview
7656 {
7657 if let (Some(previous_scroll_position), Some(position_map)) =
7658 (previous_scroll_position, self.last_position_map.as_ref())
7659 {
7660 self.set_scroll_position(
7661 previous_scroll_position
7662 .scroll_position(&position_map.snapshot.display_snapshot),
7663 window,
7664 cx,
7665 );
7666 }
7667
7668 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7669 released_too_fast: since.elapsed() < Duration::from_millis(200),
7670 };
7671 self.clear_row_highlights::<EditPredictionPreview>();
7672 self.update_visible_edit_prediction(window, cx);
7673 cx.notify();
7674 }
7675 }
7676
7677 fn update_visible_edit_prediction(
7678 &mut self,
7679 _window: &mut Window,
7680 cx: &mut Context<Self>,
7681 ) -> Option<()> {
7682 if DisableAiSettings::get_global(cx).disable_ai {
7683 return None;
7684 }
7685
7686 let selection = self.selections.newest_anchor();
7687 let cursor = selection.head();
7688 let multibuffer = self.buffer.read(cx).snapshot(cx);
7689 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7690 let excerpt_id = cursor.excerpt_id;
7691
7692 let show_in_menu = self.show_edit_predictions_in_menu();
7693 let completions_menu_has_precedence = !show_in_menu
7694 && (self.context_menu.borrow().is_some()
7695 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7696
7697 if completions_menu_has_precedence
7698 || !offset_selection.is_empty()
7699 || self
7700 .active_edit_prediction
7701 .as_ref()
7702 .is_some_and(|completion| {
7703 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7704 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7705 !invalidation_range.contains(&offset_selection.head())
7706 })
7707 {
7708 self.discard_edit_prediction(false, cx);
7709 return None;
7710 }
7711
7712 self.take_active_edit_prediction(cx);
7713 let Some(provider) = self.edit_prediction_provider() else {
7714 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7715 return None;
7716 };
7717
7718 let (buffer, cursor_buffer_position) =
7719 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7720
7721 self.edit_prediction_settings =
7722 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7723
7724 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7725 self.discard_edit_prediction(false, cx);
7726 return None;
7727 };
7728
7729 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7730
7731 if self.edit_prediction_indent_conflict {
7732 let cursor_point = cursor.to_point(&multibuffer);
7733
7734 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7735
7736 if let Some((_, indent)) = indents.iter().next()
7737 && indent.len == cursor_point.column
7738 {
7739 self.edit_prediction_indent_conflict = false;
7740 }
7741 }
7742
7743 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7744 let edits = edit_prediction
7745 .edits
7746 .into_iter()
7747 .flat_map(|(range, new_text)| {
7748 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7749 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7750 Some((start..end, new_text))
7751 })
7752 .collect::<Vec<_>>();
7753 if edits.is_empty() {
7754 return None;
7755 }
7756
7757 let first_edit_start = edits.first().unwrap().0.start;
7758 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7759 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7760
7761 let last_edit_end = edits.last().unwrap().0.end;
7762 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7763 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7764
7765 let cursor_row = cursor.to_point(&multibuffer).row;
7766
7767 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7768
7769 let mut inlay_ids = Vec::new();
7770 let invalidation_row_range;
7771 let move_invalidation_row_range = if cursor_row < edit_start_row {
7772 Some(cursor_row..edit_end_row)
7773 } else if cursor_row > edit_end_row {
7774 Some(edit_start_row..cursor_row)
7775 } else {
7776 None
7777 };
7778 let supports_jump = self
7779 .edit_prediction_provider
7780 .as_ref()
7781 .map(|provider| provider.provider.supports_jump_to_edit())
7782 .unwrap_or(true);
7783
7784 let is_move = supports_jump
7785 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7786 let completion = if is_move {
7787 invalidation_row_range =
7788 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7789 let target = first_edit_start;
7790 EditPrediction::Move { target, snapshot }
7791 } else {
7792 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7793 && !self.edit_predictions_hidden_for_vim_mode;
7794
7795 if show_completions_in_buffer {
7796 if edits
7797 .iter()
7798 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7799 {
7800 let mut inlays = Vec::new();
7801 for (range, new_text) in &edits {
7802 let inlay = Inlay::edit_prediction(
7803 post_inc(&mut self.next_inlay_id),
7804 range.start,
7805 new_text.as_str(),
7806 );
7807 inlay_ids.push(inlay.id);
7808 inlays.push(inlay);
7809 }
7810
7811 self.splice_inlays(&[], inlays, cx);
7812 } else {
7813 let background_color = cx.theme().status().deleted_background;
7814 self.highlight_text::<EditPredictionHighlight>(
7815 edits.iter().map(|(range, _)| range.clone()).collect(),
7816 HighlightStyle {
7817 background_color: Some(background_color),
7818 ..Default::default()
7819 },
7820 cx,
7821 );
7822 }
7823 }
7824
7825 invalidation_row_range = edit_start_row..edit_end_row;
7826
7827 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7828 if provider.show_tab_accept_marker() {
7829 EditDisplayMode::TabAccept
7830 } else {
7831 EditDisplayMode::Inline
7832 }
7833 } else {
7834 EditDisplayMode::DiffPopover
7835 };
7836
7837 EditPrediction::Edit {
7838 edits,
7839 edit_preview: edit_prediction.edit_preview,
7840 display_mode,
7841 snapshot,
7842 }
7843 };
7844
7845 let invalidation_range = multibuffer
7846 .anchor_before(Point::new(invalidation_row_range.start, 0))
7847 ..multibuffer.anchor_after(Point::new(
7848 invalidation_row_range.end,
7849 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7850 ));
7851
7852 self.stale_edit_prediction_in_menu = None;
7853 self.active_edit_prediction = Some(EditPredictionState {
7854 inlay_ids,
7855 completion,
7856 completion_id: edit_prediction.id,
7857 invalidation_range,
7858 });
7859
7860 cx.notify();
7861
7862 Some(())
7863 }
7864
7865 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7866 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7867 }
7868
7869 fn clear_tasks(&mut self) {
7870 self.tasks.clear()
7871 }
7872
7873 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7874 if self.tasks.insert(key, value).is_some() {
7875 // This case should hopefully be rare, but just in case...
7876 log::error!(
7877 "multiple different run targets found on a single line, only the last target will be rendered"
7878 )
7879 }
7880 }
7881
7882 /// Get all display points of breakpoints that will be rendered within editor
7883 ///
7884 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7885 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7886 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7887 fn active_breakpoints(
7888 &self,
7889 range: Range<DisplayRow>,
7890 window: &mut Window,
7891 cx: &mut Context<Self>,
7892 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7893 let mut breakpoint_display_points = HashMap::default();
7894
7895 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7896 return breakpoint_display_points;
7897 };
7898
7899 let snapshot = self.snapshot(window, cx);
7900
7901 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7902 let Some(project) = self.project() else {
7903 return breakpoint_display_points;
7904 };
7905
7906 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7907 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7908
7909 for (buffer_snapshot, range, excerpt_id) in
7910 multi_buffer_snapshot.range_to_buffer_ranges(range)
7911 {
7912 let Some(buffer) = project
7913 .read(cx)
7914 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7915 else {
7916 continue;
7917 };
7918 let breakpoints = breakpoint_store.read(cx).breakpoints(
7919 &buffer,
7920 Some(
7921 buffer_snapshot.anchor_before(range.start)
7922 ..buffer_snapshot.anchor_after(range.end),
7923 ),
7924 buffer_snapshot,
7925 cx,
7926 );
7927 for (breakpoint, state) in breakpoints {
7928 let multi_buffer_anchor =
7929 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7930 let position = multi_buffer_anchor
7931 .to_point(multi_buffer_snapshot)
7932 .to_display_point(&snapshot);
7933
7934 breakpoint_display_points.insert(
7935 position.row(),
7936 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7937 );
7938 }
7939 }
7940
7941 breakpoint_display_points
7942 }
7943
7944 fn breakpoint_context_menu(
7945 &self,
7946 anchor: Anchor,
7947 window: &mut Window,
7948 cx: &mut Context<Self>,
7949 ) -> Entity<ui::ContextMenu> {
7950 let weak_editor = cx.weak_entity();
7951 let focus_handle = self.focus_handle(cx);
7952
7953 let row = self
7954 .buffer
7955 .read(cx)
7956 .snapshot(cx)
7957 .summary_for_anchor::<Point>(&anchor)
7958 .row;
7959
7960 let breakpoint = self
7961 .breakpoint_at_row(row, window, cx)
7962 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7963
7964 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7965 "Edit Log Breakpoint"
7966 } else {
7967 "Set Log Breakpoint"
7968 };
7969
7970 let condition_breakpoint_msg = if breakpoint
7971 .as_ref()
7972 .is_some_and(|bp| bp.1.condition.is_some())
7973 {
7974 "Edit Condition Breakpoint"
7975 } else {
7976 "Set Condition Breakpoint"
7977 };
7978
7979 let hit_condition_breakpoint_msg = if breakpoint
7980 .as_ref()
7981 .is_some_and(|bp| bp.1.hit_condition.is_some())
7982 {
7983 "Edit Hit Condition Breakpoint"
7984 } else {
7985 "Set Hit Condition Breakpoint"
7986 };
7987
7988 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7989 "Unset Breakpoint"
7990 } else {
7991 "Set Breakpoint"
7992 };
7993
7994 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7995
7996 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7997 BreakpointState::Enabled => Some("Disable"),
7998 BreakpointState::Disabled => Some("Enable"),
7999 });
8000
8001 let (anchor, breakpoint) =
8002 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8003
8004 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8005 menu.on_blur_subscription(Subscription::new(|| {}))
8006 .context(focus_handle)
8007 .when(run_to_cursor, |this| {
8008 let weak_editor = weak_editor.clone();
8009 this.entry("Run to cursor", None, move |window, cx| {
8010 weak_editor
8011 .update(cx, |editor, cx| {
8012 editor.change_selections(
8013 SelectionEffects::no_scroll(),
8014 window,
8015 cx,
8016 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8017 );
8018 })
8019 .ok();
8020
8021 window.dispatch_action(Box::new(RunToCursor), cx);
8022 })
8023 .separator()
8024 })
8025 .when_some(toggle_state_msg, |this, msg| {
8026 this.entry(msg, None, {
8027 let weak_editor = weak_editor.clone();
8028 let breakpoint = breakpoint.clone();
8029 move |_window, cx| {
8030 weak_editor
8031 .update(cx, |this, cx| {
8032 this.edit_breakpoint_at_anchor(
8033 anchor,
8034 breakpoint.as_ref().clone(),
8035 BreakpointEditAction::InvertState,
8036 cx,
8037 );
8038 })
8039 .log_err();
8040 }
8041 })
8042 })
8043 .entry(set_breakpoint_msg, None, {
8044 let weak_editor = weak_editor.clone();
8045 let breakpoint = breakpoint.clone();
8046 move |_window, cx| {
8047 weak_editor
8048 .update(cx, |this, cx| {
8049 this.edit_breakpoint_at_anchor(
8050 anchor,
8051 breakpoint.as_ref().clone(),
8052 BreakpointEditAction::Toggle,
8053 cx,
8054 );
8055 })
8056 .log_err();
8057 }
8058 })
8059 .entry(log_breakpoint_msg, None, {
8060 let breakpoint = breakpoint.clone();
8061 let weak_editor = weak_editor.clone();
8062 move |window, cx| {
8063 weak_editor
8064 .update(cx, |this, cx| {
8065 this.add_edit_breakpoint_block(
8066 anchor,
8067 breakpoint.as_ref(),
8068 BreakpointPromptEditAction::Log,
8069 window,
8070 cx,
8071 );
8072 })
8073 .log_err();
8074 }
8075 })
8076 .entry(condition_breakpoint_msg, None, {
8077 let breakpoint = breakpoint.clone();
8078 let weak_editor = weak_editor.clone();
8079 move |window, cx| {
8080 weak_editor
8081 .update(cx, |this, cx| {
8082 this.add_edit_breakpoint_block(
8083 anchor,
8084 breakpoint.as_ref(),
8085 BreakpointPromptEditAction::Condition,
8086 window,
8087 cx,
8088 );
8089 })
8090 .log_err();
8091 }
8092 })
8093 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8094 weak_editor
8095 .update(cx, |this, cx| {
8096 this.add_edit_breakpoint_block(
8097 anchor,
8098 breakpoint.as_ref(),
8099 BreakpointPromptEditAction::HitCondition,
8100 window,
8101 cx,
8102 );
8103 })
8104 .log_err();
8105 })
8106 })
8107 }
8108
8109 fn render_breakpoint(
8110 &self,
8111 position: Anchor,
8112 row: DisplayRow,
8113 breakpoint: &Breakpoint,
8114 state: Option<BreakpointSessionState>,
8115 cx: &mut Context<Self>,
8116 ) -> IconButton {
8117 let is_rejected = state.is_some_and(|s| !s.verified);
8118 // Is it a breakpoint that shows up when hovering over gutter?
8119 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8120 (false, false),
8121 |PhantomBreakpointIndicator {
8122 is_active,
8123 display_row,
8124 collides_with_existing_breakpoint,
8125 }| {
8126 (
8127 is_active && display_row == row,
8128 collides_with_existing_breakpoint,
8129 )
8130 },
8131 );
8132
8133 let (color, icon) = {
8134 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8135 (false, false) => ui::IconName::DebugBreakpoint,
8136 (true, false) => ui::IconName::DebugLogBreakpoint,
8137 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8138 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8139 };
8140
8141 let color = if is_phantom {
8142 Color::Hint
8143 } else if is_rejected {
8144 Color::Disabled
8145 } else {
8146 Color::Debugger
8147 };
8148
8149 (color, icon)
8150 };
8151
8152 let breakpoint = Arc::from(breakpoint.clone());
8153
8154 let alt_as_text = gpui::Keystroke {
8155 modifiers: Modifiers::secondary_key(),
8156 ..Default::default()
8157 };
8158 let primary_action_text = if breakpoint.is_disabled() {
8159 "Enable breakpoint"
8160 } else if is_phantom && !collides_with_existing {
8161 "Set breakpoint"
8162 } else {
8163 "Unset breakpoint"
8164 };
8165 let focus_handle = self.focus_handle.clone();
8166
8167 let meta = if is_rejected {
8168 SharedString::from("No executable code is associated with this line.")
8169 } else if collides_with_existing && !breakpoint.is_disabled() {
8170 SharedString::from(format!(
8171 "{alt_as_text}-click to disable,\nright-click for more options."
8172 ))
8173 } else {
8174 SharedString::from("Right-click for more options.")
8175 };
8176 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8177 .icon_size(IconSize::XSmall)
8178 .size(ui::ButtonSize::None)
8179 .when(is_rejected, |this| {
8180 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8181 })
8182 .icon_color(color)
8183 .style(ButtonStyle::Transparent)
8184 .on_click(cx.listener({
8185 move |editor, event: &ClickEvent, window, cx| {
8186 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8187 BreakpointEditAction::InvertState
8188 } else {
8189 BreakpointEditAction::Toggle
8190 };
8191
8192 window.focus(&editor.focus_handle(cx));
8193 editor.edit_breakpoint_at_anchor(
8194 position,
8195 breakpoint.as_ref().clone(),
8196 edit_action,
8197 cx,
8198 );
8199 }
8200 }))
8201 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8202 editor.set_breakpoint_context_menu(
8203 row,
8204 Some(position),
8205 event.position(),
8206 window,
8207 cx,
8208 );
8209 }))
8210 .tooltip(move |window, cx| {
8211 Tooltip::with_meta_in(
8212 primary_action_text,
8213 Some(&ToggleBreakpoint),
8214 meta.clone(),
8215 &focus_handle,
8216 window,
8217 cx,
8218 )
8219 })
8220 }
8221
8222 fn build_tasks_context(
8223 project: &Entity<Project>,
8224 buffer: &Entity<Buffer>,
8225 buffer_row: u32,
8226 tasks: &Arc<RunnableTasks>,
8227 cx: &mut Context<Self>,
8228 ) -> Task<Option<task::TaskContext>> {
8229 let position = Point::new(buffer_row, tasks.column);
8230 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8231 let location = Location {
8232 buffer: buffer.clone(),
8233 range: range_start..range_start,
8234 };
8235 // Fill in the environmental variables from the tree-sitter captures
8236 let mut captured_task_variables = TaskVariables::default();
8237 for (capture_name, value) in tasks.extra_variables.clone() {
8238 captured_task_variables.insert(
8239 task::VariableName::Custom(capture_name.into()),
8240 value.clone(),
8241 );
8242 }
8243 project.update(cx, |project, cx| {
8244 project.task_store().update(cx, |task_store, cx| {
8245 task_store.task_context_for_location(captured_task_variables, location, cx)
8246 })
8247 })
8248 }
8249
8250 pub fn spawn_nearest_task(
8251 &mut self,
8252 action: &SpawnNearestTask,
8253 window: &mut Window,
8254 cx: &mut Context<Self>,
8255 ) {
8256 let Some((workspace, _)) = self.workspace.clone() else {
8257 return;
8258 };
8259 let Some(project) = self.project.clone() else {
8260 return;
8261 };
8262
8263 // Try to find a closest, enclosing node using tree-sitter that has a task
8264 let Some((buffer, buffer_row, tasks)) = self
8265 .find_enclosing_node_task(cx)
8266 // Or find the task that's closest in row-distance.
8267 .or_else(|| self.find_closest_task(cx))
8268 else {
8269 return;
8270 };
8271
8272 let reveal_strategy = action.reveal;
8273 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8274 cx.spawn_in(window, async move |_, cx| {
8275 let context = task_context.await?;
8276 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8277
8278 let resolved = &mut resolved_task.resolved;
8279 resolved.reveal = reveal_strategy;
8280
8281 workspace
8282 .update_in(cx, |workspace, window, cx| {
8283 workspace.schedule_resolved_task(
8284 task_source_kind,
8285 resolved_task,
8286 false,
8287 window,
8288 cx,
8289 );
8290 })
8291 .ok()
8292 })
8293 .detach();
8294 }
8295
8296 fn find_closest_task(
8297 &mut self,
8298 cx: &mut Context<Self>,
8299 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8300 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8301
8302 let ((buffer_id, row), tasks) = self
8303 .tasks
8304 .iter()
8305 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8306
8307 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8308 let tasks = Arc::new(tasks.to_owned());
8309 Some((buffer, *row, tasks))
8310 }
8311
8312 fn find_enclosing_node_task(
8313 &mut self,
8314 cx: &mut Context<Self>,
8315 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8316 let snapshot = self.buffer.read(cx).snapshot(cx);
8317 let offset = self.selections.newest::<usize>(cx).head();
8318 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8319 let buffer_id = excerpt.buffer().remote_id();
8320
8321 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8322 let mut cursor = layer.node().walk();
8323
8324 while cursor.goto_first_child_for_byte(offset).is_some() {
8325 if cursor.node().end_byte() == offset {
8326 cursor.goto_next_sibling();
8327 }
8328 }
8329
8330 // Ascend to the smallest ancestor that contains the range and has a task.
8331 loop {
8332 let node = cursor.node();
8333 let node_range = node.byte_range();
8334 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8335
8336 // Check if this node contains our offset
8337 if node_range.start <= offset && node_range.end >= offset {
8338 // If it contains offset, check for task
8339 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8340 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8341 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8342 }
8343 }
8344
8345 if !cursor.goto_parent() {
8346 break;
8347 }
8348 }
8349 None
8350 }
8351
8352 fn render_run_indicator(
8353 &self,
8354 _style: &EditorStyle,
8355 is_active: bool,
8356 row: DisplayRow,
8357 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8358 cx: &mut Context<Self>,
8359 ) -> IconButton {
8360 let color = Color::Muted;
8361 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8362
8363 IconButton::new(
8364 ("run_indicator", row.0 as usize),
8365 ui::IconName::PlayOutlined,
8366 )
8367 .shape(ui::IconButtonShape::Square)
8368 .icon_size(IconSize::XSmall)
8369 .icon_color(color)
8370 .toggle_state(is_active)
8371 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8372 let quick_launch = match e {
8373 ClickEvent::Keyboard(_) => true,
8374 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8375 };
8376
8377 window.focus(&editor.focus_handle(cx));
8378 editor.toggle_code_actions(
8379 &ToggleCodeActions {
8380 deployed_from: Some(CodeActionSource::RunMenu(row)),
8381 quick_launch,
8382 },
8383 window,
8384 cx,
8385 );
8386 }))
8387 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8388 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8389 }))
8390 }
8391
8392 pub fn context_menu_visible(&self) -> bool {
8393 !self.edit_prediction_preview_is_active()
8394 && self
8395 .context_menu
8396 .borrow()
8397 .as_ref()
8398 .is_some_and(|menu| menu.visible())
8399 }
8400
8401 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8402 self.context_menu
8403 .borrow()
8404 .as_ref()
8405 .map(|menu| menu.origin())
8406 }
8407
8408 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8409 self.context_menu_options = Some(options);
8410 }
8411
8412 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8413 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8414
8415 fn render_edit_prediction_popover(
8416 &mut self,
8417 text_bounds: &Bounds<Pixels>,
8418 content_origin: gpui::Point<Pixels>,
8419 right_margin: Pixels,
8420 editor_snapshot: &EditorSnapshot,
8421 visible_row_range: Range<DisplayRow>,
8422 scroll_top: f32,
8423 scroll_bottom: f32,
8424 line_layouts: &[LineWithInvisibles],
8425 line_height: Pixels,
8426 scroll_pixel_position: gpui::Point<Pixels>,
8427 newest_selection_head: Option<DisplayPoint>,
8428 editor_width: Pixels,
8429 style: &EditorStyle,
8430 window: &mut Window,
8431 cx: &mut App,
8432 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8433 if self.mode().is_minimap() {
8434 return None;
8435 }
8436 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8437
8438 if self.edit_prediction_visible_in_cursor_popover(true) {
8439 return None;
8440 }
8441
8442 match &active_edit_prediction.completion {
8443 EditPrediction::Move { target, .. } => {
8444 let target_display_point = target.to_display_point(editor_snapshot);
8445
8446 if self.edit_prediction_requires_modifier() {
8447 if !self.edit_prediction_preview_is_active() {
8448 return None;
8449 }
8450
8451 self.render_edit_prediction_modifier_jump_popover(
8452 text_bounds,
8453 content_origin,
8454 visible_row_range,
8455 line_layouts,
8456 line_height,
8457 scroll_pixel_position,
8458 newest_selection_head,
8459 target_display_point,
8460 window,
8461 cx,
8462 )
8463 } else {
8464 self.render_edit_prediction_eager_jump_popover(
8465 text_bounds,
8466 content_origin,
8467 editor_snapshot,
8468 visible_row_range,
8469 scroll_top,
8470 scroll_bottom,
8471 line_height,
8472 scroll_pixel_position,
8473 target_display_point,
8474 editor_width,
8475 window,
8476 cx,
8477 )
8478 }
8479 }
8480 EditPrediction::Edit {
8481 display_mode: EditDisplayMode::Inline,
8482 ..
8483 } => None,
8484 EditPrediction::Edit {
8485 display_mode: EditDisplayMode::TabAccept,
8486 edits,
8487 ..
8488 } => {
8489 let range = &edits.first()?.0;
8490 let target_display_point = range.end.to_display_point(editor_snapshot);
8491
8492 self.render_edit_prediction_end_of_line_popover(
8493 "Accept",
8494 editor_snapshot,
8495 visible_row_range,
8496 target_display_point,
8497 line_height,
8498 scroll_pixel_position,
8499 content_origin,
8500 editor_width,
8501 window,
8502 cx,
8503 )
8504 }
8505 EditPrediction::Edit {
8506 edits,
8507 edit_preview,
8508 display_mode: EditDisplayMode::DiffPopover,
8509 snapshot,
8510 } => self.render_edit_prediction_diff_popover(
8511 text_bounds,
8512 content_origin,
8513 right_margin,
8514 editor_snapshot,
8515 visible_row_range,
8516 line_layouts,
8517 line_height,
8518 scroll_pixel_position,
8519 newest_selection_head,
8520 editor_width,
8521 style,
8522 edits,
8523 edit_preview,
8524 snapshot,
8525 window,
8526 cx,
8527 ),
8528 }
8529 }
8530
8531 fn render_edit_prediction_modifier_jump_popover(
8532 &mut self,
8533 text_bounds: &Bounds<Pixels>,
8534 content_origin: gpui::Point<Pixels>,
8535 visible_row_range: Range<DisplayRow>,
8536 line_layouts: &[LineWithInvisibles],
8537 line_height: Pixels,
8538 scroll_pixel_position: gpui::Point<Pixels>,
8539 newest_selection_head: Option<DisplayPoint>,
8540 target_display_point: DisplayPoint,
8541 window: &mut Window,
8542 cx: &mut App,
8543 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8544 let scrolled_content_origin =
8545 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8546
8547 const SCROLL_PADDING_Y: Pixels = px(12.);
8548
8549 if target_display_point.row() < visible_row_range.start {
8550 return self.render_edit_prediction_scroll_popover(
8551 |_| SCROLL_PADDING_Y,
8552 IconName::ArrowUp,
8553 visible_row_range,
8554 line_layouts,
8555 newest_selection_head,
8556 scrolled_content_origin,
8557 window,
8558 cx,
8559 );
8560 } else if target_display_point.row() >= visible_row_range.end {
8561 return self.render_edit_prediction_scroll_popover(
8562 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8563 IconName::ArrowDown,
8564 visible_row_range,
8565 line_layouts,
8566 newest_selection_head,
8567 scrolled_content_origin,
8568 window,
8569 cx,
8570 );
8571 }
8572
8573 const POLE_WIDTH: Pixels = px(2.);
8574
8575 let line_layout =
8576 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8577 let target_column = target_display_point.column() as usize;
8578
8579 let target_x = line_layout.x_for_index(target_column);
8580 let target_y =
8581 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8582
8583 let flag_on_right = target_x < text_bounds.size.width / 2.;
8584
8585 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8586 border_color.l += 0.001;
8587
8588 let mut element = v_flex()
8589 .items_end()
8590 .when(flag_on_right, |el| el.items_start())
8591 .child(if flag_on_right {
8592 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8593 .rounded_bl(px(0.))
8594 .rounded_tl(px(0.))
8595 .border_l_2()
8596 .border_color(border_color)
8597 } else {
8598 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8599 .rounded_br(px(0.))
8600 .rounded_tr(px(0.))
8601 .border_r_2()
8602 .border_color(border_color)
8603 })
8604 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8605 .into_any();
8606
8607 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8608
8609 let mut origin = scrolled_content_origin + point(target_x, target_y)
8610 - point(
8611 if flag_on_right {
8612 POLE_WIDTH
8613 } else {
8614 size.width - POLE_WIDTH
8615 },
8616 size.height - line_height,
8617 );
8618
8619 origin.x = origin.x.max(content_origin.x);
8620
8621 element.prepaint_at(origin, window, cx);
8622
8623 Some((element, origin))
8624 }
8625
8626 fn render_edit_prediction_scroll_popover(
8627 &mut self,
8628 to_y: impl Fn(Size<Pixels>) -> Pixels,
8629 scroll_icon: IconName,
8630 visible_row_range: Range<DisplayRow>,
8631 line_layouts: &[LineWithInvisibles],
8632 newest_selection_head: Option<DisplayPoint>,
8633 scrolled_content_origin: gpui::Point<Pixels>,
8634 window: &mut Window,
8635 cx: &mut App,
8636 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8637 let mut element = self
8638 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8639 .into_any();
8640
8641 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8642
8643 let cursor = newest_selection_head?;
8644 let cursor_row_layout =
8645 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8646 let cursor_column = cursor.column() as usize;
8647
8648 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8649
8650 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8651
8652 element.prepaint_at(origin, window, cx);
8653 Some((element, origin))
8654 }
8655
8656 fn render_edit_prediction_eager_jump_popover(
8657 &mut self,
8658 text_bounds: &Bounds<Pixels>,
8659 content_origin: gpui::Point<Pixels>,
8660 editor_snapshot: &EditorSnapshot,
8661 visible_row_range: Range<DisplayRow>,
8662 scroll_top: f32,
8663 scroll_bottom: f32,
8664 line_height: Pixels,
8665 scroll_pixel_position: gpui::Point<Pixels>,
8666 target_display_point: DisplayPoint,
8667 editor_width: Pixels,
8668 window: &mut Window,
8669 cx: &mut App,
8670 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8671 if target_display_point.row().as_f32() < scroll_top {
8672 let mut element = self
8673 .render_edit_prediction_line_popover(
8674 "Jump to Edit",
8675 Some(IconName::ArrowUp),
8676 window,
8677 cx,
8678 )?
8679 .into_any();
8680
8681 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8682 let offset = point(
8683 (text_bounds.size.width - size.width) / 2.,
8684 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8685 );
8686
8687 let origin = text_bounds.origin + offset;
8688 element.prepaint_at(origin, window, cx);
8689 Some((element, origin))
8690 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8691 let mut element = self
8692 .render_edit_prediction_line_popover(
8693 "Jump to Edit",
8694 Some(IconName::ArrowDown),
8695 window,
8696 cx,
8697 )?
8698 .into_any();
8699
8700 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8701 let offset = point(
8702 (text_bounds.size.width - size.width) / 2.,
8703 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8704 );
8705
8706 let origin = text_bounds.origin + offset;
8707 element.prepaint_at(origin, window, cx);
8708 Some((element, origin))
8709 } else {
8710 self.render_edit_prediction_end_of_line_popover(
8711 "Jump to Edit",
8712 editor_snapshot,
8713 visible_row_range,
8714 target_display_point,
8715 line_height,
8716 scroll_pixel_position,
8717 content_origin,
8718 editor_width,
8719 window,
8720 cx,
8721 )
8722 }
8723 }
8724
8725 fn render_edit_prediction_end_of_line_popover(
8726 self: &mut Editor,
8727 label: &'static str,
8728 editor_snapshot: &EditorSnapshot,
8729 visible_row_range: Range<DisplayRow>,
8730 target_display_point: DisplayPoint,
8731 line_height: Pixels,
8732 scroll_pixel_position: gpui::Point<Pixels>,
8733 content_origin: gpui::Point<Pixels>,
8734 editor_width: Pixels,
8735 window: &mut Window,
8736 cx: &mut App,
8737 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8738 let target_line_end = DisplayPoint::new(
8739 target_display_point.row(),
8740 editor_snapshot.line_len(target_display_point.row()),
8741 );
8742
8743 let mut element = self
8744 .render_edit_prediction_line_popover(label, None, window, cx)?
8745 .into_any();
8746
8747 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8748
8749 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8750
8751 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8752 let mut origin = start_point
8753 + line_origin
8754 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8755 origin.x = origin.x.max(content_origin.x);
8756
8757 let max_x = content_origin.x + editor_width - size.width;
8758
8759 if origin.x > max_x {
8760 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8761
8762 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8763 origin.y += offset;
8764 IconName::ArrowUp
8765 } else {
8766 origin.y -= offset;
8767 IconName::ArrowDown
8768 };
8769
8770 element = self
8771 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8772 .into_any();
8773
8774 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8775
8776 origin.x = content_origin.x + editor_width - size.width - px(2.);
8777 }
8778
8779 element.prepaint_at(origin, window, cx);
8780 Some((element, origin))
8781 }
8782
8783 fn render_edit_prediction_diff_popover(
8784 self: &Editor,
8785 text_bounds: &Bounds<Pixels>,
8786 content_origin: gpui::Point<Pixels>,
8787 right_margin: Pixels,
8788 editor_snapshot: &EditorSnapshot,
8789 visible_row_range: Range<DisplayRow>,
8790 line_layouts: &[LineWithInvisibles],
8791 line_height: Pixels,
8792 scroll_pixel_position: gpui::Point<Pixels>,
8793 newest_selection_head: Option<DisplayPoint>,
8794 editor_width: Pixels,
8795 style: &EditorStyle,
8796 edits: &Vec<(Range<Anchor>, String)>,
8797 edit_preview: &Option<language::EditPreview>,
8798 snapshot: &language::BufferSnapshot,
8799 window: &mut Window,
8800 cx: &mut App,
8801 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8802 let edit_start = edits
8803 .first()
8804 .unwrap()
8805 .0
8806 .start
8807 .to_display_point(editor_snapshot);
8808 let edit_end = edits
8809 .last()
8810 .unwrap()
8811 .0
8812 .end
8813 .to_display_point(editor_snapshot);
8814
8815 let is_visible = visible_row_range.contains(&edit_start.row())
8816 || visible_row_range.contains(&edit_end.row());
8817 if !is_visible {
8818 return None;
8819 }
8820
8821 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8822 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8823 } else {
8824 // Fallback for providers without edit_preview
8825 crate::edit_prediction_fallback_text(edits, cx)
8826 };
8827
8828 let styled_text = highlighted_edits.to_styled_text(&style.text);
8829 let line_count = highlighted_edits.text.lines().count();
8830
8831 const BORDER_WIDTH: Pixels = px(1.);
8832
8833 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8834 let has_keybind = keybind.is_some();
8835
8836 let mut element = h_flex()
8837 .items_start()
8838 .child(
8839 h_flex()
8840 .bg(cx.theme().colors().editor_background)
8841 .border(BORDER_WIDTH)
8842 .shadow_xs()
8843 .border_color(cx.theme().colors().border)
8844 .rounded_l_lg()
8845 .when(line_count > 1, |el| el.rounded_br_lg())
8846 .pr_1()
8847 .child(styled_text),
8848 )
8849 .child(
8850 h_flex()
8851 .h(line_height + BORDER_WIDTH * 2.)
8852 .px_1p5()
8853 .gap_1()
8854 // Workaround: For some reason, there's a gap if we don't do this
8855 .ml(-BORDER_WIDTH)
8856 .shadow(vec![gpui::BoxShadow {
8857 color: gpui::black().opacity(0.05),
8858 offset: point(px(1.), px(1.)),
8859 blur_radius: px(2.),
8860 spread_radius: px(0.),
8861 }])
8862 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8863 .border(BORDER_WIDTH)
8864 .border_color(cx.theme().colors().border)
8865 .rounded_r_lg()
8866 .id("edit_prediction_diff_popover_keybind")
8867 .when(!has_keybind, |el| {
8868 let status_colors = cx.theme().status();
8869
8870 el.bg(status_colors.error_background)
8871 .border_color(status_colors.error.opacity(0.6))
8872 .child(Icon::new(IconName::Info).color(Color::Error))
8873 .cursor_default()
8874 .hoverable_tooltip(move |_window, cx| {
8875 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8876 })
8877 })
8878 .children(keybind),
8879 )
8880 .into_any();
8881
8882 let longest_row =
8883 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8884 let longest_line_width = if visible_row_range.contains(&longest_row) {
8885 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8886 } else {
8887 layout_line(
8888 longest_row,
8889 editor_snapshot,
8890 style,
8891 editor_width,
8892 |_| false,
8893 window,
8894 cx,
8895 )
8896 .width
8897 };
8898
8899 let viewport_bounds =
8900 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8901 right: -right_margin,
8902 ..Default::default()
8903 });
8904
8905 let x_after_longest =
8906 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8907 - scroll_pixel_position.x;
8908
8909 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8910
8911 // Fully visible if it can be displayed within the window (allow overlapping other
8912 // panes). However, this is only allowed if the popover starts within text_bounds.
8913 let can_position_to_the_right = x_after_longest < text_bounds.right()
8914 && x_after_longest + element_bounds.width < viewport_bounds.right();
8915
8916 let mut origin = if can_position_to_the_right {
8917 point(
8918 x_after_longest,
8919 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8920 - scroll_pixel_position.y,
8921 )
8922 } else {
8923 let cursor_row = newest_selection_head.map(|head| head.row());
8924 let above_edit = edit_start
8925 .row()
8926 .0
8927 .checked_sub(line_count as u32)
8928 .map(DisplayRow);
8929 let below_edit = Some(edit_end.row() + 1);
8930 let above_cursor =
8931 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8932 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8933
8934 // Place the edit popover adjacent to the edit if there is a location
8935 // available that is onscreen and does not obscure the cursor. Otherwise,
8936 // place it adjacent to the cursor.
8937 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8938 .into_iter()
8939 .flatten()
8940 .find(|&start_row| {
8941 let end_row = start_row + line_count as u32;
8942 visible_row_range.contains(&start_row)
8943 && visible_row_range.contains(&end_row)
8944 && cursor_row
8945 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
8946 })?;
8947
8948 content_origin
8949 + point(
8950 -scroll_pixel_position.x,
8951 row_target.as_f32() * line_height - scroll_pixel_position.y,
8952 )
8953 };
8954
8955 origin.x -= BORDER_WIDTH;
8956
8957 window.defer_draw(element, origin, 1);
8958
8959 // Do not return an element, since it will already be drawn due to defer_draw.
8960 None
8961 }
8962
8963 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8964 px(30.)
8965 }
8966
8967 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8968 if self.read_only(cx) {
8969 cx.theme().players().read_only()
8970 } else {
8971 self.style.as_ref().unwrap().local_player
8972 }
8973 }
8974
8975 fn render_edit_prediction_accept_keybind(
8976 &self,
8977 window: &mut Window,
8978 cx: &App,
8979 ) -> Option<AnyElement> {
8980 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8981 let accept_keystroke = accept_binding.keystroke()?;
8982
8983 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8984
8985 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8986 Color::Accent
8987 } else {
8988 Color::Muted
8989 };
8990
8991 h_flex()
8992 .px_0p5()
8993 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8994 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8995 .text_size(TextSize::XSmall.rems(cx))
8996 .child(h_flex().children(ui::render_modifiers(
8997 &accept_keystroke.modifiers,
8998 PlatformStyle::platform(),
8999 Some(modifiers_color),
9000 Some(IconSize::XSmall.rems().into()),
9001 true,
9002 )))
9003 .when(is_platform_style_mac, |parent| {
9004 parent.child(accept_keystroke.key.clone())
9005 })
9006 .when(!is_platform_style_mac, |parent| {
9007 parent.child(
9008 Key::new(
9009 util::capitalize(&accept_keystroke.key),
9010 Some(Color::Default),
9011 )
9012 .size(Some(IconSize::XSmall.rems().into())),
9013 )
9014 })
9015 .into_any()
9016 .into()
9017 }
9018
9019 fn render_edit_prediction_line_popover(
9020 &self,
9021 label: impl Into<SharedString>,
9022 icon: Option<IconName>,
9023 window: &mut Window,
9024 cx: &App,
9025 ) -> Option<Stateful<Div>> {
9026 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9027
9028 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9029 let has_keybind = keybind.is_some();
9030
9031 let result = h_flex()
9032 .id("ep-line-popover")
9033 .py_0p5()
9034 .pl_1()
9035 .pr(padding_right)
9036 .gap_1()
9037 .rounded_md()
9038 .border_1()
9039 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9040 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9041 .shadow_xs()
9042 .when(!has_keybind, |el| {
9043 let status_colors = cx.theme().status();
9044
9045 el.bg(status_colors.error_background)
9046 .border_color(status_colors.error.opacity(0.6))
9047 .pl_2()
9048 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9049 .cursor_default()
9050 .hoverable_tooltip(move |_window, cx| {
9051 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9052 })
9053 })
9054 .children(keybind)
9055 .child(
9056 Label::new(label)
9057 .size(LabelSize::Small)
9058 .when(!has_keybind, |el| {
9059 el.color(cx.theme().status().error.into()).strikethrough()
9060 }),
9061 )
9062 .when(!has_keybind, |el| {
9063 el.child(
9064 h_flex().ml_1().child(
9065 Icon::new(IconName::Info)
9066 .size(IconSize::Small)
9067 .color(cx.theme().status().error.into()),
9068 ),
9069 )
9070 })
9071 .when_some(icon, |element, icon| {
9072 element.child(
9073 div()
9074 .mt(px(1.5))
9075 .child(Icon::new(icon).size(IconSize::Small)),
9076 )
9077 });
9078
9079 Some(result)
9080 }
9081
9082 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9083 let accent_color = cx.theme().colors().text_accent;
9084 let editor_bg_color = cx.theme().colors().editor_background;
9085 editor_bg_color.blend(accent_color.opacity(0.1))
9086 }
9087
9088 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9089 let accent_color = cx.theme().colors().text_accent;
9090 let editor_bg_color = cx.theme().colors().editor_background;
9091 editor_bg_color.blend(accent_color.opacity(0.6))
9092 }
9093 fn get_prediction_provider_icon_name(
9094 provider: &Option<RegisteredEditPredictionProvider>,
9095 ) -> IconName {
9096 match provider {
9097 Some(provider) => match provider.provider.name() {
9098 "copilot" => IconName::Copilot,
9099 "supermaven" => IconName::Supermaven,
9100 _ => IconName::ZedPredict,
9101 },
9102 None => IconName::ZedPredict,
9103 }
9104 }
9105
9106 fn render_edit_prediction_cursor_popover(
9107 &self,
9108 min_width: Pixels,
9109 max_width: Pixels,
9110 cursor_point: Point,
9111 style: &EditorStyle,
9112 accept_keystroke: Option<&gpui::Keystroke>,
9113 _window: &Window,
9114 cx: &mut Context<Editor>,
9115 ) -> Option<AnyElement> {
9116 let provider = self.edit_prediction_provider.as_ref()?;
9117 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9118
9119 if provider.provider.needs_terms_acceptance(cx) {
9120 return Some(
9121 h_flex()
9122 .min_w(min_width)
9123 .flex_1()
9124 .px_2()
9125 .py_1()
9126 .gap_3()
9127 .elevation_2(cx)
9128 .hover(|style| style.bg(cx.theme().colors().element_hover))
9129 .id("accept-terms")
9130 .cursor_pointer()
9131 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9132 .on_click(cx.listener(|this, _event, window, cx| {
9133 cx.stop_propagation();
9134 this.report_editor_event(ReportEditorEvent::ZetaTosClicked, None, cx);
9135 window.dispatch_action(
9136 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9137 cx,
9138 );
9139 }))
9140 .child(
9141 h_flex()
9142 .flex_1()
9143 .gap_2()
9144 .child(Icon::new(provider_icon))
9145 .child(Label::new("Accept Terms of Service"))
9146 .child(div().w_full())
9147 .child(
9148 Icon::new(IconName::ArrowUpRight)
9149 .color(Color::Muted)
9150 .size(IconSize::Small),
9151 )
9152 .into_any_element(),
9153 )
9154 .into_any(),
9155 );
9156 }
9157
9158 let is_refreshing = provider.provider.is_refreshing(cx);
9159
9160 fn pending_completion_container(icon: IconName) -> Div {
9161 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9162 }
9163
9164 let completion = match &self.active_edit_prediction {
9165 Some(prediction) => {
9166 if !self.has_visible_completions_menu() {
9167 const RADIUS: Pixels = px(6.);
9168 const BORDER_WIDTH: Pixels = px(1.);
9169
9170 return Some(
9171 h_flex()
9172 .elevation_2(cx)
9173 .border(BORDER_WIDTH)
9174 .border_color(cx.theme().colors().border)
9175 .when(accept_keystroke.is_none(), |el| {
9176 el.border_color(cx.theme().status().error)
9177 })
9178 .rounded(RADIUS)
9179 .rounded_tl(px(0.))
9180 .overflow_hidden()
9181 .child(div().px_1p5().child(match &prediction.completion {
9182 EditPrediction::Move { target, snapshot } => {
9183 use text::ToPoint as _;
9184 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9185 {
9186 Icon::new(IconName::ZedPredictDown)
9187 } else {
9188 Icon::new(IconName::ZedPredictUp)
9189 }
9190 }
9191 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9192 }))
9193 .child(
9194 h_flex()
9195 .gap_1()
9196 .py_1()
9197 .px_2()
9198 .rounded_r(RADIUS - BORDER_WIDTH)
9199 .border_l_1()
9200 .border_color(cx.theme().colors().border)
9201 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9202 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9203 el.child(
9204 Label::new("Hold")
9205 .size(LabelSize::Small)
9206 .when(accept_keystroke.is_none(), |el| {
9207 el.strikethrough()
9208 })
9209 .line_height_style(LineHeightStyle::UiLabel),
9210 )
9211 })
9212 .id("edit_prediction_cursor_popover_keybind")
9213 .when(accept_keystroke.is_none(), |el| {
9214 let status_colors = cx.theme().status();
9215
9216 el.bg(status_colors.error_background)
9217 .border_color(status_colors.error.opacity(0.6))
9218 .child(Icon::new(IconName::Info).color(Color::Error))
9219 .cursor_default()
9220 .hoverable_tooltip(move |_window, cx| {
9221 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9222 .into()
9223 })
9224 })
9225 .when_some(
9226 accept_keystroke.as_ref(),
9227 |el, accept_keystroke| {
9228 el.child(h_flex().children(ui::render_modifiers(
9229 &accept_keystroke.modifiers,
9230 PlatformStyle::platform(),
9231 Some(Color::Default),
9232 Some(IconSize::XSmall.rems().into()),
9233 false,
9234 )))
9235 },
9236 ),
9237 )
9238 .into_any(),
9239 );
9240 }
9241
9242 self.render_edit_prediction_cursor_popover_preview(
9243 prediction,
9244 cursor_point,
9245 style,
9246 cx,
9247 )?
9248 }
9249
9250 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9251 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9252 stale_completion,
9253 cursor_point,
9254 style,
9255 cx,
9256 )?,
9257
9258 None => pending_completion_container(provider_icon)
9259 .child(Label::new("...").size(LabelSize::Small)),
9260 },
9261
9262 None => pending_completion_container(provider_icon)
9263 .child(Label::new("...").size(LabelSize::Small)),
9264 };
9265
9266 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9267 completion
9268 .with_animation(
9269 "loading-completion",
9270 Animation::new(Duration::from_secs(2))
9271 .repeat()
9272 .with_easing(pulsating_between(0.4, 0.8)),
9273 |label, delta| label.opacity(delta),
9274 )
9275 .into_any_element()
9276 } else {
9277 completion.into_any_element()
9278 };
9279
9280 let has_completion = self.active_edit_prediction.is_some();
9281
9282 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9283 Some(
9284 h_flex()
9285 .min_w(min_width)
9286 .max_w(max_width)
9287 .flex_1()
9288 .elevation_2(cx)
9289 .border_color(cx.theme().colors().border)
9290 .child(
9291 div()
9292 .flex_1()
9293 .py_1()
9294 .px_2()
9295 .overflow_hidden()
9296 .child(completion),
9297 )
9298 .when_some(accept_keystroke, |el, accept_keystroke| {
9299 if !accept_keystroke.modifiers.modified() {
9300 return el;
9301 }
9302
9303 el.child(
9304 h_flex()
9305 .h_full()
9306 .border_l_1()
9307 .rounded_r_lg()
9308 .border_color(cx.theme().colors().border)
9309 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9310 .gap_1()
9311 .py_1()
9312 .px_2()
9313 .child(
9314 h_flex()
9315 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9316 .when(is_platform_style_mac, |parent| parent.gap_1())
9317 .child(h_flex().children(ui::render_modifiers(
9318 &accept_keystroke.modifiers,
9319 PlatformStyle::platform(),
9320 Some(if !has_completion {
9321 Color::Muted
9322 } else {
9323 Color::Default
9324 }),
9325 None,
9326 false,
9327 ))),
9328 )
9329 .child(Label::new("Preview").into_any_element())
9330 .opacity(if has_completion { 1.0 } else { 0.4 }),
9331 )
9332 })
9333 .into_any(),
9334 )
9335 }
9336
9337 fn render_edit_prediction_cursor_popover_preview(
9338 &self,
9339 completion: &EditPredictionState,
9340 cursor_point: Point,
9341 style: &EditorStyle,
9342 cx: &mut Context<Editor>,
9343 ) -> Option<Div> {
9344 use text::ToPoint as _;
9345
9346 fn render_relative_row_jump(
9347 prefix: impl Into<String>,
9348 current_row: u32,
9349 target_row: u32,
9350 ) -> Div {
9351 let (row_diff, arrow) = if target_row < current_row {
9352 (current_row - target_row, IconName::ArrowUp)
9353 } else {
9354 (target_row - current_row, IconName::ArrowDown)
9355 };
9356
9357 h_flex()
9358 .child(
9359 Label::new(format!("{}{}", prefix.into(), row_diff))
9360 .color(Color::Muted)
9361 .size(LabelSize::Small),
9362 )
9363 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9364 }
9365
9366 let supports_jump = self
9367 .edit_prediction_provider
9368 .as_ref()
9369 .map(|provider| provider.provider.supports_jump_to_edit())
9370 .unwrap_or(true);
9371
9372 match &completion.completion {
9373 EditPrediction::Move {
9374 target, snapshot, ..
9375 } => {
9376 if !supports_jump {
9377 return None;
9378 }
9379
9380 Some(
9381 h_flex()
9382 .px_2()
9383 .gap_2()
9384 .flex_1()
9385 .child(
9386 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9387 Icon::new(IconName::ZedPredictDown)
9388 } else {
9389 Icon::new(IconName::ZedPredictUp)
9390 },
9391 )
9392 .child(Label::new("Jump to Edit")),
9393 )
9394 }
9395
9396 EditPrediction::Edit {
9397 edits,
9398 edit_preview,
9399 snapshot,
9400 display_mode: _,
9401 } => {
9402 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9403
9404 let (highlighted_edits, has_more_lines) =
9405 if let Some(edit_preview) = edit_preview.as_ref() {
9406 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9407 .first_line_preview()
9408 } else {
9409 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9410 };
9411
9412 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9413 .with_default_highlights(&style.text, highlighted_edits.highlights);
9414
9415 let preview = h_flex()
9416 .gap_1()
9417 .min_w_16()
9418 .child(styled_text)
9419 .when(has_more_lines, |parent| parent.child("…"));
9420
9421 let left = if supports_jump && first_edit_row != cursor_point.row {
9422 render_relative_row_jump("", cursor_point.row, first_edit_row)
9423 .into_any_element()
9424 } else {
9425 let icon_name =
9426 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9427 Icon::new(icon_name).into_any_element()
9428 };
9429
9430 Some(
9431 h_flex()
9432 .h_full()
9433 .flex_1()
9434 .gap_2()
9435 .pr_1()
9436 .overflow_x_hidden()
9437 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9438 .child(left)
9439 .child(preview),
9440 )
9441 }
9442 }
9443 }
9444
9445 pub fn render_context_menu(
9446 &self,
9447 style: &EditorStyle,
9448 max_height_in_lines: u32,
9449 window: &mut Window,
9450 cx: &mut Context<Editor>,
9451 ) -> Option<AnyElement> {
9452 let menu = self.context_menu.borrow();
9453 let menu = menu.as_ref()?;
9454 if !menu.visible() {
9455 return None;
9456 };
9457 Some(menu.render(style, max_height_in_lines, window, cx))
9458 }
9459
9460 fn render_context_menu_aside(
9461 &mut self,
9462 max_size: Size<Pixels>,
9463 window: &mut Window,
9464 cx: &mut Context<Editor>,
9465 ) -> Option<AnyElement> {
9466 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9467 if menu.visible() {
9468 menu.render_aside(max_size, window, cx)
9469 } else {
9470 None
9471 }
9472 })
9473 }
9474
9475 fn hide_context_menu(
9476 &mut self,
9477 window: &mut Window,
9478 cx: &mut Context<Self>,
9479 ) -> Option<CodeContextMenu> {
9480 cx.notify();
9481 self.completion_tasks.clear();
9482 let context_menu = self.context_menu.borrow_mut().take();
9483 self.stale_edit_prediction_in_menu.take();
9484 self.update_visible_edit_prediction(window, cx);
9485 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9486 && let Some(completion_provider) = &self.completion_provider
9487 {
9488 completion_provider.selection_changed(None, window, cx);
9489 }
9490 context_menu
9491 }
9492
9493 fn show_snippet_choices(
9494 &mut self,
9495 choices: &Vec<String>,
9496 selection: Range<Anchor>,
9497 cx: &mut Context<Self>,
9498 ) {
9499 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9500 (Some(a), Some(b)) if a == b => a,
9501 _ => {
9502 log::error!("expected anchor range to have matching buffer IDs");
9503 return;
9504 }
9505 };
9506 let multi_buffer = self.buffer().read(cx);
9507 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9508 return;
9509 };
9510
9511 let id = post_inc(&mut self.next_completion_id);
9512 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9513 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9514 CompletionsMenu::new_snippet_choices(
9515 id,
9516 true,
9517 choices,
9518 selection,
9519 buffer,
9520 snippet_sort_order,
9521 ),
9522 ));
9523 }
9524
9525 pub fn insert_snippet(
9526 &mut self,
9527 insertion_ranges: &[Range<usize>],
9528 snippet: Snippet,
9529 window: &mut Window,
9530 cx: &mut Context<Self>,
9531 ) -> Result<()> {
9532 struct Tabstop<T> {
9533 is_end_tabstop: bool,
9534 ranges: Vec<Range<T>>,
9535 choices: Option<Vec<String>>,
9536 }
9537
9538 let tabstops = self.buffer.update(cx, |buffer, cx| {
9539 let snippet_text: Arc<str> = snippet.text.clone().into();
9540 let edits = insertion_ranges
9541 .iter()
9542 .cloned()
9543 .map(|range| (range, snippet_text.clone()));
9544 let autoindent_mode = AutoindentMode::Block {
9545 original_indent_columns: Vec::new(),
9546 };
9547 buffer.edit(edits, Some(autoindent_mode), cx);
9548
9549 let snapshot = &*buffer.read(cx);
9550 let snippet = &snippet;
9551 snippet
9552 .tabstops
9553 .iter()
9554 .map(|tabstop| {
9555 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9556 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9557 });
9558 let mut tabstop_ranges = tabstop
9559 .ranges
9560 .iter()
9561 .flat_map(|tabstop_range| {
9562 let mut delta = 0_isize;
9563 insertion_ranges.iter().map(move |insertion_range| {
9564 let insertion_start = insertion_range.start as isize + delta;
9565 delta +=
9566 snippet.text.len() as isize - insertion_range.len() as isize;
9567
9568 let start = ((insertion_start + tabstop_range.start) as usize)
9569 .min(snapshot.len());
9570 let end = ((insertion_start + tabstop_range.end) as usize)
9571 .min(snapshot.len());
9572 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9573 })
9574 })
9575 .collect::<Vec<_>>();
9576 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9577
9578 Tabstop {
9579 is_end_tabstop,
9580 ranges: tabstop_ranges,
9581 choices: tabstop.choices.clone(),
9582 }
9583 })
9584 .collect::<Vec<_>>()
9585 });
9586 if let Some(tabstop) = tabstops.first() {
9587 self.change_selections(Default::default(), window, cx, |s| {
9588 // Reverse order so that the first range is the newest created selection.
9589 // Completions will use it and autoscroll will prioritize it.
9590 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9591 });
9592
9593 if let Some(choices) = &tabstop.choices
9594 && let Some(selection) = tabstop.ranges.first()
9595 {
9596 self.show_snippet_choices(choices, selection.clone(), cx)
9597 }
9598
9599 // If we're already at the last tabstop and it's at the end of the snippet,
9600 // we're done, we don't need to keep the state around.
9601 if !tabstop.is_end_tabstop {
9602 let choices = tabstops
9603 .iter()
9604 .map(|tabstop| tabstop.choices.clone())
9605 .collect();
9606
9607 let ranges = tabstops
9608 .into_iter()
9609 .map(|tabstop| tabstop.ranges)
9610 .collect::<Vec<_>>();
9611
9612 self.snippet_stack.push(SnippetState {
9613 active_index: 0,
9614 ranges,
9615 choices,
9616 });
9617 }
9618
9619 // Check whether the just-entered snippet ends with an auto-closable bracket.
9620 if self.autoclose_regions.is_empty() {
9621 let snapshot = self.buffer.read(cx).snapshot(cx);
9622 let mut all_selections = self.selections.all::<Point>(cx);
9623 for selection in &mut all_selections {
9624 let selection_head = selection.head();
9625 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9626 continue;
9627 };
9628
9629 let mut bracket_pair = None;
9630 let max_lookup_length = scope
9631 .brackets()
9632 .map(|(pair, _)| {
9633 pair.start
9634 .as_str()
9635 .chars()
9636 .count()
9637 .max(pair.end.as_str().chars().count())
9638 })
9639 .max();
9640 if let Some(max_lookup_length) = max_lookup_length {
9641 let next_text = snapshot
9642 .chars_at(selection_head)
9643 .take(max_lookup_length)
9644 .collect::<String>();
9645 let prev_text = snapshot
9646 .reversed_chars_at(selection_head)
9647 .take(max_lookup_length)
9648 .collect::<String>();
9649
9650 for (pair, enabled) in scope.brackets() {
9651 if enabled
9652 && pair.close
9653 && prev_text.starts_with(pair.start.as_str())
9654 && next_text.starts_with(pair.end.as_str())
9655 {
9656 bracket_pair = Some(pair.clone());
9657 break;
9658 }
9659 }
9660 }
9661
9662 if let Some(pair) = bracket_pair {
9663 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9664 let autoclose_enabled =
9665 self.use_autoclose && snapshot_settings.use_autoclose;
9666 if autoclose_enabled {
9667 let start = snapshot.anchor_after(selection_head);
9668 let end = snapshot.anchor_after(selection_head);
9669 self.autoclose_regions.push(AutocloseRegion {
9670 selection_id: selection.id,
9671 range: start..end,
9672 pair,
9673 });
9674 }
9675 }
9676 }
9677 }
9678 }
9679 Ok(())
9680 }
9681
9682 pub fn move_to_next_snippet_tabstop(
9683 &mut self,
9684 window: &mut Window,
9685 cx: &mut Context<Self>,
9686 ) -> bool {
9687 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9688 }
9689
9690 pub fn move_to_prev_snippet_tabstop(
9691 &mut self,
9692 window: &mut Window,
9693 cx: &mut Context<Self>,
9694 ) -> bool {
9695 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9696 }
9697
9698 pub fn move_to_snippet_tabstop(
9699 &mut self,
9700 bias: Bias,
9701 window: &mut Window,
9702 cx: &mut Context<Self>,
9703 ) -> bool {
9704 if let Some(mut snippet) = self.snippet_stack.pop() {
9705 match bias {
9706 Bias::Left => {
9707 if snippet.active_index > 0 {
9708 snippet.active_index -= 1;
9709 } else {
9710 self.snippet_stack.push(snippet);
9711 return false;
9712 }
9713 }
9714 Bias::Right => {
9715 if snippet.active_index + 1 < snippet.ranges.len() {
9716 snippet.active_index += 1;
9717 } else {
9718 self.snippet_stack.push(snippet);
9719 return false;
9720 }
9721 }
9722 }
9723 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9724 self.change_selections(Default::default(), window, cx, |s| {
9725 // Reverse order so that the first range is the newest created selection.
9726 // Completions will use it and autoscroll will prioritize it.
9727 s.select_ranges(current_ranges.iter().rev().cloned())
9728 });
9729
9730 if let Some(choices) = &snippet.choices[snippet.active_index]
9731 && let Some(selection) = current_ranges.first()
9732 {
9733 self.show_snippet_choices(choices, selection.clone(), cx);
9734 }
9735
9736 // If snippet state is not at the last tabstop, push it back on the stack
9737 if snippet.active_index + 1 < snippet.ranges.len() {
9738 self.snippet_stack.push(snippet);
9739 }
9740 return true;
9741 }
9742 }
9743
9744 false
9745 }
9746
9747 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9748 self.transact(window, cx, |this, window, cx| {
9749 this.select_all(&SelectAll, window, cx);
9750 this.insert("", window, cx);
9751 });
9752 }
9753
9754 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9755 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9756 self.transact(window, cx, |this, window, cx| {
9757 this.select_autoclose_pair(window, cx);
9758 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9759 if !this.linked_edit_ranges.is_empty() {
9760 let selections = this.selections.all::<MultiBufferPoint>(cx);
9761 let snapshot = this.buffer.read(cx).snapshot(cx);
9762
9763 for selection in selections.iter() {
9764 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9765 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9766 if selection_start.buffer_id != selection_end.buffer_id {
9767 continue;
9768 }
9769 if let Some(ranges) =
9770 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9771 {
9772 for (buffer, entries) in ranges {
9773 linked_ranges.entry(buffer).or_default().extend(entries);
9774 }
9775 }
9776 }
9777 }
9778
9779 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9780 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9781 for selection in &mut selections {
9782 if selection.is_empty() {
9783 let old_head = selection.head();
9784 let mut new_head =
9785 movement::left(&display_map, old_head.to_display_point(&display_map))
9786 .to_point(&display_map);
9787 if let Some((buffer, line_buffer_range)) = display_map
9788 .buffer_snapshot
9789 .buffer_line_for_row(MultiBufferRow(old_head.row))
9790 {
9791 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9792 let indent_len = match indent_size.kind {
9793 IndentKind::Space => {
9794 buffer.settings_at(line_buffer_range.start, cx).tab_size
9795 }
9796 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9797 };
9798 if old_head.column <= indent_size.len && old_head.column > 0 {
9799 let indent_len = indent_len.get();
9800 new_head = cmp::min(
9801 new_head,
9802 MultiBufferPoint::new(
9803 old_head.row,
9804 ((old_head.column - 1) / indent_len) * indent_len,
9805 ),
9806 );
9807 }
9808 }
9809
9810 selection.set_head(new_head, SelectionGoal::None);
9811 }
9812 }
9813
9814 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9815 this.insert("", window, cx);
9816 let empty_str: Arc<str> = Arc::from("");
9817 for (buffer, edits) in linked_ranges {
9818 let snapshot = buffer.read(cx).snapshot();
9819 use text::ToPoint as TP;
9820
9821 let edits = edits
9822 .into_iter()
9823 .map(|range| {
9824 let end_point = TP::to_point(&range.end, &snapshot);
9825 let mut start_point = TP::to_point(&range.start, &snapshot);
9826
9827 if end_point == start_point {
9828 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9829 .saturating_sub(1);
9830 start_point =
9831 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9832 };
9833
9834 (start_point..end_point, empty_str.clone())
9835 })
9836 .sorted_by_key(|(range, _)| range.start)
9837 .collect::<Vec<_>>();
9838 buffer.update(cx, |this, cx| {
9839 this.edit(edits, None, cx);
9840 })
9841 }
9842 this.refresh_edit_prediction(true, false, window, cx);
9843 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9844 });
9845 }
9846
9847 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9848 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9849 self.transact(window, cx, |this, window, cx| {
9850 this.change_selections(Default::default(), window, cx, |s| {
9851 s.move_with(|map, selection| {
9852 if selection.is_empty() {
9853 let cursor = movement::right(map, selection.head());
9854 selection.end = cursor;
9855 selection.reversed = true;
9856 selection.goal = SelectionGoal::None;
9857 }
9858 })
9859 });
9860 this.insert("", window, cx);
9861 this.refresh_edit_prediction(true, false, window, cx);
9862 });
9863 }
9864
9865 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9866 if self.mode.is_single_line() {
9867 cx.propagate();
9868 return;
9869 }
9870
9871 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9872 if self.move_to_prev_snippet_tabstop(window, cx) {
9873 return;
9874 }
9875 self.outdent(&Outdent, window, cx);
9876 }
9877
9878 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9879 if self.mode.is_single_line() {
9880 cx.propagate();
9881 return;
9882 }
9883
9884 if self.move_to_next_snippet_tabstop(window, cx) {
9885 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9886 return;
9887 }
9888 if self.read_only(cx) {
9889 return;
9890 }
9891 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9892 let mut selections = self.selections.all_adjusted(cx);
9893 let buffer = self.buffer.read(cx);
9894 let snapshot = buffer.snapshot(cx);
9895 let rows_iter = selections.iter().map(|s| s.head().row);
9896 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9897
9898 let has_some_cursor_in_whitespace = selections
9899 .iter()
9900 .filter(|selection| selection.is_empty())
9901 .any(|selection| {
9902 let cursor = selection.head();
9903 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9904 cursor.column < current_indent.len
9905 });
9906
9907 let mut edits = Vec::new();
9908 let mut prev_edited_row = 0;
9909 let mut row_delta = 0;
9910 for selection in &mut selections {
9911 if selection.start.row != prev_edited_row {
9912 row_delta = 0;
9913 }
9914 prev_edited_row = selection.end.row;
9915
9916 // If the selection is non-empty, then increase the indentation of the selected lines.
9917 if !selection.is_empty() {
9918 row_delta =
9919 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9920 continue;
9921 }
9922
9923 let cursor = selection.head();
9924 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9925 if let Some(suggested_indent) =
9926 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9927 {
9928 // Don't do anything if already at suggested indent
9929 // and there is any other cursor which is not
9930 if has_some_cursor_in_whitespace
9931 && cursor.column == current_indent.len
9932 && current_indent.len == suggested_indent.len
9933 {
9934 continue;
9935 }
9936
9937 // Adjust line and move cursor to suggested indent
9938 // if cursor is not at suggested indent
9939 if cursor.column < suggested_indent.len
9940 && cursor.column <= current_indent.len
9941 && current_indent.len <= suggested_indent.len
9942 {
9943 selection.start = Point::new(cursor.row, suggested_indent.len);
9944 selection.end = selection.start;
9945 if row_delta == 0 {
9946 edits.extend(Buffer::edit_for_indent_size_adjustment(
9947 cursor.row,
9948 current_indent,
9949 suggested_indent,
9950 ));
9951 row_delta = suggested_indent.len - current_indent.len;
9952 }
9953 continue;
9954 }
9955
9956 // If current indent is more than suggested indent
9957 // only move cursor to current indent and skip indent
9958 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9959 selection.start = Point::new(cursor.row, current_indent.len);
9960 selection.end = selection.start;
9961 continue;
9962 }
9963 }
9964
9965 // Otherwise, insert a hard or soft tab.
9966 let settings = buffer.language_settings_at(cursor, cx);
9967 let tab_size = if settings.hard_tabs {
9968 IndentSize::tab()
9969 } else {
9970 let tab_size = settings.tab_size.get();
9971 let indent_remainder = snapshot
9972 .text_for_range(Point::new(cursor.row, 0)..cursor)
9973 .flat_map(str::chars)
9974 .fold(row_delta % tab_size, |counter: u32, c| {
9975 if c == '\t' {
9976 0
9977 } else {
9978 (counter + 1) % tab_size
9979 }
9980 });
9981
9982 let chars_to_next_tab_stop = tab_size - indent_remainder;
9983 IndentSize::spaces(chars_to_next_tab_stop)
9984 };
9985 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9986 selection.end = selection.start;
9987 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9988 row_delta += tab_size.len;
9989 }
9990
9991 self.transact(window, cx, |this, window, cx| {
9992 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9993 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9994 this.refresh_edit_prediction(true, false, window, cx);
9995 });
9996 }
9997
9998 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9999 if self.read_only(cx) {
10000 return;
10001 }
10002 if self.mode.is_single_line() {
10003 cx.propagate();
10004 return;
10005 }
10006
10007 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10008 let mut selections = self.selections.all::<Point>(cx);
10009 let mut prev_edited_row = 0;
10010 let mut row_delta = 0;
10011 let mut edits = Vec::new();
10012 let buffer = self.buffer.read(cx);
10013 let snapshot = buffer.snapshot(cx);
10014 for selection in &mut selections {
10015 if selection.start.row != prev_edited_row {
10016 row_delta = 0;
10017 }
10018 prev_edited_row = selection.end.row;
10019
10020 row_delta =
10021 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10022 }
10023
10024 self.transact(window, cx, |this, window, cx| {
10025 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10026 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10027 });
10028 }
10029
10030 fn indent_selection(
10031 buffer: &MultiBuffer,
10032 snapshot: &MultiBufferSnapshot,
10033 selection: &mut Selection<Point>,
10034 edits: &mut Vec<(Range<Point>, String)>,
10035 delta_for_start_row: u32,
10036 cx: &App,
10037 ) -> u32 {
10038 let settings = buffer.language_settings_at(selection.start, cx);
10039 let tab_size = settings.tab_size.get();
10040 let indent_kind = if settings.hard_tabs {
10041 IndentKind::Tab
10042 } else {
10043 IndentKind::Space
10044 };
10045 let mut start_row = selection.start.row;
10046 let mut end_row = selection.end.row + 1;
10047
10048 // If a selection ends at the beginning of a line, don't indent
10049 // that last line.
10050 if selection.end.column == 0 && selection.end.row > selection.start.row {
10051 end_row -= 1;
10052 }
10053
10054 // Avoid re-indenting a row that has already been indented by a
10055 // previous selection, but still update this selection's column
10056 // to reflect that indentation.
10057 if delta_for_start_row > 0 {
10058 start_row += 1;
10059 selection.start.column += delta_for_start_row;
10060 if selection.end.row == selection.start.row {
10061 selection.end.column += delta_for_start_row;
10062 }
10063 }
10064
10065 let mut delta_for_end_row = 0;
10066 let has_multiple_rows = start_row + 1 != end_row;
10067 for row in start_row..end_row {
10068 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10069 let indent_delta = match (current_indent.kind, indent_kind) {
10070 (IndentKind::Space, IndentKind::Space) => {
10071 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10072 IndentSize::spaces(columns_to_next_tab_stop)
10073 }
10074 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10075 (_, IndentKind::Tab) => IndentSize::tab(),
10076 };
10077
10078 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10079 0
10080 } else {
10081 selection.start.column
10082 };
10083 let row_start = Point::new(row, start);
10084 edits.push((
10085 row_start..row_start,
10086 indent_delta.chars().collect::<String>(),
10087 ));
10088
10089 // Update this selection's endpoints to reflect the indentation.
10090 if row == selection.start.row {
10091 selection.start.column += indent_delta.len;
10092 }
10093 if row == selection.end.row {
10094 selection.end.column += indent_delta.len;
10095 delta_for_end_row = indent_delta.len;
10096 }
10097 }
10098
10099 if selection.start.row == selection.end.row {
10100 delta_for_start_row + delta_for_end_row
10101 } else {
10102 delta_for_end_row
10103 }
10104 }
10105
10106 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10107 if self.read_only(cx) {
10108 return;
10109 }
10110 if self.mode.is_single_line() {
10111 cx.propagate();
10112 return;
10113 }
10114
10115 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10116 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10117 let selections = self.selections.all::<Point>(cx);
10118 let mut deletion_ranges = Vec::new();
10119 let mut last_outdent = None;
10120 {
10121 let buffer = self.buffer.read(cx);
10122 let snapshot = buffer.snapshot(cx);
10123 for selection in &selections {
10124 let settings = buffer.language_settings_at(selection.start, cx);
10125 let tab_size = settings.tab_size.get();
10126 let mut rows = selection.spanned_rows(false, &display_map);
10127
10128 // Avoid re-outdenting a row that has already been outdented by a
10129 // previous selection.
10130 if let Some(last_row) = last_outdent
10131 && last_row == rows.start
10132 {
10133 rows.start = rows.start.next_row();
10134 }
10135 let has_multiple_rows = rows.len() > 1;
10136 for row in rows.iter_rows() {
10137 let indent_size = snapshot.indent_size_for_line(row);
10138 if indent_size.len > 0 {
10139 let deletion_len = match indent_size.kind {
10140 IndentKind::Space => {
10141 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10142 if columns_to_prev_tab_stop == 0 {
10143 tab_size
10144 } else {
10145 columns_to_prev_tab_stop
10146 }
10147 }
10148 IndentKind::Tab => 1,
10149 };
10150 let start = if has_multiple_rows
10151 || deletion_len > selection.start.column
10152 || indent_size.len < selection.start.column
10153 {
10154 0
10155 } else {
10156 selection.start.column - deletion_len
10157 };
10158 deletion_ranges.push(
10159 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10160 );
10161 last_outdent = Some(row);
10162 }
10163 }
10164 }
10165 }
10166
10167 self.transact(window, cx, |this, window, cx| {
10168 this.buffer.update(cx, |buffer, cx| {
10169 let empty_str: Arc<str> = Arc::default();
10170 buffer.edit(
10171 deletion_ranges
10172 .into_iter()
10173 .map(|range| (range, empty_str.clone())),
10174 None,
10175 cx,
10176 );
10177 });
10178 let selections = this.selections.all::<usize>(cx);
10179 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10180 });
10181 }
10182
10183 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10184 if self.read_only(cx) {
10185 return;
10186 }
10187 if self.mode.is_single_line() {
10188 cx.propagate();
10189 return;
10190 }
10191
10192 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10193 let selections = self
10194 .selections
10195 .all::<usize>(cx)
10196 .into_iter()
10197 .map(|s| s.range());
10198
10199 self.transact(window, cx, |this, window, cx| {
10200 this.buffer.update(cx, |buffer, cx| {
10201 buffer.autoindent_ranges(selections, cx);
10202 });
10203 let selections = this.selections.all::<usize>(cx);
10204 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10205 });
10206 }
10207
10208 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10209 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10210 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10211 let selections = self.selections.all::<Point>(cx);
10212
10213 let mut new_cursors = Vec::new();
10214 let mut edit_ranges = Vec::new();
10215 let mut selections = selections.iter().peekable();
10216 while let Some(selection) = selections.next() {
10217 let mut rows = selection.spanned_rows(false, &display_map);
10218 let goal_display_column = selection.head().to_display_point(&display_map).column();
10219
10220 // Accumulate contiguous regions of rows that we want to delete.
10221 while let Some(next_selection) = selections.peek() {
10222 let next_rows = next_selection.spanned_rows(false, &display_map);
10223 if next_rows.start <= rows.end {
10224 rows.end = next_rows.end;
10225 selections.next().unwrap();
10226 } else {
10227 break;
10228 }
10229 }
10230
10231 let buffer = &display_map.buffer_snapshot;
10232 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10233 let edit_end;
10234 let cursor_buffer_row;
10235 if buffer.max_point().row >= rows.end.0 {
10236 // If there's a line after the range, delete the \n from the end of the row range
10237 // and position the cursor on the next line.
10238 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10239 cursor_buffer_row = rows.end;
10240 } else {
10241 // If there isn't a line after the range, delete the \n from the line before the
10242 // start of the row range and position the cursor there.
10243 edit_start = edit_start.saturating_sub(1);
10244 edit_end = buffer.len();
10245 cursor_buffer_row = rows.start.previous_row();
10246 }
10247
10248 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10249 *cursor.column_mut() =
10250 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10251
10252 new_cursors.push((
10253 selection.id,
10254 buffer.anchor_after(cursor.to_point(&display_map)),
10255 ));
10256 edit_ranges.push(edit_start..edit_end);
10257 }
10258
10259 self.transact(window, cx, |this, window, cx| {
10260 let buffer = this.buffer.update(cx, |buffer, cx| {
10261 let empty_str: Arc<str> = Arc::default();
10262 buffer.edit(
10263 edit_ranges
10264 .into_iter()
10265 .map(|range| (range, empty_str.clone())),
10266 None,
10267 cx,
10268 );
10269 buffer.snapshot(cx)
10270 });
10271 let new_selections = new_cursors
10272 .into_iter()
10273 .map(|(id, cursor)| {
10274 let cursor = cursor.to_point(&buffer);
10275 Selection {
10276 id,
10277 start: cursor,
10278 end: cursor,
10279 reversed: false,
10280 goal: SelectionGoal::None,
10281 }
10282 })
10283 .collect();
10284
10285 this.change_selections(Default::default(), window, cx, |s| {
10286 s.select(new_selections);
10287 });
10288 });
10289 }
10290
10291 pub fn join_lines_impl(
10292 &mut self,
10293 insert_whitespace: bool,
10294 window: &mut Window,
10295 cx: &mut Context<Self>,
10296 ) {
10297 if self.read_only(cx) {
10298 return;
10299 }
10300 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10301 for selection in self.selections.all::<Point>(cx) {
10302 let start = MultiBufferRow(selection.start.row);
10303 // Treat single line selections as if they include the next line. Otherwise this action
10304 // would do nothing for single line selections individual cursors.
10305 let end = if selection.start.row == selection.end.row {
10306 MultiBufferRow(selection.start.row + 1)
10307 } else {
10308 MultiBufferRow(selection.end.row)
10309 };
10310
10311 if let Some(last_row_range) = row_ranges.last_mut()
10312 && start <= last_row_range.end
10313 {
10314 last_row_range.end = end;
10315 continue;
10316 }
10317 row_ranges.push(start..end);
10318 }
10319
10320 let snapshot = self.buffer.read(cx).snapshot(cx);
10321 let mut cursor_positions = Vec::new();
10322 for row_range in &row_ranges {
10323 let anchor = snapshot.anchor_before(Point::new(
10324 row_range.end.previous_row().0,
10325 snapshot.line_len(row_range.end.previous_row()),
10326 ));
10327 cursor_positions.push(anchor..anchor);
10328 }
10329
10330 self.transact(window, cx, |this, window, cx| {
10331 for row_range in row_ranges.into_iter().rev() {
10332 for row in row_range.iter_rows().rev() {
10333 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10334 let next_line_row = row.next_row();
10335 let indent = snapshot.indent_size_for_line(next_line_row);
10336 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10337
10338 let replace =
10339 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10340 " "
10341 } else {
10342 ""
10343 };
10344
10345 this.buffer.update(cx, |buffer, cx| {
10346 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10347 });
10348 }
10349 }
10350
10351 this.change_selections(Default::default(), window, cx, |s| {
10352 s.select_anchor_ranges(cursor_positions)
10353 });
10354 });
10355 }
10356
10357 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10358 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10359 self.join_lines_impl(true, window, cx);
10360 }
10361
10362 pub fn sort_lines_case_sensitive(
10363 &mut self,
10364 _: &SortLinesCaseSensitive,
10365 window: &mut Window,
10366 cx: &mut Context<Self>,
10367 ) {
10368 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10369 }
10370
10371 pub fn sort_lines_by_length(
10372 &mut self,
10373 _: &SortLinesByLength,
10374 window: &mut Window,
10375 cx: &mut Context<Self>,
10376 ) {
10377 self.manipulate_immutable_lines(window, cx, |lines| {
10378 lines.sort_by_key(|&line| line.chars().count())
10379 })
10380 }
10381
10382 pub fn sort_lines_case_insensitive(
10383 &mut self,
10384 _: &SortLinesCaseInsensitive,
10385 window: &mut Window,
10386 cx: &mut Context<Self>,
10387 ) {
10388 self.manipulate_immutable_lines(window, cx, |lines| {
10389 lines.sort_by_key(|line| line.to_lowercase())
10390 })
10391 }
10392
10393 pub fn unique_lines_case_insensitive(
10394 &mut self,
10395 _: &UniqueLinesCaseInsensitive,
10396 window: &mut Window,
10397 cx: &mut Context<Self>,
10398 ) {
10399 self.manipulate_immutable_lines(window, cx, |lines| {
10400 let mut seen = HashSet::default();
10401 lines.retain(|line| seen.insert(line.to_lowercase()));
10402 })
10403 }
10404
10405 pub fn unique_lines_case_sensitive(
10406 &mut self,
10407 _: &UniqueLinesCaseSensitive,
10408 window: &mut Window,
10409 cx: &mut Context<Self>,
10410 ) {
10411 self.manipulate_immutable_lines(window, cx, |lines| {
10412 let mut seen = HashSet::default();
10413 lines.retain(|line| seen.insert(*line));
10414 })
10415 }
10416
10417 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10418 let Some(project) = self.project.clone() else {
10419 return;
10420 };
10421 self.reload(project, window, cx)
10422 .detach_and_notify_err(window, cx);
10423 }
10424
10425 pub fn restore_file(
10426 &mut self,
10427 _: &::git::RestoreFile,
10428 window: &mut Window,
10429 cx: &mut Context<Self>,
10430 ) {
10431 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10432 let mut buffer_ids = HashSet::default();
10433 let snapshot = self.buffer().read(cx).snapshot(cx);
10434 for selection in self.selections.all::<usize>(cx) {
10435 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10436 }
10437
10438 let buffer = self.buffer().read(cx);
10439 let ranges = buffer_ids
10440 .into_iter()
10441 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10442 .collect::<Vec<_>>();
10443
10444 self.restore_hunks_in_ranges(ranges, window, cx);
10445 }
10446
10447 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10448 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10449 let selections = self
10450 .selections
10451 .all(cx)
10452 .into_iter()
10453 .map(|s| s.range())
10454 .collect();
10455 self.restore_hunks_in_ranges(selections, window, cx);
10456 }
10457
10458 pub fn restore_hunks_in_ranges(
10459 &mut self,
10460 ranges: Vec<Range<Point>>,
10461 window: &mut Window,
10462 cx: &mut Context<Editor>,
10463 ) {
10464 let mut revert_changes = HashMap::default();
10465 let chunk_by = self
10466 .snapshot(window, cx)
10467 .hunks_for_ranges(ranges)
10468 .into_iter()
10469 .chunk_by(|hunk| hunk.buffer_id);
10470 for (buffer_id, hunks) in &chunk_by {
10471 let hunks = hunks.collect::<Vec<_>>();
10472 for hunk in &hunks {
10473 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10474 }
10475 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10476 }
10477 drop(chunk_by);
10478 if !revert_changes.is_empty() {
10479 self.transact(window, cx, |editor, window, cx| {
10480 editor.restore(revert_changes, window, cx);
10481 });
10482 }
10483 }
10484
10485 pub fn open_active_item_in_terminal(
10486 &mut self,
10487 _: &OpenInTerminal,
10488 window: &mut Window,
10489 cx: &mut Context<Self>,
10490 ) {
10491 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10492 let project_path = buffer.read(cx).project_path(cx)?;
10493 let project = self.project()?.read(cx);
10494 let entry = project.entry_for_path(&project_path, cx)?;
10495 let parent = match &entry.canonical_path {
10496 Some(canonical_path) => canonical_path.to_path_buf(),
10497 None => project.absolute_path(&project_path, cx)?,
10498 }
10499 .parent()?
10500 .to_path_buf();
10501 Some(parent)
10502 }) {
10503 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10504 }
10505 }
10506
10507 fn set_breakpoint_context_menu(
10508 &mut self,
10509 display_row: DisplayRow,
10510 position: Option<Anchor>,
10511 clicked_point: gpui::Point<Pixels>,
10512 window: &mut Window,
10513 cx: &mut Context<Self>,
10514 ) {
10515 let source = self
10516 .buffer
10517 .read(cx)
10518 .snapshot(cx)
10519 .anchor_before(Point::new(display_row.0, 0u32));
10520
10521 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10522
10523 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10524 self,
10525 source,
10526 clicked_point,
10527 context_menu,
10528 window,
10529 cx,
10530 );
10531 }
10532
10533 fn add_edit_breakpoint_block(
10534 &mut self,
10535 anchor: Anchor,
10536 breakpoint: &Breakpoint,
10537 edit_action: BreakpointPromptEditAction,
10538 window: &mut Window,
10539 cx: &mut Context<Self>,
10540 ) {
10541 let weak_editor = cx.weak_entity();
10542 let bp_prompt = cx.new(|cx| {
10543 BreakpointPromptEditor::new(
10544 weak_editor,
10545 anchor,
10546 breakpoint.clone(),
10547 edit_action,
10548 window,
10549 cx,
10550 )
10551 });
10552
10553 let height = bp_prompt.update(cx, |this, cx| {
10554 this.prompt
10555 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10556 });
10557 let cloned_prompt = bp_prompt.clone();
10558 let blocks = vec![BlockProperties {
10559 style: BlockStyle::Sticky,
10560 placement: BlockPlacement::Above(anchor),
10561 height: Some(height),
10562 render: Arc::new(move |cx| {
10563 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10564 cloned_prompt.clone().into_any_element()
10565 }),
10566 priority: 0,
10567 }];
10568
10569 let focus_handle = bp_prompt.focus_handle(cx);
10570 window.focus(&focus_handle);
10571
10572 let block_ids = self.insert_blocks(blocks, None, cx);
10573 bp_prompt.update(cx, |prompt, _| {
10574 prompt.add_block_ids(block_ids);
10575 });
10576 }
10577
10578 pub(crate) fn breakpoint_at_row(
10579 &self,
10580 row: u32,
10581 window: &mut Window,
10582 cx: &mut Context<Self>,
10583 ) -> Option<(Anchor, Breakpoint)> {
10584 let snapshot = self.snapshot(window, cx);
10585 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10586
10587 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10588 }
10589
10590 pub(crate) fn breakpoint_at_anchor(
10591 &self,
10592 breakpoint_position: Anchor,
10593 snapshot: &EditorSnapshot,
10594 cx: &mut Context<Self>,
10595 ) -> Option<(Anchor, Breakpoint)> {
10596 let project = self.project.clone()?;
10597
10598 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10599 snapshot
10600 .buffer_snapshot
10601 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10602 })?;
10603
10604 let enclosing_excerpt = breakpoint_position.excerpt_id;
10605 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10606 let buffer_snapshot = buffer.read(cx).snapshot();
10607
10608 let row = buffer_snapshot
10609 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10610 .row;
10611
10612 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10613 let anchor_end = snapshot
10614 .buffer_snapshot
10615 .anchor_after(Point::new(row, line_len));
10616
10617 self.breakpoint_store
10618 .as_ref()?
10619 .read_with(cx, |breakpoint_store, cx| {
10620 breakpoint_store
10621 .breakpoints(
10622 &buffer,
10623 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10624 &buffer_snapshot,
10625 cx,
10626 )
10627 .next()
10628 .and_then(|(bp, _)| {
10629 let breakpoint_row = buffer_snapshot
10630 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10631 .row;
10632
10633 if breakpoint_row == row {
10634 snapshot
10635 .buffer_snapshot
10636 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10637 .map(|position| (position, bp.bp.clone()))
10638 } else {
10639 None
10640 }
10641 })
10642 })
10643 }
10644
10645 pub fn edit_log_breakpoint(
10646 &mut self,
10647 _: &EditLogBreakpoint,
10648 window: &mut Window,
10649 cx: &mut Context<Self>,
10650 ) {
10651 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10652 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10653 message: None,
10654 state: BreakpointState::Enabled,
10655 condition: None,
10656 hit_condition: None,
10657 });
10658
10659 self.add_edit_breakpoint_block(
10660 anchor,
10661 &breakpoint,
10662 BreakpointPromptEditAction::Log,
10663 window,
10664 cx,
10665 );
10666 }
10667 }
10668
10669 fn breakpoints_at_cursors(
10670 &self,
10671 window: &mut Window,
10672 cx: &mut Context<Self>,
10673 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10674 let snapshot = self.snapshot(window, cx);
10675 let cursors = self
10676 .selections
10677 .disjoint_anchors()
10678 .iter()
10679 .map(|selection| {
10680 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10681
10682 let breakpoint_position = self
10683 .breakpoint_at_row(cursor_position.row, window, cx)
10684 .map(|bp| bp.0)
10685 .unwrap_or_else(|| {
10686 snapshot
10687 .display_snapshot
10688 .buffer_snapshot
10689 .anchor_after(Point::new(cursor_position.row, 0))
10690 });
10691
10692 let breakpoint = self
10693 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10694 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10695
10696 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10697 })
10698 // 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.
10699 .collect::<HashMap<Anchor, _>>();
10700
10701 cursors.into_iter().collect()
10702 }
10703
10704 pub fn enable_breakpoint(
10705 &mut self,
10706 _: &crate::actions::EnableBreakpoint,
10707 window: &mut Window,
10708 cx: &mut Context<Self>,
10709 ) {
10710 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10711 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10712 continue;
10713 };
10714 self.edit_breakpoint_at_anchor(
10715 anchor,
10716 breakpoint,
10717 BreakpointEditAction::InvertState,
10718 cx,
10719 );
10720 }
10721 }
10722
10723 pub fn disable_breakpoint(
10724 &mut self,
10725 _: &crate::actions::DisableBreakpoint,
10726 window: &mut Window,
10727 cx: &mut Context<Self>,
10728 ) {
10729 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10730 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10731 continue;
10732 };
10733 self.edit_breakpoint_at_anchor(
10734 anchor,
10735 breakpoint,
10736 BreakpointEditAction::InvertState,
10737 cx,
10738 );
10739 }
10740 }
10741
10742 pub fn toggle_breakpoint(
10743 &mut self,
10744 _: &crate::actions::ToggleBreakpoint,
10745 window: &mut Window,
10746 cx: &mut Context<Self>,
10747 ) {
10748 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10749 if let Some(breakpoint) = breakpoint {
10750 self.edit_breakpoint_at_anchor(
10751 anchor,
10752 breakpoint,
10753 BreakpointEditAction::Toggle,
10754 cx,
10755 );
10756 } else {
10757 self.edit_breakpoint_at_anchor(
10758 anchor,
10759 Breakpoint::new_standard(),
10760 BreakpointEditAction::Toggle,
10761 cx,
10762 );
10763 }
10764 }
10765 }
10766
10767 pub fn edit_breakpoint_at_anchor(
10768 &mut self,
10769 breakpoint_position: Anchor,
10770 breakpoint: Breakpoint,
10771 edit_action: BreakpointEditAction,
10772 cx: &mut Context<Self>,
10773 ) {
10774 let Some(breakpoint_store) = &self.breakpoint_store else {
10775 return;
10776 };
10777
10778 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10779 if breakpoint_position == Anchor::min() {
10780 self.buffer()
10781 .read(cx)
10782 .excerpt_buffer_ids()
10783 .into_iter()
10784 .next()
10785 } else {
10786 None
10787 }
10788 }) else {
10789 return;
10790 };
10791
10792 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10793 return;
10794 };
10795
10796 breakpoint_store.update(cx, |breakpoint_store, cx| {
10797 breakpoint_store.toggle_breakpoint(
10798 buffer,
10799 BreakpointWithPosition {
10800 position: breakpoint_position.text_anchor,
10801 bp: breakpoint,
10802 },
10803 edit_action,
10804 cx,
10805 );
10806 });
10807
10808 cx.notify();
10809 }
10810
10811 #[cfg(any(test, feature = "test-support"))]
10812 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10813 self.breakpoint_store.clone()
10814 }
10815
10816 pub fn prepare_restore_change(
10817 &self,
10818 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10819 hunk: &MultiBufferDiffHunk,
10820 cx: &mut App,
10821 ) -> Option<()> {
10822 if hunk.is_created_file() {
10823 return None;
10824 }
10825 let buffer = self.buffer.read(cx);
10826 let diff = buffer.diff_for(hunk.buffer_id)?;
10827 let buffer = buffer.buffer(hunk.buffer_id)?;
10828 let buffer = buffer.read(cx);
10829 let original_text = diff
10830 .read(cx)
10831 .base_text()
10832 .as_rope()
10833 .slice(hunk.diff_base_byte_range.clone());
10834 let buffer_snapshot = buffer.snapshot();
10835 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10836 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10837 probe
10838 .0
10839 .start
10840 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10841 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10842 }) {
10843 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10844 Some(())
10845 } else {
10846 None
10847 }
10848 }
10849
10850 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10851 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10852 }
10853
10854 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10855 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10856 }
10857
10858 fn manipulate_lines<M>(
10859 &mut self,
10860 window: &mut Window,
10861 cx: &mut Context<Self>,
10862 mut manipulate: M,
10863 ) where
10864 M: FnMut(&str) -> LineManipulationResult,
10865 {
10866 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10867
10868 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10869 let buffer = self.buffer.read(cx).snapshot(cx);
10870
10871 let mut edits = Vec::new();
10872
10873 let selections = self.selections.all::<Point>(cx);
10874 let mut selections = selections.iter().peekable();
10875 let mut contiguous_row_selections = Vec::new();
10876 let mut new_selections = Vec::new();
10877 let mut added_lines = 0;
10878 let mut removed_lines = 0;
10879
10880 while let Some(selection) = selections.next() {
10881 let (start_row, end_row) = consume_contiguous_rows(
10882 &mut contiguous_row_selections,
10883 selection,
10884 &display_map,
10885 &mut selections,
10886 );
10887
10888 let start_point = Point::new(start_row.0, 0);
10889 let end_point = Point::new(
10890 end_row.previous_row().0,
10891 buffer.line_len(end_row.previous_row()),
10892 );
10893 let text = buffer
10894 .text_for_range(start_point..end_point)
10895 .collect::<String>();
10896
10897 let LineManipulationResult {
10898 new_text,
10899 line_count_before,
10900 line_count_after,
10901 } = manipulate(&text);
10902
10903 edits.push((start_point..end_point, new_text));
10904
10905 // Selections must change based on added and removed line count
10906 let start_row =
10907 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10908 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10909 new_selections.push(Selection {
10910 id: selection.id,
10911 start: start_row,
10912 end: end_row,
10913 goal: SelectionGoal::None,
10914 reversed: selection.reversed,
10915 });
10916
10917 if line_count_after > line_count_before {
10918 added_lines += line_count_after - line_count_before;
10919 } else if line_count_before > line_count_after {
10920 removed_lines += line_count_before - line_count_after;
10921 }
10922 }
10923
10924 self.transact(window, cx, |this, window, cx| {
10925 let buffer = this.buffer.update(cx, |buffer, cx| {
10926 buffer.edit(edits, None, cx);
10927 buffer.snapshot(cx)
10928 });
10929
10930 // Recalculate offsets on newly edited buffer
10931 let new_selections = new_selections
10932 .iter()
10933 .map(|s| {
10934 let start_point = Point::new(s.start.0, 0);
10935 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10936 Selection {
10937 id: s.id,
10938 start: buffer.point_to_offset(start_point),
10939 end: buffer.point_to_offset(end_point),
10940 goal: s.goal,
10941 reversed: s.reversed,
10942 }
10943 })
10944 .collect();
10945
10946 this.change_selections(Default::default(), window, cx, |s| {
10947 s.select(new_selections);
10948 });
10949
10950 this.request_autoscroll(Autoscroll::fit(), cx);
10951 });
10952 }
10953
10954 fn manipulate_immutable_lines<Fn>(
10955 &mut self,
10956 window: &mut Window,
10957 cx: &mut Context<Self>,
10958 mut callback: Fn,
10959 ) where
10960 Fn: FnMut(&mut Vec<&str>),
10961 {
10962 self.manipulate_lines(window, cx, |text| {
10963 let mut lines: Vec<&str> = text.split('\n').collect();
10964 let line_count_before = lines.len();
10965
10966 callback(&mut lines);
10967
10968 LineManipulationResult {
10969 new_text: lines.join("\n"),
10970 line_count_before,
10971 line_count_after: lines.len(),
10972 }
10973 });
10974 }
10975
10976 fn manipulate_mutable_lines<Fn>(
10977 &mut self,
10978 window: &mut Window,
10979 cx: &mut Context<Self>,
10980 mut callback: Fn,
10981 ) where
10982 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10983 {
10984 self.manipulate_lines(window, cx, |text| {
10985 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10986 let line_count_before = lines.len();
10987
10988 callback(&mut lines);
10989
10990 LineManipulationResult {
10991 new_text: lines.join("\n"),
10992 line_count_before,
10993 line_count_after: lines.len(),
10994 }
10995 });
10996 }
10997
10998 pub fn convert_indentation_to_spaces(
10999 &mut self,
11000 _: &ConvertIndentationToSpaces,
11001 window: &mut Window,
11002 cx: &mut Context<Self>,
11003 ) {
11004 let settings = self.buffer.read(cx).language_settings(cx);
11005 let tab_size = settings.tab_size.get() as usize;
11006
11007 self.manipulate_mutable_lines(window, cx, |lines| {
11008 // Allocates a reasonably sized scratch buffer once for the whole loop
11009 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11010 // Avoids recomputing spaces that could be inserted many times
11011 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11012 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11013 .collect();
11014
11015 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11016 let mut chars = line.as_ref().chars();
11017 let mut col = 0;
11018 let mut changed = false;
11019
11020 for ch in chars.by_ref() {
11021 match ch {
11022 ' ' => {
11023 reindented_line.push(' ');
11024 col += 1;
11025 }
11026 '\t' => {
11027 // \t are converted to spaces depending on the current column
11028 let spaces_len = tab_size - (col % tab_size);
11029 reindented_line.extend(&space_cache[spaces_len - 1]);
11030 col += spaces_len;
11031 changed = true;
11032 }
11033 _ => {
11034 // If we dont append before break, the character is consumed
11035 reindented_line.push(ch);
11036 break;
11037 }
11038 }
11039 }
11040
11041 if !changed {
11042 reindented_line.clear();
11043 continue;
11044 }
11045 // Append the rest of the line and replace old reference with new one
11046 reindented_line.extend(chars);
11047 *line = Cow::Owned(reindented_line.clone());
11048 reindented_line.clear();
11049 }
11050 });
11051 }
11052
11053 pub fn convert_indentation_to_tabs(
11054 &mut self,
11055 _: &ConvertIndentationToTabs,
11056 window: &mut Window,
11057 cx: &mut Context<Self>,
11058 ) {
11059 let settings = self.buffer.read(cx).language_settings(cx);
11060 let tab_size = settings.tab_size.get() as usize;
11061
11062 self.manipulate_mutable_lines(window, cx, |lines| {
11063 // Allocates a reasonably sized buffer once for the whole loop
11064 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11065 // Avoids recomputing spaces that could be inserted many times
11066 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11067 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11068 .collect();
11069
11070 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11071 let mut chars = line.chars();
11072 let mut spaces_count = 0;
11073 let mut first_non_indent_char = None;
11074 let mut changed = false;
11075
11076 for ch in chars.by_ref() {
11077 match ch {
11078 ' ' => {
11079 // Keep track of spaces. Append \t when we reach tab_size
11080 spaces_count += 1;
11081 changed = true;
11082 if spaces_count == tab_size {
11083 reindented_line.push('\t');
11084 spaces_count = 0;
11085 }
11086 }
11087 '\t' => {
11088 reindented_line.push('\t');
11089 spaces_count = 0;
11090 }
11091 _ => {
11092 // Dont append it yet, we might have remaining spaces
11093 first_non_indent_char = Some(ch);
11094 break;
11095 }
11096 }
11097 }
11098
11099 if !changed {
11100 reindented_line.clear();
11101 continue;
11102 }
11103 // Remaining spaces that didn't make a full tab stop
11104 if spaces_count > 0 {
11105 reindented_line.extend(&space_cache[spaces_count - 1]);
11106 }
11107 // If we consume an extra character that was not indentation, add it back
11108 if let Some(extra_char) = first_non_indent_char {
11109 reindented_line.push(extra_char);
11110 }
11111 // Append the rest of the line and replace old reference with new one
11112 reindented_line.extend(chars);
11113 *line = Cow::Owned(reindented_line.clone());
11114 reindented_line.clear();
11115 }
11116 });
11117 }
11118
11119 pub fn convert_to_upper_case(
11120 &mut self,
11121 _: &ConvertToUpperCase,
11122 window: &mut Window,
11123 cx: &mut Context<Self>,
11124 ) {
11125 self.manipulate_text(window, cx, |text| text.to_uppercase())
11126 }
11127
11128 pub fn convert_to_lower_case(
11129 &mut self,
11130 _: &ConvertToLowerCase,
11131 window: &mut Window,
11132 cx: &mut Context<Self>,
11133 ) {
11134 self.manipulate_text(window, cx, |text| text.to_lowercase())
11135 }
11136
11137 pub fn convert_to_title_case(
11138 &mut self,
11139 _: &ConvertToTitleCase,
11140 window: &mut Window,
11141 cx: &mut Context<Self>,
11142 ) {
11143 self.manipulate_text(window, cx, |text| {
11144 text.split('\n')
11145 .map(|line| line.to_case(Case::Title))
11146 .join("\n")
11147 })
11148 }
11149
11150 pub fn convert_to_snake_case(
11151 &mut self,
11152 _: &ConvertToSnakeCase,
11153 window: &mut Window,
11154 cx: &mut Context<Self>,
11155 ) {
11156 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11157 }
11158
11159 pub fn convert_to_kebab_case(
11160 &mut self,
11161 _: &ConvertToKebabCase,
11162 window: &mut Window,
11163 cx: &mut Context<Self>,
11164 ) {
11165 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11166 }
11167
11168 pub fn convert_to_upper_camel_case(
11169 &mut self,
11170 _: &ConvertToUpperCamelCase,
11171 window: &mut Window,
11172 cx: &mut Context<Self>,
11173 ) {
11174 self.manipulate_text(window, cx, |text| {
11175 text.split('\n')
11176 .map(|line| line.to_case(Case::UpperCamel))
11177 .join("\n")
11178 })
11179 }
11180
11181 pub fn convert_to_lower_camel_case(
11182 &mut self,
11183 _: &ConvertToLowerCamelCase,
11184 window: &mut Window,
11185 cx: &mut Context<Self>,
11186 ) {
11187 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11188 }
11189
11190 pub fn convert_to_opposite_case(
11191 &mut self,
11192 _: &ConvertToOppositeCase,
11193 window: &mut Window,
11194 cx: &mut Context<Self>,
11195 ) {
11196 self.manipulate_text(window, cx, |text| {
11197 text.chars()
11198 .fold(String::with_capacity(text.len()), |mut t, c| {
11199 if c.is_uppercase() {
11200 t.extend(c.to_lowercase());
11201 } else {
11202 t.extend(c.to_uppercase());
11203 }
11204 t
11205 })
11206 })
11207 }
11208
11209 pub fn convert_to_sentence_case(
11210 &mut self,
11211 _: &ConvertToSentenceCase,
11212 window: &mut Window,
11213 cx: &mut Context<Self>,
11214 ) {
11215 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11216 }
11217
11218 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11219 self.manipulate_text(window, cx, |text| {
11220 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11221 if has_upper_case_characters {
11222 text.to_lowercase()
11223 } else {
11224 text.to_uppercase()
11225 }
11226 })
11227 }
11228
11229 pub fn convert_to_rot13(
11230 &mut self,
11231 _: &ConvertToRot13,
11232 window: &mut Window,
11233 cx: &mut Context<Self>,
11234 ) {
11235 self.manipulate_text(window, cx, |text| {
11236 text.chars()
11237 .map(|c| match c {
11238 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11239 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11240 _ => c,
11241 })
11242 .collect()
11243 })
11244 }
11245
11246 pub fn convert_to_rot47(
11247 &mut self,
11248 _: &ConvertToRot47,
11249 window: &mut Window,
11250 cx: &mut Context<Self>,
11251 ) {
11252 self.manipulate_text(window, cx, |text| {
11253 text.chars()
11254 .map(|c| {
11255 let code_point = c as u32;
11256 if code_point >= 33 && code_point <= 126 {
11257 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11258 }
11259 c
11260 })
11261 .collect()
11262 })
11263 }
11264
11265 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11266 where
11267 Fn: FnMut(&str) -> String,
11268 {
11269 let buffer = self.buffer.read(cx).snapshot(cx);
11270
11271 let mut new_selections = Vec::new();
11272 let mut edits = Vec::new();
11273 let mut selection_adjustment = 0i32;
11274
11275 for selection in self.selections.all::<usize>(cx) {
11276 let selection_is_empty = selection.is_empty();
11277
11278 let (start, end) = if selection_is_empty {
11279 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11280 (word_range.start, word_range.end)
11281 } else {
11282 (selection.start, selection.end)
11283 };
11284
11285 let text = buffer.text_for_range(start..end).collect::<String>();
11286 let old_length = text.len() as i32;
11287 let text = callback(&text);
11288
11289 new_selections.push(Selection {
11290 start: (start as i32 - selection_adjustment) as usize,
11291 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11292 goal: SelectionGoal::None,
11293 ..selection
11294 });
11295
11296 selection_adjustment += old_length - text.len() as i32;
11297
11298 edits.push((start..end, text));
11299 }
11300
11301 self.transact(window, cx, |this, window, cx| {
11302 this.buffer.update(cx, |buffer, cx| {
11303 buffer.edit(edits, None, cx);
11304 });
11305
11306 this.change_selections(Default::default(), window, cx, |s| {
11307 s.select(new_selections);
11308 });
11309
11310 this.request_autoscroll(Autoscroll::fit(), cx);
11311 });
11312 }
11313
11314 pub fn move_selection_on_drop(
11315 &mut self,
11316 selection: &Selection<Anchor>,
11317 target: DisplayPoint,
11318 is_cut: bool,
11319 window: &mut Window,
11320 cx: &mut Context<Self>,
11321 ) {
11322 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11323 let buffer = &display_map.buffer_snapshot;
11324 let mut edits = Vec::new();
11325 let insert_point = display_map
11326 .clip_point(target, Bias::Left)
11327 .to_point(&display_map);
11328 let text = buffer
11329 .text_for_range(selection.start..selection.end)
11330 .collect::<String>();
11331 if is_cut {
11332 edits.push(((selection.start..selection.end), String::new()));
11333 }
11334 let insert_anchor = buffer.anchor_before(insert_point);
11335 edits.push(((insert_anchor..insert_anchor), text));
11336 let last_edit_start = insert_anchor.bias_left(buffer);
11337 let last_edit_end = insert_anchor.bias_right(buffer);
11338 self.transact(window, cx, |this, window, cx| {
11339 this.buffer.update(cx, |buffer, cx| {
11340 buffer.edit(edits, None, cx);
11341 });
11342 this.change_selections(Default::default(), window, cx, |s| {
11343 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11344 });
11345 });
11346 }
11347
11348 pub fn clear_selection_drag_state(&mut self) {
11349 self.selection_drag_state = SelectionDragState::None;
11350 }
11351
11352 pub fn duplicate(
11353 &mut self,
11354 upwards: bool,
11355 whole_lines: bool,
11356 window: &mut Window,
11357 cx: &mut Context<Self>,
11358 ) {
11359 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11360
11361 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11362 let buffer = &display_map.buffer_snapshot;
11363 let selections = self.selections.all::<Point>(cx);
11364
11365 let mut edits = Vec::new();
11366 let mut selections_iter = selections.iter().peekable();
11367 while let Some(selection) = selections_iter.next() {
11368 let mut rows = selection.spanned_rows(false, &display_map);
11369 // duplicate line-wise
11370 if whole_lines || selection.start == selection.end {
11371 // Avoid duplicating the same lines twice.
11372 while let Some(next_selection) = selections_iter.peek() {
11373 let next_rows = next_selection.spanned_rows(false, &display_map);
11374 if next_rows.start < rows.end {
11375 rows.end = next_rows.end;
11376 selections_iter.next().unwrap();
11377 } else {
11378 break;
11379 }
11380 }
11381
11382 // Copy the text from the selected row region and splice it either at the start
11383 // or end of the region.
11384 let start = Point::new(rows.start.0, 0);
11385 let end = Point::new(
11386 rows.end.previous_row().0,
11387 buffer.line_len(rows.end.previous_row()),
11388 );
11389 let text = buffer
11390 .text_for_range(start..end)
11391 .chain(Some("\n"))
11392 .collect::<String>();
11393 let insert_location = if upwards {
11394 Point::new(rows.end.0, 0)
11395 } else {
11396 start
11397 };
11398 edits.push((insert_location..insert_location, text));
11399 } else {
11400 // duplicate character-wise
11401 let start = selection.start;
11402 let end = selection.end;
11403 let text = buffer.text_for_range(start..end).collect::<String>();
11404 edits.push((selection.end..selection.end, text));
11405 }
11406 }
11407
11408 self.transact(window, cx, |this, _, cx| {
11409 this.buffer.update(cx, |buffer, cx| {
11410 buffer.edit(edits, None, cx);
11411 });
11412
11413 this.request_autoscroll(Autoscroll::fit(), cx);
11414 });
11415 }
11416
11417 pub fn duplicate_line_up(
11418 &mut self,
11419 _: &DuplicateLineUp,
11420 window: &mut Window,
11421 cx: &mut Context<Self>,
11422 ) {
11423 self.duplicate(true, true, window, cx);
11424 }
11425
11426 pub fn duplicate_line_down(
11427 &mut self,
11428 _: &DuplicateLineDown,
11429 window: &mut Window,
11430 cx: &mut Context<Self>,
11431 ) {
11432 self.duplicate(false, true, window, cx);
11433 }
11434
11435 pub fn duplicate_selection(
11436 &mut self,
11437 _: &DuplicateSelection,
11438 window: &mut Window,
11439 cx: &mut Context<Self>,
11440 ) {
11441 self.duplicate(false, false, window, cx);
11442 }
11443
11444 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11445 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11446 if self.mode.is_single_line() {
11447 cx.propagate();
11448 return;
11449 }
11450
11451 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11452 let buffer = self.buffer.read(cx).snapshot(cx);
11453
11454 let mut edits = Vec::new();
11455 let mut unfold_ranges = Vec::new();
11456 let mut refold_creases = Vec::new();
11457
11458 let selections = self.selections.all::<Point>(cx);
11459 let mut selections = selections.iter().peekable();
11460 let mut contiguous_row_selections = Vec::new();
11461 let mut new_selections = Vec::new();
11462
11463 while let Some(selection) = selections.next() {
11464 // Find all the selections that span a contiguous row range
11465 let (start_row, end_row) = consume_contiguous_rows(
11466 &mut contiguous_row_selections,
11467 selection,
11468 &display_map,
11469 &mut selections,
11470 );
11471
11472 // Move the text spanned by the row range to be before the line preceding the row range
11473 if start_row.0 > 0 {
11474 let range_to_move = Point::new(
11475 start_row.previous_row().0,
11476 buffer.line_len(start_row.previous_row()),
11477 )
11478 ..Point::new(
11479 end_row.previous_row().0,
11480 buffer.line_len(end_row.previous_row()),
11481 );
11482 let insertion_point = display_map
11483 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11484 .0;
11485
11486 // Don't move lines across excerpts
11487 if buffer
11488 .excerpt_containing(insertion_point..range_to_move.end)
11489 .is_some()
11490 {
11491 let text = buffer
11492 .text_for_range(range_to_move.clone())
11493 .flat_map(|s| s.chars())
11494 .skip(1)
11495 .chain(['\n'])
11496 .collect::<String>();
11497
11498 edits.push((
11499 buffer.anchor_after(range_to_move.start)
11500 ..buffer.anchor_before(range_to_move.end),
11501 String::new(),
11502 ));
11503 let insertion_anchor = buffer.anchor_after(insertion_point);
11504 edits.push((insertion_anchor..insertion_anchor, text));
11505
11506 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11507
11508 // Move selections up
11509 new_selections.extend(contiguous_row_selections.drain(..).map(
11510 |mut selection| {
11511 selection.start.row -= row_delta;
11512 selection.end.row -= row_delta;
11513 selection
11514 },
11515 ));
11516
11517 // Move folds up
11518 unfold_ranges.push(range_to_move.clone());
11519 for fold in display_map.folds_in_range(
11520 buffer.anchor_before(range_to_move.start)
11521 ..buffer.anchor_after(range_to_move.end),
11522 ) {
11523 let mut start = fold.range.start.to_point(&buffer);
11524 let mut end = fold.range.end.to_point(&buffer);
11525 start.row -= row_delta;
11526 end.row -= row_delta;
11527 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11528 }
11529 }
11530 }
11531
11532 // If we didn't move line(s), preserve the existing selections
11533 new_selections.append(&mut contiguous_row_selections);
11534 }
11535
11536 self.transact(window, cx, |this, window, cx| {
11537 this.unfold_ranges(&unfold_ranges, true, true, cx);
11538 this.buffer.update(cx, |buffer, cx| {
11539 for (range, text) in edits {
11540 buffer.edit([(range, text)], None, cx);
11541 }
11542 });
11543 this.fold_creases(refold_creases, true, window, cx);
11544 this.change_selections(Default::default(), window, cx, |s| {
11545 s.select(new_selections);
11546 })
11547 });
11548 }
11549
11550 pub fn move_line_down(
11551 &mut self,
11552 _: &MoveLineDown,
11553 window: &mut Window,
11554 cx: &mut Context<Self>,
11555 ) {
11556 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11557 if self.mode.is_single_line() {
11558 cx.propagate();
11559 return;
11560 }
11561
11562 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11563 let buffer = self.buffer.read(cx).snapshot(cx);
11564
11565 let mut edits = Vec::new();
11566 let mut unfold_ranges = Vec::new();
11567 let mut refold_creases = Vec::new();
11568
11569 let selections = self.selections.all::<Point>(cx);
11570 let mut selections = selections.iter().peekable();
11571 let mut contiguous_row_selections = Vec::new();
11572 let mut new_selections = Vec::new();
11573
11574 while let Some(selection) = selections.next() {
11575 // Find all the selections that span a contiguous row range
11576 let (start_row, end_row) = consume_contiguous_rows(
11577 &mut contiguous_row_selections,
11578 selection,
11579 &display_map,
11580 &mut selections,
11581 );
11582
11583 // Move the text spanned by the row range to be after the last line of the row range
11584 if end_row.0 <= buffer.max_point().row {
11585 let range_to_move =
11586 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11587 let insertion_point = display_map
11588 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11589 .0;
11590
11591 // Don't move lines across excerpt boundaries
11592 if buffer
11593 .excerpt_containing(range_to_move.start..insertion_point)
11594 .is_some()
11595 {
11596 let mut text = String::from("\n");
11597 text.extend(buffer.text_for_range(range_to_move.clone()));
11598 text.pop(); // Drop trailing newline
11599 edits.push((
11600 buffer.anchor_after(range_to_move.start)
11601 ..buffer.anchor_before(range_to_move.end),
11602 String::new(),
11603 ));
11604 let insertion_anchor = buffer.anchor_after(insertion_point);
11605 edits.push((insertion_anchor..insertion_anchor, text));
11606
11607 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11608
11609 // Move selections down
11610 new_selections.extend(contiguous_row_selections.drain(..).map(
11611 |mut selection| {
11612 selection.start.row += row_delta;
11613 selection.end.row += row_delta;
11614 selection
11615 },
11616 ));
11617
11618 // Move folds down
11619 unfold_ranges.push(range_to_move.clone());
11620 for fold in display_map.folds_in_range(
11621 buffer.anchor_before(range_to_move.start)
11622 ..buffer.anchor_after(range_to_move.end),
11623 ) {
11624 let mut start = fold.range.start.to_point(&buffer);
11625 let mut end = fold.range.end.to_point(&buffer);
11626 start.row += row_delta;
11627 end.row += row_delta;
11628 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11629 }
11630 }
11631 }
11632
11633 // If we didn't move line(s), preserve the existing selections
11634 new_selections.append(&mut contiguous_row_selections);
11635 }
11636
11637 self.transact(window, cx, |this, window, cx| {
11638 this.unfold_ranges(&unfold_ranges, true, true, cx);
11639 this.buffer.update(cx, |buffer, cx| {
11640 for (range, text) in edits {
11641 buffer.edit([(range, text)], None, cx);
11642 }
11643 });
11644 this.fold_creases(refold_creases, true, window, cx);
11645 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11646 });
11647 }
11648
11649 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11650 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11651 let text_layout_details = &self.text_layout_details(window);
11652 self.transact(window, cx, |this, window, cx| {
11653 let edits = this.change_selections(Default::default(), window, cx, |s| {
11654 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11655 s.move_with(|display_map, selection| {
11656 if !selection.is_empty() {
11657 return;
11658 }
11659
11660 let mut head = selection.head();
11661 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11662 if head.column() == display_map.line_len(head.row()) {
11663 transpose_offset = display_map
11664 .buffer_snapshot
11665 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11666 }
11667
11668 if transpose_offset == 0 {
11669 return;
11670 }
11671
11672 *head.column_mut() += 1;
11673 head = display_map.clip_point(head, Bias::Right);
11674 let goal = SelectionGoal::HorizontalPosition(
11675 display_map
11676 .x_for_display_point(head, text_layout_details)
11677 .into(),
11678 );
11679 selection.collapse_to(head, goal);
11680
11681 let transpose_start = display_map
11682 .buffer_snapshot
11683 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11684 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11685 let transpose_end = display_map
11686 .buffer_snapshot
11687 .clip_offset(transpose_offset + 1, Bias::Right);
11688 if let Some(ch) =
11689 display_map.buffer_snapshot.chars_at(transpose_start).next()
11690 {
11691 edits.push((transpose_start..transpose_offset, String::new()));
11692 edits.push((transpose_end..transpose_end, ch.to_string()));
11693 }
11694 }
11695 });
11696 edits
11697 });
11698 this.buffer
11699 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11700 let selections = this.selections.all::<usize>(cx);
11701 this.change_selections(Default::default(), window, cx, |s| {
11702 s.select(selections);
11703 });
11704 });
11705 }
11706
11707 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11708 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11709 if self.mode.is_single_line() {
11710 cx.propagate();
11711 return;
11712 }
11713
11714 self.rewrap_impl(RewrapOptions::default(), cx)
11715 }
11716
11717 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11718 let buffer = self.buffer.read(cx).snapshot(cx);
11719 let selections = self.selections.all::<Point>(cx);
11720
11721 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11722 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11723 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11724 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11725 .peekable();
11726
11727 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11728 row
11729 } else {
11730 return Vec::new();
11731 };
11732
11733 let language_settings = buffer.language_settings_at(selection.head(), cx);
11734 let language_scope = buffer.language_scope_at(selection.head());
11735
11736 let indent_and_prefix_for_row =
11737 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11738 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11739 let (comment_prefix, rewrap_prefix) =
11740 if let Some(language_scope) = &language_scope {
11741 let indent_end = Point::new(row, indent.len);
11742 let comment_prefix = language_scope
11743 .line_comment_prefixes()
11744 .iter()
11745 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11746 .map(|prefix| prefix.to_string());
11747 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11748 let line_text_after_indent = buffer
11749 .text_for_range(indent_end..line_end)
11750 .collect::<String>();
11751 let rewrap_prefix = language_scope
11752 .rewrap_prefixes()
11753 .iter()
11754 .find_map(|prefix_regex| {
11755 prefix_regex.find(&line_text_after_indent).map(|mat| {
11756 if mat.start() == 0 {
11757 Some(mat.as_str().to_string())
11758 } else {
11759 None
11760 }
11761 })
11762 })
11763 .flatten();
11764 (comment_prefix, rewrap_prefix)
11765 } else {
11766 (None, None)
11767 };
11768 (indent, comment_prefix, rewrap_prefix)
11769 };
11770
11771 let mut ranges = Vec::new();
11772 let from_empty_selection = selection.is_empty();
11773
11774 let mut current_range_start = first_row;
11775 let mut prev_row = first_row;
11776 let (
11777 mut current_range_indent,
11778 mut current_range_comment_prefix,
11779 mut current_range_rewrap_prefix,
11780 ) = indent_and_prefix_for_row(first_row);
11781
11782 for row in non_blank_rows_iter.skip(1) {
11783 let has_paragraph_break = row > prev_row + 1;
11784
11785 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11786 indent_and_prefix_for_row(row);
11787
11788 let has_indent_change = row_indent != current_range_indent;
11789 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11790
11791 let has_boundary_change = has_comment_change
11792 || row_rewrap_prefix.is_some()
11793 || (has_indent_change && current_range_comment_prefix.is_some());
11794
11795 if has_paragraph_break || has_boundary_change {
11796 ranges.push((
11797 language_settings.clone(),
11798 Point::new(current_range_start, 0)
11799 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11800 current_range_indent,
11801 current_range_comment_prefix.clone(),
11802 current_range_rewrap_prefix.clone(),
11803 from_empty_selection,
11804 ));
11805 current_range_start = row;
11806 current_range_indent = row_indent;
11807 current_range_comment_prefix = row_comment_prefix;
11808 current_range_rewrap_prefix = row_rewrap_prefix;
11809 }
11810 prev_row = row;
11811 }
11812
11813 ranges.push((
11814 language_settings.clone(),
11815 Point::new(current_range_start, 0)
11816 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11817 current_range_indent,
11818 current_range_comment_prefix,
11819 current_range_rewrap_prefix,
11820 from_empty_selection,
11821 ));
11822
11823 ranges
11824 });
11825
11826 let mut edits = Vec::new();
11827 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11828
11829 for (
11830 language_settings,
11831 wrap_range,
11832 indent_size,
11833 comment_prefix,
11834 rewrap_prefix,
11835 from_empty_selection,
11836 ) in wrap_ranges
11837 {
11838 let mut start_row = wrap_range.start.row;
11839 let mut end_row = wrap_range.end.row;
11840
11841 // Skip selections that overlap with a range that has already been rewrapped.
11842 let selection_range = start_row..end_row;
11843 if rewrapped_row_ranges
11844 .iter()
11845 .any(|range| range.overlaps(&selection_range))
11846 {
11847 continue;
11848 }
11849
11850 let tab_size = language_settings.tab_size;
11851
11852 let indent_prefix = indent_size.chars().collect::<String>();
11853 let mut line_prefix = indent_prefix.clone();
11854 let mut inside_comment = false;
11855 if let Some(prefix) = &comment_prefix {
11856 line_prefix.push_str(prefix);
11857 inside_comment = true;
11858 }
11859 if let Some(prefix) = &rewrap_prefix {
11860 line_prefix.push_str(prefix);
11861 }
11862
11863 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11864 RewrapBehavior::InComments => inside_comment,
11865 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11866 RewrapBehavior::Anywhere => true,
11867 };
11868
11869 let should_rewrap = options.override_language_settings
11870 || allow_rewrap_based_on_language
11871 || self.hard_wrap.is_some();
11872 if !should_rewrap {
11873 continue;
11874 }
11875
11876 if from_empty_selection {
11877 'expand_upwards: while start_row > 0 {
11878 let prev_row = start_row - 1;
11879 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11880 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11881 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11882 {
11883 start_row = prev_row;
11884 } else {
11885 break 'expand_upwards;
11886 }
11887 }
11888
11889 'expand_downwards: while end_row < buffer.max_point().row {
11890 let next_row = end_row + 1;
11891 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11892 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11893 && !buffer.is_line_blank(MultiBufferRow(next_row))
11894 {
11895 end_row = next_row;
11896 } else {
11897 break 'expand_downwards;
11898 }
11899 }
11900 }
11901
11902 let start = Point::new(start_row, 0);
11903 let start_offset = start.to_offset(&buffer);
11904 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11905 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11906 let Some(lines_without_prefixes) = selection_text
11907 .lines()
11908 .enumerate()
11909 .map(|(ix, line)| {
11910 let line_trimmed = line.trim_start();
11911 if rewrap_prefix.is_some() && ix > 0 {
11912 Ok(line_trimmed)
11913 } else {
11914 line_trimmed
11915 .strip_prefix(&line_prefix.trim_start())
11916 .with_context(|| {
11917 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11918 })
11919 }
11920 })
11921 .collect::<Result<Vec<_>, _>>()
11922 .log_err()
11923 else {
11924 continue;
11925 };
11926
11927 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11928 buffer
11929 .language_settings_at(Point::new(start_row, 0), cx)
11930 .preferred_line_length as usize
11931 });
11932
11933 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11934 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11935 } else {
11936 line_prefix.clone()
11937 };
11938
11939 let wrapped_text = wrap_with_prefix(
11940 line_prefix,
11941 subsequent_lines_prefix,
11942 lines_without_prefixes.join("\n"),
11943 wrap_column,
11944 tab_size,
11945 options.preserve_existing_whitespace,
11946 );
11947
11948 // TODO: should always use char-based diff while still supporting cursor behavior that
11949 // matches vim.
11950 let mut diff_options = DiffOptions::default();
11951 if options.override_language_settings {
11952 diff_options.max_word_diff_len = 0;
11953 diff_options.max_word_diff_line_count = 0;
11954 } else {
11955 diff_options.max_word_diff_len = usize::MAX;
11956 diff_options.max_word_diff_line_count = usize::MAX;
11957 }
11958
11959 for (old_range, new_text) in
11960 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11961 {
11962 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11963 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11964 edits.push((edit_start..edit_end, new_text));
11965 }
11966
11967 rewrapped_row_ranges.push(start_row..=end_row);
11968 }
11969
11970 self.buffer
11971 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11972 }
11973
11974 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11975 let mut text = String::new();
11976 let buffer = self.buffer.read(cx).snapshot(cx);
11977 let mut selections = self.selections.all::<Point>(cx);
11978 let mut clipboard_selections = Vec::with_capacity(selections.len());
11979 {
11980 let max_point = buffer.max_point();
11981 let mut is_first = true;
11982 for selection in &mut selections {
11983 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11984 if is_entire_line {
11985 selection.start = Point::new(selection.start.row, 0);
11986 if !selection.is_empty() && selection.end.column == 0 {
11987 selection.end = cmp::min(max_point, selection.end);
11988 } else {
11989 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11990 }
11991 selection.goal = SelectionGoal::None;
11992 }
11993 if is_first {
11994 is_first = false;
11995 } else {
11996 text += "\n";
11997 }
11998 let mut len = 0;
11999 for chunk in buffer.text_for_range(selection.start..selection.end) {
12000 text.push_str(chunk);
12001 len += chunk.len();
12002 }
12003 clipboard_selections.push(ClipboardSelection {
12004 len,
12005 is_entire_line,
12006 first_line_indent: buffer
12007 .indent_size_for_line(MultiBufferRow(selection.start.row))
12008 .len,
12009 });
12010 }
12011 }
12012
12013 self.transact(window, cx, |this, window, cx| {
12014 this.change_selections(Default::default(), window, cx, |s| {
12015 s.select(selections);
12016 });
12017 this.insert("", window, cx);
12018 });
12019 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12020 }
12021
12022 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12023 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12024 let item = self.cut_common(window, cx);
12025 cx.write_to_clipboard(item);
12026 }
12027
12028 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12029 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12030 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12031 s.move_with(|snapshot, sel| {
12032 if sel.is_empty() {
12033 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12034 }
12035 });
12036 });
12037 let item = self.cut_common(window, cx);
12038 cx.set_global(KillRing(item))
12039 }
12040
12041 pub fn kill_ring_yank(
12042 &mut self,
12043 _: &KillRingYank,
12044 window: &mut Window,
12045 cx: &mut Context<Self>,
12046 ) {
12047 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12048 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12049 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12050 (kill_ring.text().to_string(), kill_ring.metadata_json())
12051 } else {
12052 return;
12053 }
12054 } else {
12055 return;
12056 };
12057 self.do_paste(&text, metadata, false, window, cx);
12058 }
12059
12060 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12061 self.do_copy(true, cx);
12062 }
12063
12064 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12065 self.do_copy(false, cx);
12066 }
12067
12068 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12069 let selections = self.selections.all::<Point>(cx);
12070 let buffer = self.buffer.read(cx).read(cx);
12071 let mut text = String::new();
12072
12073 let mut clipboard_selections = Vec::with_capacity(selections.len());
12074 {
12075 let max_point = buffer.max_point();
12076 let mut is_first = true;
12077 for selection in &selections {
12078 let mut start = selection.start;
12079 let mut end = selection.end;
12080 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12081 if is_entire_line {
12082 start = Point::new(start.row, 0);
12083 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12084 }
12085
12086 let mut trimmed_selections = Vec::new();
12087 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12088 let row = MultiBufferRow(start.row);
12089 let first_indent = buffer.indent_size_for_line(row);
12090 if first_indent.len == 0 || start.column > first_indent.len {
12091 trimmed_selections.push(start..end);
12092 } else {
12093 trimmed_selections.push(
12094 Point::new(row.0, first_indent.len)
12095 ..Point::new(row.0, buffer.line_len(row)),
12096 );
12097 for row in start.row + 1..=end.row {
12098 let mut line_len = buffer.line_len(MultiBufferRow(row));
12099 if row == end.row {
12100 line_len = end.column;
12101 }
12102 if line_len == 0 {
12103 trimmed_selections
12104 .push(Point::new(row, 0)..Point::new(row, line_len));
12105 continue;
12106 }
12107 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12108 if row_indent_size.len >= first_indent.len {
12109 trimmed_selections.push(
12110 Point::new(row, first_indent.len)..Point::new(row, line_len),
12111 );
12112 } else {
12113 trimmed_selections.clear();
12114 trimmed_selections.push(start..end);
12115 break;
12116 }
12117 }
12118 }
12119 } else {
12120 trimmed_selections.push(start..end);
12121 }
12122
12123 for trimmed_range in trimmed_selections {
12124 if is_first {
12125 is_first = false;
12126 } else {
12127 text += "\n";
12128 }
12129 let mut len = 0;
12130 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12131 text.push_str(chunk);
12132 len += chunk.len();
12133 }
12134 clipboard_selections.push(ClipboardSelection {
12135 len,
12136 is_entire_line,
12137 first_line_indent: buffer
12138 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12139 .len,
12140 });
12141 }
12142 }
12143 }
12144
12145 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12146 text,
12147 clipboard_selections,
12148 ));
12149 }
12150
12151 pub fn do_paste(
12152 &mut self,
12153 text: &String,
12154 clipboard_selections: Option<Vec<ClipboardSelection>>,
12155 handle_entire_lines: bool,
12156 window: &mut Window,
12157 cx: &mut Context<Self>,
12158 ) {
12159 if self.read_only(cx) {
12160 return;
12161 }
12162
12163 let clipboard_text = Cow::Borrowed(text);
12164
12165 self.transact(window, cx, |this, window, cx| {
12166 let had_active_edit_prediction = this.has_active_edit_prediction();
12167
12168 if let Some(mut clipboard_selections) = clipboard_selections {
12169 let old_selections = this.selections.all::<usize>(cx);
12170 let all_selections_were_entire_line =
12171 clipboard_selections.iter().all(|s| s.is_entire_line);
12172 let first_selection_indent_column =
12173 clipboard_selections.first().map(|s| s.first_line_indent);
12174 if clipboard_selections.len() != old_selections.len() {
12175 clipboard_selections.drain(..);
12176 }
12177 let cursor_offset = this.selections.last::<usize>(cx).head();
12178 let mut auto_indent_on_paste = true;
12179
12180 this.buffer.update(cx, |buffer, cx| {
12181 let snapshot = buffer.read(cx);
12182 auto_indent_on_paste = snapshot
12183 .language_settings_at(cursor_offset, cx)
12184 .auto_indent_on_paste;
12185
12186 let mut start_offset = 0;
12187 let mut edits = Vec::new();
12188 let mut original_indent_columns = Vec::new();
12189 for (ix, selection) in old_selections.iter().enumerate() {
12190 let to_insert;
12191 let entire_line;
12192 let original_indent_column;
12193 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12194 let end_offset = start_offset + clipboard_selection.len;
12195 to_insert = &clipboard_text[start_offset..end_offset];
12196 entire_line = clipboard_selection.is_entire_line;
12197 start_offset = end_offset + 1;
12198 original_indent_column = Some(clipboard_selection.first_line_indent);
12199 } else {
12200 to_insert = clipboard_text.as_str();
12201 entire_line = all_selections_were_entire_line;
12202 original_indent_column = first_selection_indent_column
12203 }
12204
12205 // If the corresponding selection was empty when this slice of the
12206 // clipboard text was written, then the entire line containing the
12207 // selection was copied. If this selection is also currently empty,
12208 // then paste the line before the current line of the buffer.
12209 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12210 let column = selection.start.to_point(&snapshot).column as usize;
12211 let line_start = selection.start - column;
12212 line_start..line_start
12213 } else {
12214 selection.range()
12215 };
12216
12217 edits.push((range, to_insert));
12218 original_indent_columns.push(original_indent_column);
12219 }
12220 drop(snapshot);
12221
12222 buffer.edit(
12223 edits,
12224 if auto_indent_on_paste {
12225 Some(AutoindentMode::Block {
12226 original_indent_columns,
12227 })
12228 } else {
12229 None
12230 },
12231 cx,
12232 );
12233 });
12234
12235 let selections = this.selections.all::<usize>(cx);
12236 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12237 } else {
12238 this.insert(&clipboard_text, window, cx);
12239 }
12240
12241 let trigger_in_words =
12242 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12243
12244 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12245 });
12246 }
12247
12248 pub fn diff_clipboard_with_selection(
12249 &mut self,
12250 _: &DiffClipboardWithSelection,
12251 window: &mut Window,
12252 cx: &mut Context<Self>,
12253 ) {
12254 let selections = self.selections.all::<usize>(cx);
12255
12256 if selections.is_empty() {
12257 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12258 return;
12259 };
12260
12261 let clipboard_text = match cx.read_from_clipboard() {
12262 Some(item) => match item.entries().first() {
12263 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12264 _ => None,
12265 },
12266 None => None,
12267 };
12268
12269 let Some(clipboard_text) = clipboard_text else {
12270 log::warn!("Clipboard doesn't contain text.");
12271 return;
12272 };
12273
12274 window.dispatch_action(
12275 Box::new(DiffClipboardWithSelectionData {
12276 clipboard_text,
12277 editor: cx.entity(),
12278 }),
12279 cx,
12280 );
12281 }
12282
12283 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12284 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12285 if let Some(item) = cx.read_from_clipboard() {
12286 let entries = item.entries();
12287
12288 match entries.first() {
12289 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12290 // of all the pasted entries.
12291 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12292 .do_paste(
12293 clipboard_string.text(),
12294 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12295 true,
12296 window,
12297 cx,
12298 ),
12299 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12300 }
12301 }
12302 }
12303
12304 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12305 if self.read_only(cx) {
12306 return;
12307 }
12308
12309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12310
12311 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12312 if let Some((selections, _)) =
12313 self.selection_history.transaction(transaction_id).cloned()
12314 {
12315 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12316 s.select_anchors(selections.to_vec());
12317 });
12318 } else {
12319 log::error!(
12320 "No entry in selection_history found for undo. \
12321 This may correspond to a bug where undo does not update the selection. \
12322 If this is occurring, please add details to \
12323 https://github.com/zed-industries/zed/issues/22692"
12324 );
12325 }
12326 self.request_autoscroll(Autoscroll::fit(), cx);
12327 self.unmark_text(window, cx);
12328 self.refresh_edit_prediction(true, false, window, cx);
12329 cx.emit(EditorEvent::Edited { transaction_id });
12330 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12331 }
12332 }
12333
12334 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12335 if self.read_only(cx) {
12336 return;
12337 }
12338
12339 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12340
12341 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12342 if let Some((_, Some(selections))) =
12343 self.selection_history.transaction(transaction_id).cloned()
12344 {
12345 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12346 s.select_anchors(selections.to_vec());
12347 });
12348 } else {
12349 log::error!(
12350 "No entry in selection_history found for redo. \
12351 This may correspond to a bug where undo does not update the selection. \
12352 If this is occurring, please add details to \
12353 https://github.com/zed-industries/zed/issues/22692"
12354 );
12355 }
12356 self.request_autoscroll(Autoscroll::fit(), cx);
12357 self.unmark_text(window, cx);
12358 self.refresh_edit_prediction(true, false, window, cx);
12359 cx.emit(EditorEvent::Edited { transaction_id });
12360 }
12361 }
12362
12363 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12364 self.buffer
12365 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12366 }
12367
12368 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12369 self.buffer
12370 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12371 }
12372
12373 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12374 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12375 self.change_selections(Default::default(), window, cx, |s| {
12376 s.move_with(|map, selection| {
12377 let cursor = if selection.is_empty() {
12378 movement::left(map, selection.start)
12379 } else {
12380 selection.start
12381 };
12382 selection.collapse_to(cursor, SelectionGoal::None);
12383 });
12384 })
12385 }
12386
12387 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12388 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12389 self.change_selections(Default::default(), window, cx, |s| {
12390 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12391 })
12392 }
12393
12394 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12395 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12396 self.change_selections(Default::default(), window, cx, |s| {
12397 s.move_with(|map, selection| {
12398 let cursor = if selection.is_empty() {
12399 movement::right(map, selection.end)
12400 } else {
12401 selection.end
12402 };
12403 selection.collapse_to(cursor, SelectionGoal::None)
12404 });
12405 })
12406 }
12407
12408 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12409 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12410 self.change_selections(Default::default(), window, cx, |s| {
12411 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12412 })
12413 }
12414
12415 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12416 if self.take_rename(true, window, cx).is_some() {
12417 return;
12418 }
12419
12420 if self.mode.is_single_line() {
12421 cx.propagate();
12422 return;
12423 }
12424
12425 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12426
12427 let text_layout_details = &self.text_layout_details(window);
12428 let selection_count = self.selections.count();
12429 let first_selection = self.selections.first_anchor();
12430
12431 self.change_selections(Default::default(), window, cx, |s| {
12432 s.move_with(|map, selection| {
12433 if !selection.is_empty() {
12434 selection.goal = SelectionGoal::None;
12435 }
12436 let (cursor, goal) = movement::up(
12437 map,
12438 selection.start,
12439 selection.goal,
12440 false,
12441 text_layout_details,
12442 );
12443 selection.collapse_to(cursor, goal);
12444 });
12445 });
12446
12447 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12448 {
12449 cx.propagate();
12450 }
12451 }
12452
12453 pub fn move_up_by_lines(
12454 &mut self,
12455 action: &MoveUpByLines,
12456 window: &mut Window,
12457 cx: &mut Context<Self>,
12458 ) {
12459 if self.take_rename(true, window, cx).is_some() {
12460 return;
12461 }
12462
12463 if self.mode.is_single_line() {
12464 cx.propagate();
12465 return;
12466 }
12467
12468 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12469
12470 let text_layout_details = &self.text_layout_details(window);
12471
12472 self.change_selections(Default::default(), window, cx, |s| {
12473 s.move_with(|map, selection| {
12474 if !selection.is_empty() {
12475 selection.goal = SelectionGoal::None;
12476 }
12477 let (cursor, goal) = movement::up_by_rows(
12478 map,
12479 selection.start,
12480 action.lines,
12481 selection.goal,
12482 false,
12483 text_layout_details,
12484 );
12485 selection.collapse_to(cursor, goal);
12486 });
12487 })
12488 }
12489
12490 pub fn move_down_by_lines(
12491 &mut self,
12492 action: &MoveDownByLines,
12493 window: &mut Window,
12494 cx: &mut Context<Self>,
12495 ) {
12496 if self.take_rename(true, window, cx).is_some() {
12497 return;
12498 }
12499
12500 if self.mode.is_single_line() {
12501 cx.propagate();
12502 return;
12503 }
12504
12505 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12506
12507 let text_layout_details = &self.text_layout_details(window);
12508
12509 self.change_selections(Default::default(), window, cx, |s| {
12510 s.move_with(|map, selection| {
12511 if !selection.is_empty() {
12512 selection.goal = SelectionGoal::None;
12513 }
12514 let (cursor, goal) = movement::down_by_rows(
12515 map,
12516 selection.start,
12517 action.lines,
12518 selection.goal,
12519 false,
12520 text_layout_details,
12521 );
12522 selection.collapse_to(cursor, goal);
12523 });
12524 })
12525 }
12526
12527 pub fn select_down_by_lines(
12528 &mut self,
12529 action: &SelectDownByLines,
12530 window: &mut Window,
12531 cx: &mut Context<Self>,
12532 ) {
12533 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12534 let text_layout_details = &self.text_layout_details(window);
12535 self.change_selections(Default::default(), window, cx, |s| {
12536 s.move_heads_with(|map, head, goal| {
12537 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12538 })
12539 })
12540 }
12541
12542 pub fn select_up_by_lines(
12543 &mut self,
12544 action: &SelectUpByLines,
12545 window: &mut Window,
12546 cx: &mut Context<Self>,
12547 ) {
12548 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12549 let text_layout_details = &self.text_layout_details(window);
12550 self.change_selections(Default::default(), window, cx, |s| {
12551 s.move_heads_with(|map, head, goal| {
12552 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12553 })
12554 })
12555 }
12556
12557 pub fn select_page_up(
12558 &mut self,
12559 _: &SelectPageUp,
12560 window: &mut Window,
12561 cx: &mut Context<Self>,
12562 ) {
12563 let Some(row_count) = self.visible_row_count() else {
12564 return;
12565 };
12566
12567 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12568
12569 let text_layout_details = &self.text_layout_details(window);
12570
12571 self.change_selections(Default::default(), window, cx, |s| {
12572 s.move_heads_with(|map, head, goal| {
12573 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12574 })
12575 })
12576 }
12577
12578 pub fn move_page_up(
12579 &mut self,
12580 action: &MovePageUp,
12581 window: &mut Window,
12582 cx: &mut Context<Self>,
12583 ) {
12584 if self.take_rename(true, window, cx).is_some() {
12585 return;
12586 }
12587
12588 if self
12589 .context_menu
12590 .borrow_mut()
12591 .as_mut()
12592 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12593 .unwrap_or(false)
12594 {
12595 return;
12596 }
12597
12598 if matches!(self.mode, EditorMode::SingleLine) {
12599 cx.propagate();
12600 return;
12601 }
12602
12603 let Some(row_count) = self.visible_row_count() else {
12604 return;
12605 };
12606
12607 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12608
12609 let effects = if action.center_cursor {
12610 SelectionEffects::scroll(Autoscroll::center())
12611 } else {
12612 SelectionEffects::default()
12613 };
12614
12615 let text_layout_details = &self.text_layout_details(window);
12616
12617 self.change_selections(effects, window, cx, |s| {
12618 s.move_with(|map, selection| {
12619 if !selection.is_empty() {
12620 selection.goal = SelectionGoal::None;
12621 }
12622 let (cursor, goal) = movement::up_by_rows(
12623 map,
12624 selection.end,
12625 row_count,
12626 selection.goal,
12627 false,
12628 text_layout_details,
12629 );
12630 selection.collapse_to(cursor, goal);
12631 });
12632 });
12633 }
12634
12635 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12636 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12637 let text_layout_details = &self.text_layout_details(window);
12638 self.change_selections(Default::default(), window, cx, |s| {
12639 s.move_heads_with(|map, head, goal| {
12640 movement::up(map, head, goal, false, text_layout_details)
12641 })
12642 })
12643 }
12644
12645 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12646 self.take_rename(true, window, cx);
12647
12648 if self.mode.is_single_line() {
12649 cx.propagate();
12650 return;
12651 }
12652
12653 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12654
12655 let text_layout_details = &self.text_layout_details(window);
12656 let selection_count = self.selections.count();
12657 let first_selection = self.selections.first_anchor();
12658
12659 self.change_selections(Default::default(), window, cx, |s| {
12660 s.move_with(|map, selection| {
12661 if !selection.is_empty() {
12662 selection.goal = SelectionGoal::None;
12663 }
12664 let (cursor, goal) = movement::down(
12665 map,
12666 selection.end,
12667 selection.goal,
12668 false,
12669 text_layout_details,
12670 );
12671 selection.collapse_to(cursor, goal);
12672 });
12673 });
12674
12675 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12676 {
12677 cx.propagate();
12678 }
12679 }
12680
12681 pub fn select_page_down(
12682 &mut self,
12683 _: &SelectPageDown,
12684 window: &mut Window,
12685 cx: &mut Context<Self>,
12686 ) {
12687 let Some(row_count) = self.visible_row_count() else {
12688 return;
12689 };
12690
12691 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12692
12693 let text_layout_details = &self.text_layout_details(window);
12694
12695 self.change_selections(Default::default(), window, cx, |s| {
12696 s.move_heads_with(|map, head, goal| {
12697 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12698 })
12699 })
12700 }
12701
12702 pub fn move_page_down(
12703 &mut self,
12704 action: &MovePageDown,
12705 window: &mut Window,
12706 cx: &mut Context<Self>,
12707 ) {
12708 if self.take_rename(true, window, cx).is_some() {
12709 return;
12710 }
12711
12712 if self
12713 .context_menu
12714 .borrow_mut()
12715 .as_mut()
12716 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12717 .unwrap_or(false)
12718 {
12719 return;
12720 }
12721
12722 if matches!(self.mode, EditorMode::SingleLine) {
12723 cx.propagate();
12724 return;
12725 }
12726
12727 let Some(row_count) = self.visible_row_count() else {
12728 return;
12729 };
12730
12731 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12732
12733 let effects = if action.center_cursor {
12734 SelectionEffects::scroll(Autoscroll::center())
12735 } else {
12736 SelectionEffects::default()
12737 };
12738
12739 let text_layout_details = &self.text_layout_details(window);
12740 self.change_selections(effects, window, cx, |s| {
12741 s.move_with(|map, selection| {
12742 if !selection.is_empty() {
12743 selection.goal = SelectionGoal::None;
12744 }
12745 let (cursor, goal) = movement::down_by_rows(
12746 map,
12747 selection.end,
12748 row_count,
12749 selection.goal,
12750 false,
12751 text_layout_details,
12752 );
12753 selection.collapse_to(cursor, goal);
12754 });
12755 });
12756 }
12757
12758 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12759 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12760 let text_layout_details = &self.text_layout_details(window);
12761 self.change_selections(Default::default(), window, cx, |s| {
12762 s.move_heads_with(|map, head, goal| {
12763 movement::down(map, head, goal, false, text_layout_details)
12764 })
12765 });
12766 }
12767
12768 pub fn context_menu_first(
12769 &mut self,
12770 _: &ContextMenuFirst,
12771 window: &mut Window,
12772 cx: &mut Context<Self>,
12773 ) {
12774 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12775 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12776 }
12777 }
12778
12779 pub fn context_menu_prev(
12780 &mut self,
12781 _: &ContextMenuPrevious,
12782 window: &mut Window,
12783 cx: &mut Context<Self>,
12784 ) {
12785 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12786 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12787 }
12788 }
12789
12790 pub fn context_menu_next(
12791 &mut self,
12792 _: &ContextMenuNext,
12793 window: &mut Window,
12794 cx: &mut Context<Self>,
12795 ) {
12796 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12797 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12798 }
12799 }
12800
12801 pub fn context_menu_last(
12802 &mut self,
12803 _: &ContextMenuLast,
12804 window: &mut Window,
12805 cx: &mut Context<Self>,
12806 ) {
12807 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12808 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12809 }
12810 }
12811
12812 pub fn signature_help_prev(
12813 &mut self,
12814 _: &SignatureHelpPrevious,
12815 _: &mut Window,
12816 cx: &mut Context<Self>,
12817 ) {
12818 if let Some(popover) = self.signature_help_state.popover_mut() {
12819 if popover.current_signature == 0 {
12820 popover.current_signature = popover.signatures.len() - 1;
12821 } else {
12822 popover.current_signature -= 1;
12823 }
12824 cx.notify();
12825 }
12826 }
12827
12828 pub fn signature_help_next(
12829 &mut self,
12830 _: &SignatureHelpNext,
12831 _: &mut Window,
12832 cx: &mut Context<Self>,
12833 ) {
12834 if let Some(popover) = self.signature_help_state.popover_mut() {
12835 if popover.current_signature + 1 == popover.signatures.len() {
12836 popover.current_signature = 0;
12837 } else {
12838 popover.current_signature += 1;
12839 }
12840 cx.notify();
12841 }
12842 }
12843
12844 pub fn move_to_previous_word_start(
12845 &mut self,
12846 _: &MoveToPreviousWordStart,
12847 window: &mut Window,
12848 cx: &mut Context<Self>,
12849 ) {
12850 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12851 self.change_selections(Default::default(), window, cx, |s| {
12852 s.move_cursors_with(|map, head, _| {
12853 (
12854 movement::previous_word_start(map, head),
12855 SelectionGoal::None,
12856 )
12857 });
12858 })
12859 }
12860
12861 pub fn move_to_previous_subword_start(
12862 &mut self,
12863 _: &MoveToPreviousSubwordStart,
12864 window: &mut Window,
12865 cx: &mut Context<Self>,
12866 ) {
12867 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12868 self.change_selections(Default::default(), window, cx, |s| {
12869 s.move_cursors_with(|map, head, _| {
12870 (
12871 movement::previous_subword_start(map, head),
12872 SelectionGoal::None,
12873 )
12874 });
12875 })
12876 }
12877
12878 pub fn select_to_previous_word_start(
12879 &mut self,
12880 _: &SelectToPreviousWordStart,
12881 window: &mut Window,
12882 cx: &mut Context<Self>,
12883 ) {
12884 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12885 self.change_selections(Default::default(), window, cx, |s| {
12886 s.move_heads_with(|map, head, _| {
12887 (
12888 movement::previous_word_start(map, head),
12889 SelectionGoal::None,
12890 )
12891 });
12892 })
12893 }
12894
12895 pub fn select_to_previous_subword_start(
12896 &mut self,
12897 _: &SelectToPreviousSubwordStart,
12898 window: &mut Window,
12899 cx: &mut Context<Self>,
12900 ) {
12901 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12902 self.change_selections(Default::default(), window, cx, |s| {
12903 s.move_heads_with(|map, head, _| {
12904 (
12905 movement::previous_subword_start(map, head),
12906 SelectionGoal::None,
12907 )
12908 });
12909 })
12910 }
12911
12912 pub fn delete_to_previous_word_start(
12913 &mut self,
12914 action: &DeleteToPreviousWordStart,
12915 window: &mut Window,
12916 cx: &mut Context<Self>,
12917 ) {
12918 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12919 self.transact(window, cx, |this, window, cx| {
12920 this.select_autoclose_pair(window, cx);
12921 this.change_selections(Default::default(), window, cx, |s| {
12922 s.move_with(|map, selection| {
12923 if selection.is_empty() {
12924 let cursor = if action.ignore_newlines {
12925 movement::previous_word_start(map, selection.head())
12926 } else {
12927 movement::previous_word_start_or_newline(map, selection.head())
12928 };
12929 selection.set_head(cursor, SelectionGoal::None);
12930 }
12931 });
12932 });
12933 this.insert("", window, cx);
12934 });
12935 }
12936
12937 pub fn delete_to_previous_subword_start(
12938 &mut self,
12939 _: &DeleteToPreviousSubwordStart,
12940 window: &mut Window,
12941 cx: &mut Context<Self>,
12942 ) {
12943 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12944 self.transact(window, cx, |this, window, cx| {
12945 this.select_autoclose_pair(window, cx);
12946 this.change_selections(Default::default(), window, cx, |s| {
12947 s.move_with(|map, selection| {
12948 if selection.is_empty() {
12949 let cursor = movement::previous_subword_start(map, selection.head());
12950 selection.set_head(cursor, SelectionGoal::None);
12951 }
12952 });
12953 });
12954 this.insert("", window, cx);
12955 });
12956 }
12957
12958 pub fn move_to_next_word_end(
12959 &mut self,
12960 _: &MoveToNextWordEnd,
12961 window: &mut Window,
12962 cx: &mut Context<Self>,
12963 ) {
12964 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12965 self.change_selections(Default::default(), window, cx, |s| {
12966 s.move_cursors_with(|map, head, _| {
12967 (movement::next_word_end(map, head), SelectionGoal::None)
12968 });
12969 })
12970 }
12971
12972 pub fn move_to_next_subword_end(
12973 &mut self,
12974 _: &MoveToNextSubwordEnd,
12975 window: &mut Window,
12976 cx: &mut Context<Self>,
12977 ) {
12978 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12979 self.change_selections(Default::default(), window, cx, |s| {
12980 s.move_cursors_with(|map, head, _| {
12981 (movement::next_subword_end(map, head), SelectionGoal::None)
12982 });
12983 })
12984 }
12985
12986 pub fn select_to_next_word_end(
12987 &mut self,
12988 _: &SelectToNextWordEnd,
12989 window: &mut Window,
12990 cx: &mut Context<Self>,
12991 ) {
12992 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12993 self.change_selections(Default::default(), window, cx, |s| {
12994 s.move_heads_with(|map, head, _| {
12995 (movement::next_word_end(map, head), SelectionGoal::None)
12996 });
12997 })
12998 }
12999
13000 pub fn select_to_next_subword_end(
13001 &mut self,
13002 _: &SelectToNextSubwordEnd,
13003 window: &mut Window,
13004 cx: &mut Context<Self>,
13005 ) {
13006 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13007 self.change_selections(Default::default(), window, cx, |s| {
13008 s.move_heads_with(|map, head, _| {
13009 (movement::next_subword_end(map, head), SelectionGoal::None)
13010 });
13011 })
13012 }
13013
13014 pub fn delete_to_next_word_end(
13015 &mut self,
13016 action: &DeleteToNextWordEnd,
13017 window: &mut Window,
13018 cx: &mut Context<Self>,
13019 ) {
13020 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13021 self.transact(window, cx, |this, window, cx| {
13022 this.change_selections(Default::default(), window, cx, |s| {
13023 s.move_with(|map, selection| {
13024 if selection.is_empty() {
13025 let cursor = if action.ignore_newlines {
13026 movement::next_word_end(map, selection.head())
13027 } else {
13028 movement::next_word_end_or_newline(map, selection.head())
13029 };
13030 selection.set_head(cursor, SelectionGoal::None);
13031 }
13032 });
13033 });
13034 this.insert("", window, cx);
13035 });
13036 }
13037
13038 pub fn delete_to_next_subword_end(
13039 &mut self,
13040 _: &DeleteToNextSubwordEnd,
13041 window: &mut Window,
13042 cx: &mut Context<Self>,
13043 ) {
13044 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13045 self.transact(window, cx, |this, window, cx| {
13046 this.change_selections(Default::default(), window, cx, |s| {
13047 s.move_with(|map, selection| {
13048 if selection.is_empty() {
13049 let cursor = movement::next_subword_end(map, selection.head());
13050 selection.set_head(cursor, SelectionGoal::None);
13051 }
13052 });
13053 });
13054 this.insert("", window, cx);
13055 });
13056 }
13057
13058 pub fn move_to_beginning_of_line(
13059 &mut self,
13060 action: &MoveToBeginningOfLine,
13061 window: &mut Window,
13062 cx: &mut Context<Self>,
13063 ) {
13064 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13065 self.change_selections(Default::default(), window, cx, |s| {
13066 s.move_cursors_with(|map, head, _| {
13067 (
13068 movement::indented_line_beginning(
13069 map,
13070 head,
13071 action.stop_at_soft_wraps,
13072 action.stop_at_indent,
13073 ),
13074 SelectionGoal::None,
13075 )
13076 });
13077 })
13078 }
13079
13080 pub fn select_to_beginning_of_line(
13081 &mut self,
13082 action: &SelectToBeginningOfLine,
13083 window: &mut Window,
13084 cx: &mut Context<Self>,
13085 ) {
13086 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13087 self.change_selections(Default::default(), window, cx, |s| {
13088 s.move_heads_with(|map, head, _| {
13089 (
13090 movement::indented_line_beginning(
13091 map,
13092 head,
13093 action.stop_at_soft_wraps,
13094 action.stop_at_indent,
13095 ),
13096 SelectionGoal::None,
13097 )
13098 });
13099 });
13100 }
13101
13102 pub fn delete_to_beginning_of_line(
13103 &mut self,
13104 action: &DeleteToBeginningOfLine,
13105 window: &mut Window,
13106 cx: &mut Context<Self>,
13107 ) {
13108 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13109 self.transact(window, cx, |this, window, cx| {
13110 this.change_selections(Default::default(), window, cx, |s| {
13111 s.move_with(|_, selection| {
13112 selection.reversed = true;
13113 });
13114 });
13115
13116 this.select_to_beginning_of_line(
13117 &SelectToBeginningOfLine {
13118 stop_at_soft_wraps: false,
13119 stop_at_indent: action.stop_at_indent,
13120 },
13121 window,
13122 cx,
13123 );
13124 this.backspace(&Backspace, window, cx);
13125 });
13126 }
13127
13128 pub fn move_to_end_of_line(
13129 &mut self,
13130 action: &MoveToEndOfLine,
13131 window: &mut Window,
13132 cx: &mut Context<Self>,
13133 ) {
13134 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13135 self.change_selections(Default::default(), window, cx, |s| {
13136 s.move_cursors_with(|map, head, _| {
13137 (
13138 movement::line_end(map, head, action.stop_at_soft_wraps),
13139 SelectionGoal::None,
13140 )
13141 });
13142 })
13143 }
13144
13145 pub fn select_to_end_of_line(
13146 &mut self,
13147 action: &SelectToEndOfLine,
13148 window: &mut Window,
13149 cx: &mut Context<Self>,
13150 ) {
13151 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13152 self.change_selections(Default::default(), window, cx, |s| {
13153 s.move_heads_with(|map, head, _| {
13154 (
13155 movement::line_end(map, head, action.stop_at_soft_wraps),
13156 SelectionGoal::None,
13157 )
13158 });
13159 })
13160 }
13161
13162 pub fn delete_to_end_of_line(
13163 &mut self,
13164 _: &DeleteToEndOfLine,
13165 window: &mut Window,
13166 cx: &mut Context<Self>,
13167 ) {
13168 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13169 self.transact(window, cx, |this, window, cx| {
13170 this.select_to_end_of_line(
13171 &SelectToEndOfLine {
13172 stop_at_soft_wraps: false,
13173 },
13174 window,
13175 cx,
13176 );
13177 this.delete(&Delete, window, cx);
13178 });
13179 }
13180
13181 pub fn cut_to_end_of_line(
13182 &mut self,
13183 _: &CutToEndOfLine,
13184 window: &mut Window,
13185 cx: &mut Context<Self>,
13186 ) {
13187 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13188 self.transact(window, cx, |this, window, cx| {
13189 this.select_to_end_of_line(
13190 &SelectToEndOfLine {
13191 stop_at_soft_wraps: false,
13192 },
13193 window,
13194 cx,
13195 );
13196 this.cut(&Cut, window, cx);
13197 });
13198 }
13199
13200 pub fn move_to_start_of_paragraph(
13201 &mut self,
13202 _: &MoveToStartOfParagraph,
13203 window: &mut Window,
13204 cx: &mut Context<Self>,
13205 ) {
13206 if matches!(self.mode, EditorMode::SingleLine) {
13207 cx.propagate();
13208 return;
13209 }
13210 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13211 self.change_selections(Default::default(), window, cx, |s| {
13212 s.move_with(|map, selection| {
13213 selection.collapse_to(
13214 movement::start_of_paragraph(map, selection.head(), 1),
13215 SelectionGoal::None,
13216 )
13217 });
13218 })
13219 }
13220
13221 pub fn move_to_end_of_paragraph(
13222 &mut self,
13223 _: &MoveToEndOfParagraph,
13224 window: &mut Window,
13225 cx: &mut Context<Self>,
13226 ) {
13227 if matches!(self.mode, EditorMode::SingleLine) {
13228 cx.propagate();
13229 return;
13230 }
13231 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13232 self.change_selections(Default::default(), window, cx, |s| {
13233 s.move_with(|map, selection| {
13234 selection.collapse_to(
13235 movement::end_of_paragraph(map, selection.head(), 1),
13236 SelectionGoal::None,
13237 )
13238 });
13239 })
13240 }
13241
13242 pub fn select_to_start_of_paragraph(
13243 &mut self,
13244 _: &SelectToStartOfParagraph,
13245 window: &mut Window,
13246 cx: &mut Context<Self>,
13247 ) {
13248 if matches!(self.mode, EditorMode::SingleLine) {
13249 cx.propagate();
13250 return;
13251 }
13252 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13253 self.change_selections(Default::default(), window, cx, |s| {
13254 s.move_heads_with(|map, head, _| {
13255 (
13256 movement::start_of_paragraph(map, head, 1),
13257 SelectionGoal::None,
13258 )
13259 });
13260 })
13261 }
13262
13263 pub fn select_to_end_of_paragraph(
13264 &mut self,
13265 _: &SelectToEndOfParagraph,
13266 window: &mut Window,
13267 cx: &mut Context<Self>,
13268 ) {
13269 if matches!(self.mode, EditorMode::SingleLine) {
13270 cx.propagate();
13271 return;
13272 }
13273 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13274 self.change_selections(Default::default(), window, cx, |s| {
13275 s.move_heads_with(|map, head, _| {
13276 (
13277 movement::end_of_paragraph(map, head, 1),
13278 SelectionGoal::None,
13279 )
13280 });
13281 })
13282 }
13283
13284 pub fn move_to_start_of_excerpt(
13285 &mut self,
13286 _: &MoveToStartOfExcerpt,
13287 window: &mut Window,
13288 cx: &mut Context<Self>,
13289 ) {
13290 if matches!(self.mode, EditorMode::SingleLine) {
13291 cx.propagate();
13292 return;
13293 }
13294 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13295 self.change_selections(Default::default(), window, cx, |s| {
13296 s.move_with(|map, selection| {
13297 selection.collapse_to(
13298 movement::start_of_excerpt(
13299 map,
13300 selection.head(),
13301 workspace::searchable::Direction::Prev,
13302 ),
13303 SelectionGoal::None,
13304 )
13305 });
13306 })
13307 }
13308
13309 pub fn move_to_start_of_next_excerpt(
13310 &mut self,
13311 _: &MoveToStartOfNextExcerpt,
13312 window: &mut Window,
13313 cx: &mut Context<Self>,
13314 ) {
13315 if matches!(self.mode, EditorMode::SingleLine) {
13316 cx.propagate();
13317 return;
13318 }
13319
13320 self.change_selections(Default::default(), window, cx, |s| {
13321 s.move_with(|map, selection| {
13322 selection.collapse_to(
13323 movement::start_of_excerpt(
13324 map,
13325 selection.head(),
13326 workspace::searchable::Direction::Next,
13327 ),
13328 SelectionGoal::None,
13329 )
13330 });
13331 })
13332 }
13333
13334 pub fn move_to_end_of_excerpt(
13335 &mut self,
13336 _: &MoveToEndOfExcerpt,
13337 window: &mut Window,
13338 cx: &mut Context<Self>,
13339 ) {
13340 if matches!(self.mode, EditorMode::SingleLine) {
13341 cx.propagate();
13342 return;
13343 }
13344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13345 self.change_selections(Default::default(), window, cx, |s| {
13346 s.move_with(|map, selection| {
13347 selection.collapse_to(
13348 movement::end_of_excerpt(
13349 map,
13350 selection.head(),
13351 workspace::searchable::Direction::Next,
13352 ),
13353 SelectionGoal::None,
13354 )
13355 });
13356 })
13357 }
13358
13359 pub fn move_to_end_of_previous_excerpt(
13360 &mut self,
13361 _: &MoveToEndOfPreviousExcerpt,
13362 window: &mut Window,
13363 cx: &mut Context<Self>,
13364 ) {
13365 if matches!(self.mode, EditorMode::SingleLine) {
13366 cx.propagate();
13367 return;
13368 }
13369 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13370 self.change_selections(Default::default(), window, cx, |s| {
13371 s.move_with(|map, selection| {
13372 selection.collapse_to(
13373 movement::end_of_excerpt(
13374 map,
13375 selection.head(),
13376 workspace::searchable::Direction::Prev,
13377 ),
13378 SelectionGoal::None,
13379 )
13380 });
13381 })
13382 }
13383
13384 pub fn select_to_start_of_excerpt(
13385 &mut self,
13386 _: &SelectToStartOfExcerpt,
13387 window: &mut Window,
13388 cx: &mut Context<Self>,
13389 ) {
13390 if matches!(self.mode, EditorMode::SingleLine) {
13391 cx.propagate();
13392 return;
13393 }
13394 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13395 self.change_selections(Default::default(), window, cx, |s| {
13396 s.move_heads_with(|map, head, _| {
13397 (
13398 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13399 SelectionGoal::None,
13400 )
13401 });
13402 })
13403 }
13404
13405 pub fn select_to_start_of_next_excerpt(
13406 &mut self,
13407 _: &SelectToStartOfNextExcerpt,
13408 window: &mut Window,
13409 cx: &mut Context<Self>,
13410 ) {
13411 if matches!(self.mode, EditorMode::SingleLine) {
13412 cx.propagate();
13413 return;
13414 }
13415 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13416 self.change_selections(Default::default(), window, cx, |s| {
13417 s.move_heads_with(|map, head, _| {
13418 (
13419 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13420 SelectionGoal::None,
13421 )
13422 });
13423 })
13424 }
13425
13426 pub fn select_to_end_of_excerpt(
13427 &mut self,
13428 _: &SelectToEndOfExcerpt,
13429 window: &mut Window,
13430 cx: &mut Context<Self>,
13431 ) {
13432 if matches!(self.mode, EditorMode::SingleLine) {
13433 cx.propagate();
13434 return;
13435 }
13436 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13437 self.change_selections(Default::default(), window, cx, |s| {
13438 s.move_heads_with(|map, head, _| {
13439 (
13440 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13441 SelectionGoal::None,
13442 )
13443 });
13444 })
13445 }
13446
13447 pub fn select_to_end_of_previous_excerpt(
13448 &mut self,
13449 _: &SelectToEndOfPreviousExcerpt,
13450 window: &mut Window,
13451 cx: &mut Context<Self>,
13452 ) {
13453 if matches!(self.mode, EditorMode::SingleLine) {
13454 cx.propagate();
13455 return;
13456 }
13457 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13458 self.change_selections(Default::default(), window, cx, |s| {
13459 s.move_heads_with(|map, head, _| {
13460 (
13461 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13462 SelectionGoal::None,
13463 )
13464 });
13465 })
13466 }
13467
13468 pub fn move_to_beginning(
13469 &mut self,
13470 _: &MoveToBeginning,
13471 window: &mut Window,
13472 cx: &mut Context<Self>,
13473 ) {
13474 if matches!(self.mode, EditorMode::SingleLine) {
13475 cx.propagate();
13476 return;
13477 }
13478 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13479 self.change_selections(Default::default(), window, cx, |s| {
13480 s.select_ranges(vec![0..0]);
13481 });
13482 }
13483
13484 pub fn select_to_beginning(
13485 &mut self,
13486 _: &SelectToBeginning,
13487 window: &mut Window,
13488 cx: &mut Context<Self>,
13489 ) {
13490 let mut selection = self.selections.last::<Point>(cx);
13491 selection.set_head(Point::zero(), SelectionGoal::None);
13492 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13493 self.change_selections(Default::default(), window, cx, |s| {
13494 s.select(vec![selection]);
13495 });
13496 }
13497
13498 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13499 if matches!(self.mode, EditorMode::SingleLine) {
13500 cx.propagate();
13501 return;
13502 }
13503 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13504 let cursor = self.buffer.read(cx).read(cx).len();
13505 self.change_selections(Default::default(), window, cx, |s| {
13506 s.select_ranges(vec![cursor..cursor])
13507 });
13508 }
13509
13510 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13511 self.nav_history = nav_history;
13512 }
13513
13514 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13515 self.nav_history.as_ref()
13516 }
13517
13518 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13519 self.push_to_nav_history(
13520 self.selections.newest_anchor().head(),
13521 None,
13522 false,
13523 true,
13524 cx,
13525 );
13526 }
13527
13528 fn push_to_nav_history(
13529 &mut self,
13530 cursor_anchor: Anchor,
13531 new_position: Option<Point>,
13532 is_deactivate: bool,
13533 always: bool,
13534 cx: &mut Context<Self>,
13535 ) {
13536 if let Some(nav_history) = self.nav_history.as_mut() {
13537 let buffer = self.buffer.read(cx).read(cx);
13538 let cursor_position = cursor_anchor.to_point(&buffer);
13539 let scroll_state = self.scroll_manager.anchor();
13540 let scroll_top_row = scroll_state.top_row(&buffer);
13541 drop(buffer);
13542
13543 if let Some(new_position) = new_position {
13544 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13545 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13546 return;
13547 }
13548 }
13549
13550 nav_history.push(
13551 Some(NavigationData {
13552 cursor_anchor,
13553 cursor_position,
13554 scroll_anchor: scroll_state,
13555 scroll_top_row,
13556 }),
13557 cx,
13558 );
13559 cx.emit(EditorEvent::PushedToNavHistory {
13560 anchor: cursor_anchor,
13561 is_deactivate,
13562 })
13563 }
13564 }
13565
13566 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13567 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13568 let buffer = self.buffer.read(cx).snapshot(cx);
13569 let mut selection = self.selections.first::<usize>(cx);
13570 selection.set_head(buffer.len(), SelectionGoal::None);
13571 self.change_selections(Default::default(), window, cx, |s| {
13572 s.select(vec![selection]);
13573 });
13574 }
13575
13576 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13577 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13578 let end = self.buffer.read(cx).read(cx).len();
13579 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13580 s.select_ranges(vec![0..end]);
13581 });
13582 }
13583
13584 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13585 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13586 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13587 let mut selections = self.selections.all::<Point>(cx);
13588 let max_point = display_map.buffer_snapshot.max_point();
13589 for selection in &mut selections {
13590 let rows = selection.spanned_rows(true, &display_map);
13591 selection.start = Point::new(rows.start.0, 0);
13592 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13593 selection.reversed = false;
13594 }
13595 self.change_selections(Default::default(), window, cx, |s| {
13596 s.select(selections);
13597 });
13598 }
13599
13600 pub fn split_selection_into_lines(
13601 &mut self,
13602 action: &SplitSelectionIntoLines,
13603 window: &mut Window,
13604 cx: &mut Context<Self>,
13605 ) {
13606 let selections = self
13607 .selections
13608 .all::<Point>(cx)
13609 .into_iter()
13610 .map(|selection| selection.start..selection.end)
13611 .collect::<Vec<_>>();
13612 self.unfold_ranges(&selections, true, true, cx);
13613
13614 let mut new_selection_ranges = Vec::new();
13615 {
13616 let buffer = self.buffer.read(cx).read(cx);
13617 for selection in selections {
13618 for row in selection.start.row..selection.end.row {
13619 let line_start = Point::new(row, 0);
13620 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13621
13622 if action.keep_selections {
13623 // Keep the selection range for each line
13624 let selection_start = if row == selection.start.row {
13625 selection.start
13626 } else {
13627 line_start
13628 };
13629 new_selection_ranges.push(selection_start..line_end);
13630 } else {
13631 // Collapse to cursor at end of line
13632 new_selection_ranges.push(line_end..line_end);
13633 }
13634 }
13635
13636 let is_multiline_selection = selection.start.row != selection.end.row;
13637 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13638 // so this action feels more ergonomic when paired with other selection operations
13639 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13640 if !should_skip_last {
13641 if action.keep_selections {
13642 if is_multiline_selection {
13643 let line_start = Point::new(selection.end.row, 0);
13644 new_selection_ranges.push(line_start..selection.end);
13645 } else {
13646 new_selection_ranges.push(selection.start..selection.end);
13647 }
13648 } else {
13649 new_selection_ranges.push(selection.end..selection.end);
13650 }
13651 }
13652 }
13653 }
13654 self.change_selections(Default::default(), window, cx, |s| {
13655 s.select_ranges(new_selection_ranges);
13656 });
13657 }
13658
13659 pub fn add_selection_above(
13660 &mut self,
13661 _: &AddSelectionAbove,
13662 window: &mut Window,
13663 cx: &mut Context<Self>,
13664 ) {
13665 self.add_selection(true, window, cx);
13666 }
13667
13668 pub fn add_selection_below(
13669 &mut self,
13670 _: &AddSelectionBelow,
13671 window: &mut Window,
13672 cx: &mut Context<Self>,
13673 ) {
13674 self.add_selection(false, window, cx);
13675 }
13676
13677 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13678 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13679
13680 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13681 let all_selections = self.selections.all::<Point>(cx);
13682 let text_layout_details = self.text_layout_details(window);
13683
13684 let (mut columnar_selections, new_selections_to_columnarize) = {
13685 if let Some(state) = self.add_selections_state.as_ref() {
13686 let columnar_selection_ids: HashSet<_> = state
13687 .groups
13688 .iter()
13689 .flat_map(|group| group.stack.iter())
13690 .copied()
13691 .collect();
13692
13693 all_selections
13694 .into_iter()
13695 .partition(|s| columnar_selection_ids.contains(&s.id))
13696 } else {
13697 (Vec::new(), all_selections)
13698 }
13699 };
13700
13701 let mut state = self
13702 .add_selections_state
13703 .take()
13704 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13705
13706 for selection in new_selections_to_columnarize {
13707 let range = selection.display_range(&display_map).sorted();
13708 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13709 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13710 let positions = start_x.min(end_x)..start_x.max(end_x);
13711 let mut stack = Vec::new();
13712 for row in range.start.row().0..=range.end.row().0 {
13713 if let Some(selection) = self.selections.build_columnar_selection(
13714 &display_map,
13715 DisplayRow(row),
13716 &positions,
13717 selection.reversed,
13718 &text_layout_details,
13719 ) {
13720 stack.push(selection.id);
13721 columnar_selections.push(selection);
13722 }
13723 }
13724 if !stack.is_empty() {
13725 if above {
13726 stack.reverse();
13727 }
13728 state.groups.push(AddSelectionsGroup { above, stack });
13729 }
13730 }
13731
13732 let mut final_selections = Vec::new();
13733 let end_row = if above {
13734 DisplayRow(0)
13735 } else {
13736 display_map.max_point().row()
13737 };
13738
13739 let mut last_added_item_per_group = HashMap::default();
13740 for group in state.groups.iter_mut() {
13741 if let Some(last_id) = group.stack.last() {
13742 last_added_item_per_group.insert(*last_id, group);
13743 }
13744 }
13745
13746 for selection in columnar_selections {
13747 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13748 if above == group.above {
13749 let range = selection.display_range(&display_map).sorted();
13750 debug_assert_eq!(range.start.row(), range.end.row());
13751 let mut row = range.start.row();
13752 let positions =
13753 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13754 px(start)..px(end)
13755 } else {
13756 let start_x =
13757 display_map.x_for_display_point(range.start, &text_layout_details);
13758 let end_x =
13759 display_map.x_for_display_point(range.end, &text_layout_details);
13760 start_x.min(end_x)..start_x.max(end_x)
13761 };
13762
13763 let mut maybe_new_selection = None;
13764 while row != end_row {
13765 if above {
13766 row.0 -= 1;
13767 } else {
13768 row.0 += 1;
13769 }
13770 if let Some(new_selection) = self.selections.build_columnar_selection(
13771 &display_map,
13772 row,
13773 &positions,
13774 selection.reversed,
13775 &text_layout_details,
13776 ) {
13777 maybe_new_selection = Some(new_selection);
13778 break;
13779 }
13780 }
13781
13782 if let Some(new_selection) = maybe_new_selection {
13783 group.stack.push(new_selection.id);
13784 if above {
13785 final_selections.push(new_selection);
13786 final_selections.push(selection);
13787 } else {
13788 final_selections.push(selection);
13789 final_selections.push(new_selection);
13790 }
13791 } else {
13792 final_selections.push(selection);
13793 }
13794 } else {
13795 group.stack.pop();
13796 }
13797 } else {
13798 final_selections.push(selection);
13799 }
13800 }
13801
13802 self.change_selections(Default::default(), window, cx, |s| {
13803 s.select(final_selections);
13804 });
13805
13806 let final_selection_ids: HashSet<_> = self
13807 .selections
13808 .all::<Point>(cx)
13809 .iter()
13810 .map(|s| s.id)
13811 .collect();
13812 state.groups.retain_mut(|group| {
13813 // selections might get merged above so we remove invalid items from stacks
13814 group.stack.retain(|id| final_selection_ids.contains(id));
13815
13816 // single selection in stack can be treated as initial state
13817 group.stack.len() > 1
13818 });
13819
13820 if !state.groups.is_empty() {
13821 self.add_selections_state = Some(state);
13822 }
13823 }
13824
13825 fn select_match_ranges(
13826 &mut self,
13827 range: Range<usize>,
13828 reversed: bool,
13829 replace_newest: bool,
13830 auto_scroll: Option<Autoscroll>,
13831 window: &mut Window,
13832 cx: &mut Context<Editor>,
13833 ) {
13834 self.unfold_ranges(
13835 std::slice::from_ref(&range),
13836 false,
13837 auto_scroll.is_some(),
13838 cx,
13839 );
13840 let effects = if let Some(scroll) = auto_scroll {
13841 SelectionEffects::scroll(scroll)
13842 } else {
13843 SelectionEffects::no_scroll()
13844 };
13845 self.change_selections(effects, window, cx, |s| {
13846 if replace_newest {
13847 s.delete(s.newest_anchor().id);
13848 }
13849 if reversed {
13850 s.insert_range(range.end..range.start);
13851 } else {
13852 s.insert_range(range);
13853 }
13854 });
13855 }
13856
13857 pub fn select_next_match_internal(
13858 &mut self,
13859 display_map: &DisplaySnapshot,
13860 replace_newest: bool,
13861 autoscroll: Option<Autoscroll>,
13862 window: &mut Window,
13863 cx: &mut Context<Self>,
13864 ) -> Result<()> {
13865 let buffer = &display_map.buffer_snapshot;
13866 let mut selections = self.selections.all::<usize>(cx);
13867 if let Some(mut select_next_state) = self.select_next_state.take() {
13868 let query = &select_next_state.query;
13869 if !select_next_state.done {
13870 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13871 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13872 let mut next_selected_range = None;
13873
13874 let bytes_after_last_selection =
13875 buffer.bytes_in_range(last_selection.end..buffer.len());
13876 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13877 let query_matches = query
13878 .stream_find_iter(bytes_after_last_selection)
13879 .map(|result| (last_selection.end, result))
13880 .chain(
13881 query
13882 .stream_find_iter(bytes_before_first_selection)
13883 .map(|result| (0, result)),
13884 );
13885
13886 for (start_offset, query_match) in query_matches {
13887 let query_match = query_match.unwrap(); // can only fail due to I/O
13888 let offset_range =
13889 start_offset + query_match.start()..start_offset + query_match.end();
13890
13891 if !select_next_state.wordwise
13892 || (!buffer.is_inside_word(offset_range.start, false)
13893 && !buffer.is_inside_word(offset_range.end, false))
13894 {
13895 // TODO: This is n^2, because we might check all the selections
13896 if !selections
13897 .iter()
13898 .any(|selection| selection.range().overlaps(&offset_range))
13899 {
13900 next_selected_range = Some(offset_range);
13901 break;
13902 }
13903 }
13904 }
13905
13906 if let Some(next_selected_range) = next_selected_range {
13907 self.select_match_ranges(
13908 next_selected_range,
13909 last_selection.reversed,
13910 replace_newest,
13911 autoscroll,
13912 window,
13913 cx,
13914 );
13915 } else {
13916 select_next_state.done = true;
13917 }
13918 }
13919
13920 self.select_next_state = Some(select_next_state);
13921 } else {
13922 let mut only_carets = true;
13923 let mut same_text_selected = true;
13924 let mut selected_text = None;
13925
13926 let mut selections_iter = selections.iter().peekable();
13927 while let Some(selection) = selections_iter.next() {
13928 if selection.start != selection.end {
13929 only_carets = false;
13930 }
13931
13932 if same_text_selected {
13933 if selected_text.is_none() {
13934 selected_text =
13935 Some(buffer.text_for_range(selection.range()).collect::<String>());
13936 }
13937
13938 if let Some(next_selection) = selections_iter.peek() {
13939 if next_selection.range().len() == selection.range().len() {
13940 let next_selected_text = buffer
13941 .text_for_range(next_selection.range())
13942 .collect::<String>();
13943 if Some(next_selected_text) != selected_text {
13944 same_text_selected = false;
13945 selected_text = None;
13946 }
13947 } else {
13948 same_text_selected = false;
13949 selected_text = None;
13950 }
13951 }
13952 }
13953 }
13954
13955 if only_carets {
13956 for selection in &mut selections {
13957 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13958 selection.start = word_range.start;
13959 selection.end = word_range.end;
13960 selection.goal = SelectionGoal::None;
13961 selection.reversed = false;
13962 self.select_match_ranges(
13963 selection.start..selection.end,
13964 selection.reversed,
13965 replace_newest,
13966 autoscroll,
13967 window,
13968 cx,
13969 );
13970 }
13971
13972 if selections.len() == 1 {
13973 let selection = selections
13974 .last()
13975 .expect("ensured that there's only one selection");
13976 let query = buffer
13977 .text_for_range(selection.start..selection.end)
13978 .collect::<String>();
13979 let is_empty = query.is_empty();
13980 let select_state = SelectNextState {
13981 query: AhoCorasick::new(&[query])?,
13982 wordwise: true,
13983 done: is_empty,
13984 };
13985 self.select_next_state = Some(select_state);
13986 } else {
13987 self.select_next_state = None;
13988 }
13989 } else if let Some(selected_text) = selected_text {
13990 self.select_next_state = Some(SelectNextState {
13991 query: AhoCorasick::new(&[selected_text])?,
13992 wordwise: false,
13993 done: false,
13994 });
13995 self.select_next_match_internal(
13996 display_map,
13997 replace_newest,
13998 autoscroll,
13999 window,
14000 cx,
14001 )?;
14002 }
14003 }
14004 Ok(())
14005 }
14006
14007 pub fn select_all_matches(
14008 &mut self,
14009 _action: &SelectAllMatches,
14010 window: &mut Window,
14011 cx: &mut Context<Self>,
14012 ) -> Result<()> {
14013 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14014
14015 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14016
14017 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14018 let Some(select_next_state) = self.select_next_state.as_mut() else {
14019 return Ok(());
14020 };
14021 if select_next_state.done {
14022 return Ok(());
14023 }
14024
14025 let mut new_selections = Vec::new();
14026
14027 let reversed = self.selections.oldest::<usize>(cx).reversed;
14028 let buffer = &display_map.buffer_snapshot;
14029 let query_matches = select_next_state
14030 .query
14031 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14032
14033 for query_match in query_matches.into_iter() {
14034 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14035 let offset_range = if reversed {
14036 query_match.end()..query_match.start()
14037 } else {
14038 query_match.start()..query_match.end()
14039 };
14040
14041 if !select_next_state.wordwise
14042 || (!buffer.is_inside_word(offset_range.start, false)
14043 && !buffer.is_inside_word(offset_range.end, false))
14044 {
14045 new_selections.push(offset_range.start..offset_range.end);
14046 }
14047 }
14048
14049 select_next_state.done = true;
14050
14051 if new_selections.is_empty() {
14052 log::error!("bug: new_selections is empty in select_all_matches");
14053 return Ok(());
14054 }
14055
14056 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14057 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14058 selections.select_ranges(new_selections)
14059 });
14060
14061 Ok(())
14062 }
14063
14064 pub fn select_next(
14065 &mut self,
14066 action: &SelectNext,
14067 window: &mut Window,
14068 cx: &mut Context<Self>,
14069 ) -> Result<()> {
14070 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14071 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14072 self.select_next_match_internal(
14073 &display_map,
14074 action.replace_newest,
14075 Some(Autoscroll::newest()),
14076 window,
14077 cx,
14078 )?;
14079 Ok(())
14080 }
14081
14082 pub fn select_previous(
14083 &mut self,
14084 action: &SelectPrevious,
14085 window: &mut Window,
14086 cx: &mut Context<Self>,
14087 ) -> Result<()> {
14088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14089 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14090 let buffer = &display_map.buffer_snapshot;
14091 let mut selections = self.selections.all::<usize>(cx);
14092 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14093 let query = &select_prev_state.query;
14094 if !select_prev_state.done {
14095 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14096 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14097 let mut next_selected_range = None;
14098 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14099 let bytes_before_last_selection =
14100 buffer.reversed_bytes_in_range(0..last_selection.start);
14101 let bytes_after_first_selection =
14102 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14103 let query_matches = query
14104 .stream_find_iter(bytes_before_last_selection)
14105 .map(|result| (last_selection.start, result))
14106 .chain(
14107 query
14108 .stream_find_iter(bytes_after_first_selection)
14109 .map(|result| (buffer.len(), result)),
14110 );
14111 for (end_offset, query_match) in query_matches {
14112 let query_match = query_match.unwrap(); // can only fail due to I/O
14113 let offset_range =
14114 end_offset - query_match.end()..end_offset - query_match.start();
14115
14116 if !select_prev_state.wordwise
14117 || (!buffer.is_inside_word(offset_range.start, false)
14118 && !buffer.is_inside_word(offset_range.end, false))
14119 {
14120 next_selected_range = Some(offset_range);
14121 break;
14122 }
14123 }
14124
14125 if let Some(next_selected_range) = next_selected_range {
14126 self.select_match_ranges(
14127 next_selected_range,
14128 last_selection.reversed,
14129 action.replace_newest,
14130 Some(Autoscroll::newest()),
14131 window,
14132 cx,
14133 );
14134 } else {
14135 select_prev_state.done = true;
14136 }
14137 }
14138
14139 self.select_prev_state = Some(select_prev_state);
14140 } else {
14141 let mut only_carets = true;
14142 let mut same_text_selected = true;
14143 let mut selected_text = None;
14144
14145 let mut selections_iter = selections.iter().peekable();
14146 while let Some(selection) = selections_iter.next() {
14147 if selection.start != selection.end {
14148 only_carets = false;
14149 }
14150
14151 if same_text_selected {
14152 if selected_text.is_none() {
14153 selected_text =
14154 Some(buffer.text_for_range(selection.range()).collect::<String>());
14155 }
14156
14157 if let Some(next_selection) = selections_iter.peek() {
14158 if next_selection.range().len() == selection.range().len() {
14159 let next_selected_text = buffer
14160 .text_for_range(next_selection.range())
14161 .collect::<String>();
14162 if Some(next_selected_text) != selected_text {
14163 same_text_selected = false;
14164 selected_text = None;
14165 }
14166 } else {
14167 same_text_selected = false;
14168 selected_text = None;
14169 }
14170 }
14171 }
14172 }
14173
14174 if only_carets {
14175 for selection in &mut selections {
14176 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14177 selection.start = word_range.start;
14178 selection.end = word_range.end;
14179 selection.goal = SelectionGoal::None;
14180 selection.reversed = false;
14181 self.select_match_ranges(
14182 selection.start..selection.end,
14183 selection.reversed,
14184 action.replace_newest,
14185 Some(Autoscroll::newest()),
14186 window,
14187 cx,
14188 );
14189 }
14190 if selections.len() == 1 {
14191 let selection = selections
14192 .last()
14193 .expect("ensured that there's only one selection");
14194 let query = buffer
14195 .text_for_range(selection.start..selection.end)
14196 .collect::<String>();
14197 let is_empty = query.is_empty();
14198 let select_state = SelectNextState {
14199 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14200 wordwise: true,
14201 done: is_empty,
14202 };
14203 self.select_prev_state = Some(select_state);
14204 } else {
14205 self.select_prev_state = None;
14206 }
14207 } else if let Some(selected_text) = selected_text {
14208 self.select_prev_state = Some(SelectNextState {
14209 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14210 wordwise: false,
14211 done: false,
14212 });
14213 self.select_previous(action, window, cx)?;
14214 }
14215 }
14216 Ok(())
14217 }
14218
14219 pub fn find_next_match(
14220 &mut self,
14221 _: &FindNextMatch,
14222 window: &mut Window,
14223 cx: &mut Context<Self>,
14224 ) -> Result<()> {
14225 let selections = self.selections.disjoint_anchors();
14226 match selections.first() {
14227 Some(first) if selections.len() >= 2 => {
14228 self.change_selections(Default::default(), window, cx, |s| {
14229 s.select_ranges([first.range()]);
14230 });
14231 }
14232 _ => self.select_next(
14233 &SelectNext {
14234 replace_newest: true,
14235 },
14236 window,
14237 cx,
14238 )?,
14239 }
14240 Ok(())
14241 }
14242
14243 pub fn find_previous_match(
14244 &mut self,
14245 _: &FindPreviousMatch,
14246 window: &mut Window,
14247 cx: &mut Context<Self>,
14248 ) -> Result<()> {
14249 let selections = self.selections.disjoint_anchors();
14250 match selections.last() {
14251 Some(last) if selections.len() >= 2 => {
14252 self.change_selections(Default::default(), window, cx, |s| {
14253 s.select_ranges([last.range()]);
14254 });
14255 }
14256 _ => self.select_previous(
14257 &SelectPrevious {
14258 replace_newest: true,
14259 },
14260 window,
14261 cx,
14262 )?,
14263 }
14264 Ok(())
14265 }
14266
14267 pub fn toggle_comments(
14268 &mut self,
14269 action: &ToggleComments,
14270 window: &mut Window,
14271 cx: &mut Context<Self>,
14272 ) {
14273 if self.read_only(cx) {
14274 return;
14275 }
14276 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14277 let text_layout_details = &self.text_layout_details(window);
14278 self.transact(window, cx, |this, window, cx| {
14279 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14280 let mut edits = Vec::new();
14281 let mut selection_edit_ranges = Vec::new();
14282 let mut last_toggled_row = None;
14283 let snapshot = this.buffer.read(cx).read(cx);
14284 let empty_str: Arc<str> = Arc::default();
14285 let mut suffixes_inserted = Vec::new();
14286 let ignore_indent = action.ignore_indent;
14287
14288 fn comment_prefix_range(
14289 snapshot: &MultiBufferSnapshot,
14290 row: MultiBufferRow,
14291 comment_prefix: &str,
14292 comment_prefix_whitespace: &str,
14293 ignore_indent: bool,
14294 ) -> Range<Point> {
14295 let indent_size = if ignore_indent {
14296 0
14297 } else {
14298 snapshot.indent_size_for_line(row).len
14299 };
14300
14301 let start = Point::new(row.0, indent_size);
14302
14303 let mut line_bytes = snapshot
14304 .bytes_in_range(start..snapshot.max_point())
14305 .flatten()
14306 .copied();
14307
14308 // If this line currently begins with the line comment prefix, then record
14309 // the range containing the prefix.
14310 if line_bytes
14311 .by_ref()
14312 .take(comment_prefix.len())
14313 .eq(comment_prefix.bytes())
14314 {
14315 // Include any whitespace that matches the comment prefix.
14316 let matching_whitespace_len = line_bytes
14317 .zip(comment_prefix_whitespace.bytes())
14318 .take_while(|(a, b)| a == b)
14319 .count() as u32;
14320 let end = Point::new(
14321 start.row,
14322 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14323 );
14324 start..end
14325 } else {
14326 start..start
14327 }
14328 }
14329
14330 fn comment_suffix_range(
14331 snapshot: &MultiBufferSnapshot,
14332 row: MultiBufferRow,
14333 comment_suffix: &str,
14334 comment_suffix_has_leading_space: bool,
14335 ) -> Range<Point> {
14336 let end = Point::new(row.0, snapshot.line_len(row));
14337 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14338
14339 let mut line_end_bytes = snapshot
14340 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14341 .flatten()
14342 .copied();
14343
14344 let leading_space_len = if suffix_start_column > 0
14345 && line_end_bytes.next() == Some(b' ')
14346 && comment_suffix_has_leading_space
14347 {
14348 1
14349 } else {
14350 0
14351 };
14352
14353 // If this line currently begins with the line comment prefix, then record
14354 // the range containing the prefix.
14355 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14356 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14357 start..end
14358 } else {
14359 end..end
14360 }
14361 }
14362
14363 // TODO: Handle selections that cross excerpts
14364 for selection in &mut selections {
14365 let start_column = snapshot
14366 .indent_size_for_line(MultiBufferRow(selection.start.row))
14367 .len;
14368 let language = if let Some(language) =
14369 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14370 {
14371 language
14372 } else {
14373 continue;
14374 };
14375
14376 selection_edit_ranges.clear();
14377
14378 // If multiple selections contain a given row, avoid processing that
14379 // row more than once.
14380 let mut start_row = MultiBufferRow(selection.start.row);
14381 if last_toggled_row == Some(start_row) {
14382 start_row = start_row.next_row();
14383 }
14384 let end_row =
14385 if selection.end.row > selection.start.row && selection.end.column == 0 {
14386 MultiBufferRow(selection.end.row - 1)
14387 } else {
14388 MultiBufferRow(selection.end.row)
14389 };
14390 last_toggled_row = Some(end_row);
14391
14392 if start_row > end_row {
14393 continue;
14394 }
14395
14396 // If the language has line comments, toggle those.
14397 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14398
14399 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14400 if ignore_indent {
14401 full_comment_prefixes = full_comment_prefixes
14402 .into_iter()
14403 .map(|s| Arc::from(s.trim_end()))
14404 .collect();
14405 }
14406
14407 if !full_comment_prefixes.is_empty() {
14408 let first_prefix = full_comment_prefixes
14409 .first()
14410 .expect("prefixes is non-empty");
14411 let prefix_trimmed_lengths = full_comment_prefixes
14412 .iter()
14413 .map(|p| p.trim_end_matches(' ').len())
14414 .collect::<SmallVec<[usize; 4]>>();
14415
14416 let mut all_selection_lines_are_comments = true;
14417
14418 for row in start_row.0..=end_row.0 {
14419 let row = MultiBufferRow(row);
14420 if start_row < end_row && snapshot.is_line_blank(row) {
14421 continue;
14422 }
14423
14424 let prefix_range = full_comment_prefixes
14425 .iter()
14426 .zip(prefix_trimmed_lengths.iter().copied())
14427 .map(|(prefix, trimmed_prefix_len)| {
14428 comment_prefix_range(
14429 snapshot.deref(),
14430 row,
14431 &prefix[..trimmed_prefix_len],
14432 &prefix[trimmed_prefix_len..],
14433 ignore_indent,
14434 )
14435 })
14436 .max_by_key(|range| range.end.column - range.start.column)
14437 .expect("prefixes is non-empty");
14438
14439 if prefix_range.is_empty() {
14440 all_selection_lines_are_comments = false;
14441 }
14442
14443 selection_edit_ranges.push(prefix_range);
14444 }
14445
14446 if all_selection_lines_are_comments {
14447 edits.extend(
14448 selection_edit_ranges
14449 .iter()
14450 .cloned()
14451 .map(|range| (range, empty_str.clone())),
14452 );
14453 } else {
14454 let min_column = selection_edit_ranges
14455 .iter()
14456 .map(|range| range.start.column)
14457 .min()
14458 .unwrap_or(0);
14459 edits.extend(selection_edit_ranges.iter().map(|range| {
14460 let position = Point::new(range.start.row, min_column);
14461 (position..position, first_prefix.clone())
14462 }));
14463 }
14464 } else if let Some(BlockCommentConfig {
14465 start: full_comment_prefix,
14466 end: comment_suffix,
14467 ..
14468 }) = language.block_comment()
14469 {
14470 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14471 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14472 let prefix_range = comment_prefix_range(
14473 snapshot.deref(),
14474 start_row,
14475 comment_prefix,
14476 comment_prefix_whitespace,
14477 ignore_indent,
14478 );
14479 let suffix_range = comment_suffix_range(
14480 snapshot.deref(),
14481 end_row,
14482 comment_suffix.trim_start_matches(' '),
14483 comment_suffix.starts_with(' '),
14484 );
14485
14486 if prefix_range.is_empty() || suffix_range.is_empty() {
14487 edits.push((
14488 prefix_range.start..prefix_range.start,
14489 full_comment_prefix.clone(),
14490 ));
14491 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14492 suffixes_inserted.push((end_row, comment_suffix.len()));
14493 } else {
14494 edits.push((prefix_range, empty_str.clone()));
14495 edits.push((suffix_range, empty_str.clone()));
14496 }
14497 } else {
14498 continue;
14499 }
14500 }
14501
14502 drop(snapshot);
14503 this.buffer.update(cx, |buffer, cx| {
14504 buffer.edit(edits, None, cx);
14505 });
14506
14507 // Adjust selections so that they end before any comment suffixes that
14508 // were inserted.
14509 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14510 let mut selections = this.selections.all::<Point>(cx);
14511 let snapshot = this.buffer.read(cx).read(cx);
14512 for selection in &mut selections {
14513 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14514 match row.cmp(&MultiBufferRow(selection.end.row)) {
14515 Ordering::Less => {
14516 suffixes_inserted.next();
14517 continue;
14518 }
14519 Ordering::Greater => break,
14520 Ordering::Equal => {
14521 if selection.end.column == snapshot.line_len(row) {
14522 if selection.is_empty() {
14523 selection.start.column -= suffix_len as u32;
14524 }
14525 selection.end.column -= suffix_len as u32;
14526 }
14527 break;
14528 }
14529 }
14530 }
14531 }
14532
14533 drop(snapshot);
14534 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14535
14536 let selections = this.selections.all::<Point>(cx);
14537 let selections_on_single_row = selections.windows(2).all(|selections| {
14538 selections[0].start.row == selections[1].start.row
14539 && selections[0].end.row == selections[1].end.row
14540 && selections[0].start.row == selections[0].end.row
14541 });
14542 let selections_selecting = selections
14543 .iter()
14544 .any(|selection| selection.start != selection.end);
14545 let advance_downwards = action.advance_downwards
14546 && selections_on_single_row
14547 && !selections_selecting
14548 && !matches!(this.mode, EditorMode::SingleLine);
14549
14550 if advance_downwards {
14551 let snapshot = this.buffer.read(cx).snapshot(cx);
14552
14553 this.change_selections(Default::default(), window, cx, |s| {
14554 s.move_cursors_with(|display_snapshot, display_point, _| {
14555 let mut point = display_point.to_point(display_snapshot);
14556 point.row += 1;
14557 point = snapshot.clip_point(point, Bias::Left);
14558 let display_point = point.to_display_point(display_snapshot);
14559 let goal = SelectionGoal::HorizontalPosition(
14560 display_snapshot
14561 .x_for_display_point(display_point, text_layout_details)
14562 .into(),
14563 );
14564 (display_point, goal)
14565 })
14566 });
14567 }
14568 });
14569 }
14570
14571 pub fn select_enclosing_symbol(
14572 &mut self,
14573 _: &SelectEnclosingSymbol,
14574 window: &mut Window,
14575 cx: &mut Context<Self>,
14576 ) {
14577 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14578
14579 let buffer = self.buffer.read(cx).snapshot(cx);
14580 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14581
14582 fn update_selection(
14583 selection: &Selection<usize>,
14584 buffer_snap: &MultiBufferSnapshot,
14585 ) -> Option<Selection<usize>> {
14586 let cursor = selection.head();
14587 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14588 for symbol in symbols.iter().rev() {
14589 let start = symbol.range.start.to_offset(buffer_snap);
14590 let end = symbol.range.end.to_offset(buffer_snap);
14591 let new_range = start..end;
14592 if start < selection.start || end > selection.end {
14593 return Some(Selection {
14594 id: selection.id,
14595 start: new_range.start,
14596 end: new_range.end,
14597 goal: SelectionGoal::None,
14598 reversed: selection.reversed,
14599 });
14600 }
14601 }
14602 None
14603 }
14604
14605 let mut selected_larger_symbol = false;
14606 let new_selections = old_selections
14607 .iter()
14608 .map(|selection| match update_selection(selection, &buffer) {
14609 Some(new_selection) => {
14610 if new_selection.range() != selection.range() {
14611 selected_larger_symbol = true;
14612 }
14613 new_selection
14614 }
14615 None => selection.clone(),
14616 })
14617 .collect::<Vec<_>>();
14618
14619 if selected_larger_symbol {
14620 self.change_selections(Default::default(), window, cx, |s| {
14621 s.select(new_selections);
14622 });
14623 }
14624 }
14625
14626 pub fn select_larger_syntax_node(
14627 &mut self,
14628 _: &SelectLargerSyntaxNode,
14629 window: &mut Window,
14630 cx: &mut Context<Self>,
14631 ) {
14632 let Some(visible_row_count) = self.visible_row_count() else {
14633 return;
14634 };
14635 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14636 if old_selections.is_empty() {
14637 return;
14638 }
14639
14640 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14641
14642 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14643 let buffer = self.buffer.read(cx).snapshot(cx);
14644
14645 let mut selected_larger_node = false;
14646 let mut new_selections = old_selections
14647 .iter()
14648 .map(|selection| {
14649 let old_range = selection.start..selection.end;
14650
14651 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14652 // manually select word at selection
14653 if ["string_content", "inline"].contains(&node.kind()) {
14654 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14655 // ignore if word is already selected
14656 if !word_range.is_empty() && old_range != word_range {
14657 let (last_word_range, _) =
14658 buffer.surrounding_word(old_range.end, false);
14659 // only select word if start and end point belongs to same word
14660 if word_range == last_word_range {
14661 selected_larger_node = true;
14662 return Selection {
14663 id: selection.id,
14664 start: word_range.start,
14665 end: word_range.end,
14666 goal: SelectionGoal::None,
14667 reversed: selection.reversed,
14668 };
14669 }
14670 }
14671 }
14672 }
14673
14674 let mut new_range = old_range.clone();
14675 while let Some((_node, containing_range)) =
14676 buffer.syntax_ancestor(new_range.clone())
14677 {
14678 new_range = match containing_range {
14679 MultiOrSingleBufferOffsetRange::Single(_) => break,
14680 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14681 };
14682 if !display_map.intersects_fold(new_range.start)
14683 && !display_map.intersects_fold(new_range.end)
14684 {
14685 break;
14686 }
14687 }
14688
14689 selected_larger_node |= new_range != old_range;
14690 Selection {
14691 id: selection.id,
14692 start: new_range.start,
14693 end: new_range.end,
14694 goal: SelectionGoal::None,
14695 reversed: selection.reversed,
14696 }
14697 })
14698 .collect::<Vec<_>>();
14699
14700 if !selected_larger_node {
14701 return; // don't put this call in the history
14702 }
14703
14704 // scroll based on transformation done to the last selection created by the user
14705 let (last_old, last_new) = old_selections
14706 .last()
14707 .zip(new_selections.last().cloned())
14708 .expect("old_selections isn't empty");
14709
14710 // revert selection
14711 let is_selection_reversed = {
14712 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14713 new_selections.last_mut().expect("checked above").reversed =
14714 should_newest_selection_be_reversed;
14715 should_newest_selection_be_reversed
14716 };
14717
14718 if selected_larger_node {
14719 self.select_syntax_node_history.disable_clearing = true;
14720 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14721 s.select(new_selections.clone());
14722 });
14723 self.select_syntax_node_history.disable_clearing = false;
14724 }
14725
14726 let start_row = last_new.start.to_display_point(&display_map).row().0;
14727 let end_row = last_new.end.to_display_point(&display_map).row().0;
14728 let selection_height = end_row - start_row + 1;
14729 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14730
14731 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14732 let scroll_behavior = if fits_on_the_screen {
14733 self.request_autoscroll(Autoscroll::fit(), cx);
14734 SelectSyntaxNodeScrollBehavior::FitSelection
14735 } else if is_selection_reversed {
14736 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14737 SelectSyntaxNodeScrollBehavior::CursorTop
14738 } else {
14739 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14740 SelectSyntaxNodeScrollBehavior::CursorBottom
14741 };
14742
14743 self.select_syntax_node_history.push((
14744 old_selections,
14745 scroll_behavior,
14746 is_selection_reversed,
14747 ));
14748 }
14749
14750 pub fn select_smaller_syntax_node(
14751 &mut self,
14752 _: &SelectSmallerSyntaxNode,
14753 window: &mut Window,
14754 cx: &mut Context<Self>,
14755 ) {
14756 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14757
14758 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14759 self.select_syntax_node_history.pop()
14760 {
14761 if let Some(selection) = selections.last_mut() {
14762 selection.reversed = is_selection_reversed;
14763 }
14764
14765 self.select_syntax_node_history.disable_clearing = true;
14766 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14767 s.select(selections.to_vec());
14768 });
14769 self.select_syntax_node_history.disable_clearing = false;
14770
14771 match scroll_behavior {
14772 SelectSyntaxNodeScrollBehavior::CursorTop => {
14773 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14774 }
14775 SelectSyntaxNodeScrollBehavior::FitSelection => {
14776 self.request_autoscroll(Autoscroll::fit(), cx);
14777 }
14778 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14779 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14780 }
14781 }
14782 }
14783 }
14784
14785 pub fn unwrap_syntax_node(
14786 &mut self,
14787 _: &UnwrapSyntaxNode,
14788 window: &mut Window,
14789 cx: &mut Context<Self>,
14790 ) {
14791 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14792
14793 let buffer = self.buffer.read(cx).snapshot(cx);
14794 let selections = self
14795 .selections
14796 .all::<usize>(cx)
14797 .into_iter()
14798 // subtracting the offset requires sorting
14799 .sorted_by_key(|i| i.start);
14800
14801 let full_edits = selections
14802 .into_iter()
14803 .filter_map(|selection| {
14804 // Only requires two branches once if-let-chains stabilize (#53667)
14805 let child = if !selection.is_empty() {
14806 selection.range()
14807 } else if let Some((_, ancestor_range)) =
14808 buffer.syntax_ancestor(selection.start..selection.end)
14809 {
14810 match ancestor_range {
14811 MultiOrSingleBufferOffsetRange::Single(range) => range,
14812 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14813 }
14814 } else {
14815 selection.range()
14816 };
14817
14818 let mut parent = child.clone();
14819 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
14820 parent = match ancestor_range {
14821 MultiOrSingleBufferOffsetRange::Single(range) => range,
14822 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14823 };
14824 if parent.start < child.start || parent.end > child.end {
14825 break;
14826 }
14827 }
14828
14829 if parent == child {
14830 return None;
14831 }
14832 let text = buffer.text_for_range(child).collect::<String>();
14833 Some((selection.id, parent, text))
14834 })
14835 .collect::<Vec<_>>();
14836
14837 self.transact(window, cx, |this, window, cx| {
14838 this.buffer.update(cx, |buffer, cx| {
14839 buffer.edit(
14840 full_edits
14841 .iter()
14842 .map(|(_, p, t)| (p.clone(), t.clone()))
14843 .collect::<Vec<_>>(),
14844 None,
14845 cx,
14846 );
14847 });
14848 this.change_selections(Default::default(), window, cx, |s| {
14849 let mut offset = 0;
14850 let mut selections = vec![];
14851 for (id, parent, text) in full_edits {
14852 let start = parent.start - offset;
14853 offset += parent.len() - text.len();
14854 selections.push(Selection {
14855 id,
14856 start,
14857 end: start + text.len(),
14858 reversed: false,
14859 goal: Default::default(),
14860 });
14861 }
14862 s.select(selections);
14863 });
14864 });
14865 }
14866
14867 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14868 if !EditorSettings::get_global(cx).gutter.runnables {
14869 self.clear_tasks();
14870 return Task::ready(());
14871 }
14872 let project = self.project().map(Entity::downgrade);
14873 let task_sources = self.lsp_task_sources(cx);
14874 let multi_buffer = self.buffer.downgrade();
14875 cx.spawn_in(window, async move |editor, cx| {
14876 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14877 let Some(project) = project.and_then(|p| p.upgrade()) else {
14878 return;
14879 };
14880 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14881 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14882 }) else {
14883 return;
14884 };
14885
14886 let hide_runnables = project
14887 .update(cx, |project, _| project.is_via_collab())
14888 .unwrap_or(true);
14889 if hide_runnables {
14890 return;
14891 }
14892 let new_rows =
14893 cx.background_spawn({
14894 let snapshot = display_snapshot.clone();
14895 async move {
14896 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14897 }
14898 })
14899 .await;
14900 let Ok(lsp_tasks) =
14901 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14902 else {
14903 return;
14904 };
14905 let lsp_tasks = lsp_tasks.await;
14906
14907 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14908 lsp_tasks
14909 .into_iter()
14910 .flat_map(|(kind, tasks)| {
14911 tasks.into_iter().filter_map(move |(location, task)| {
14912 Some((kind.clone(), location?, task))
14913 })
14914 })
14915 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14916 let buffer = location.target.buffer;
14917 let buffer_snapshot = buffer.read(cx).snapshot();
14918 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14919 |(excerpt_id, snapshot, _)| {
14920 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14921 display_snapshot
14922 .buffer_snapshot
14923 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14924 } else {
14925 None
14926 }
14927 },
14928 );
14929 if let Some(offset) = offset {
14930 let task_buffer_range =
14931 location.target.range.to_point(&buffer_snapshot);
14932 let context_buffer_range =
14933 task_buffer_range.to_offset(&buffer_snapshot);
14934 let context_range = BufferOffset(context_buffer_range.start)
14935 ..BufferOffset(context_buffer_range.end);
14936
14937 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14938 .or_insert_with(|| RunnableTasks {
14939 templates: Vec::new(),
14940 offset,
14941 column: task_buffer_range.start.column,
14942 extra_variables: HashMap::default(),
14943 context_range,
14944 })
14945 .templates
14946 .push((kind, task.original_task().clone()));
14947 }
14948
14949 acc
14950 })
14951 }) else {
14952 return;
14953 };
14954
14955 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14956 buffer.language_settings(cx).tasks.prefer_lsp
14957 }) else {
14958 return;
14959 };
14960
14961 let rows = Self::runnable_rows(
14962 project,
14963 display_snapshot,
14964 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14965 new_rows,
14966 cx.clone(),
14967 )
14968 .await;
14969 editor
14970 .update(cx, |editor, _| {
14971 editor.clear_tasks();
14972 for (key, mut value) in rows {
14973 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14974 value.templates.extend(lsp_tasks.templates);
14975 }
14976
14977 editor.insert_tasks(key, value);
14978 }
14979 for (key, value) in lsp_tasks_by_rows {
14980 editor.insert_tasks(key, value);
14981 }
14982 })
14983 .ok();
14984 })
14985 }
14986 fn fetch_runnable_ranges(
14987 snapshot: &DisplaySnapshot,
14988 range: Range<Anchor>,
14989 ) -> Vec<language::RunnableRange> {
14990 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14991 }
14992
14993 fn runnable_rows(
14994 project: Entity<Project>,
14995 snapshot: DisplaySnapshot,
14996 prefer_lsp: bool,
14997 runnable_ranges: Vec<RunnableRange>,
14998 cx: AsyncWindowContext,
14999 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15000 cx.spawn(async move |cx| {
15001 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15002 for mut runnable in runnable_ranges {
15003 let Some(tasks) = cx
15004 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15005 .ok()
15006 else {
15007 continue;
15008 };
15009 let mut tasks = tasks.await;
15010
15011 if prefer_lsp {
15012 tasks.retain(|(task_kind, _)| {
15013 !matches!(task_kind, TaskSourceKind::Language { .. })
15014 });
15015 }
15016 if tasks.is_empty() {
15017 continue;
15018 }
15019
15020 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15021 let Some(row) = snapshot
15022 .buffer_snapshot
15023 .buffer_line_for_row(MultiBufferRow(point.row))
15024 .map(|(_, range)| range.start.row)
15025 else {
15026 continue;
15027 };
15028
15029 let context_range =
15030 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15031 runnable_rows.push((
15032 (runnable.buffer_id, row),
15033 RunnableTasks {
15034 templates: tasks,
15035 offset: snapshot
15036 .buffer_snapshot
15037 .anchor_before(runnable.run_range.start),
15038 context_range,
15039 column: point.column,
15040 extra_variables: runnable.extra_captures,
15041 },
15042 ));
15043 }
15044 runnable_rows
15045 })
15046 }
15047
15048 fn templates_with_tags(
15049 project: &Entity<Project>,
15050 runnable: &mut Runnable,
15051 cx: &mut App,
15052 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15053 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15054 let (worktree_id, file) = project
15055 .buffer_for_id(runnable.buffer, cx)
15056 .and_then(|buffer| buffer.read(cx).file())
15057 .map(|file| (file.worktree_id(cx), file.clone()))
15058 .unzip();
15059
15060 (
15061 project.task_store().read(cx).task_inventory().cloned(),
15062 worktree_id,
15063 file,
15064 )
15065 });
15066
15067 let tags = mem::take(&mut runnable.tags);
15068 let language = runnable.language.clone();
15069 cx.spawn(async move |cx| {
15070 let mut templates_with_tags = Vec::new();
15071 if let Some(inventory) = inventory {
15072 for RunnableTag(tag) in tags {
15073 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15074 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15075 }) else {
15076 return templates_with_tags;
15077 };
15078 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15079 move |(_, template)| {
15080 template.tags.iter().any(|source_tag| source_tag == &tag)
15081 },
15082 ));
15083 }
15084 }
15085 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15086
15087 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15088 // Strongest source wins; if we have worktree tag binding, prefer that to
15089 // global and language bindings;
15090 // if we have a global binding, prefer that to language binding.
15091 let first_mismatch = templates_with_tags
15092 .iter()
15093 .position(|(tag_source, _)| tag_source != leading_tag_source);
15094 if let Some(index) = first_mismatch {
15095 templates_with_tags.truncate(index);
15096 }
15097 }
15098
15099 templates_with_tags
15100 })
15101 }
15102
15103 pub fn move_to_enclosing_bracket(
15104 &mut self,
15105 _: &MoveToEnclosingBracket,
15106 window: &mut Window,
15107 cx: &mut Context<Self>,
15108 ) {
15109 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15110 self.change_selections(Default::default(), window, cx, |s| {
15111 s.move_offsets_with(|snapshot, selection| {
15112 let Some(enclosing_bracket_ranges) =
15113 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15114 else {
15115 return;
15116 };
15117
15118 let mut best_length = usize::MAX;
15119 let mut best_inside = false;
15120 let mut best_in_bracket_range = false;
15121 let mut best_destination = None;
15122 for (open, close) in enclosing_bracket_ranges {
15123 let close = close.to_inclusive();
15124 let length = close.end() - open.start;
15125 let inside = selection.start >= open.end && selection.end <= *close.start();
15126 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15127 || close.contains(&selection.head());
15128
15129 // If best is next to a bracket and current isn't, skip
15130 if !in_bracket_range && best_in_bracket_range {
15131 continue;
15132 }
15133
15134 // Prefer smaller lengths unless best is inside and current isn't
15135 if length > best_length && (best_inside || !inside) {
15136 continue;
15137 }
15138
15139 best_length = length;
15140 best_inside = inside;
15141 best_in_bracket_range = in_bracket_range;
15142 best_destination = Some(
15143 if close.contains(&selection.start) && close.contains(&selection.end) {
15144 if inside { open.end } else { open.start }
15145 } else if inside {
15146 *close.start()
15147 } else {
15148 *close.end()
15149 },
15150 );
15151 }
15152
15153 if let Some(destination) = best_destination {
15154 selection.collapse_to(destination, SelectionGoal::None);
15155 }
15156 })
15157 });
15158 }
15159
15160 pub fn undo_selection(
15161 &mut self,
15162 _: &UndoSelection,
15163 window: &mut Window,
15164 cx: &mut Context<Self>,
15165 ) {
15166 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15167 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15168 self.selection_history.mode = SelectionHistoryMode::Undoing;
15169 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15170 this.end_selection(window, cx);
15171 this.change_selections(
15172 SelectionEffects::scroll(Autoscroll::newest()),
15173 window,
15174 cx,
15175 |s| s.select_anchors(entry.selections.to_vec()),
15176 );
15177 });
15178 self.selection_history.mode = SelectionHistoryMode::Normal;
15179
15180 self.select_next_state = entry.select_next_state;
15181 self.select_prev_state = entry.select_prev_state;
15182 self.add_selections_state = entry.add_selections_state;
15183 }
15184 }
15185
15186 pub fn redo_selection(
15187 &mut self,
15188 _: &RedoSelection,
15189 window: &mut Window,
15190 cx: &mut Context<Self>,
15191 ) {
15192 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15193 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15194 self.selection_history.mode = SelectionHistoryMode::Redoing;
15195 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15196 this.end_selection(window, cx);
15197 this.change_selections(
15198 SelectionEffects::scroll(Autoscroll::newest()),
15199 window,
15200 cx,
15201 |s| s.select_anchors(entry.selections.to_vec()),
15202 );
15203 });
15204 self.selection_history.mode = SelectionHistoryMode::Normal;
15205
15206 self.select_next_state = entry.select_next_state;
15207 self.select_prev_state = entry.select_prev_state;
15208 self.add_selections_state = entry.add_selections_state;
15209 }
15210 }
15211
15212 pub fn expand_excerpts(
15213 &mut self,
15214 action: &ExpandExcerpts,
15215 _: &mut Window,
15216 cx: &mut Context<Self>,
15217 ) {
15218 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15219 }
15220
15221 pub fn expand_excerpts_down(
15222 &mut self,
15223 action: &ExpandExcerptsDown,
15224 _: &mut Window,
15225 cx: &mut Context<Self>,
15226 ) {
15227 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15228 }
15229
15230 pub fn expand_excerpts_up(
15231 &mut self,
15232 action: &ExpandExcerptsUp,
15233 _: &mut Window,
15234 cx: &mut Context<Self>,
15235 ) {
15236 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15237 }
15238
15239 pub fn expand_excerpts_for_direction(
15240 &mut self,
15241 lines: u32,
15242 direction: ExpandExcerptDirection,
15243
15244 cx: &mut Context<Self>,
15245 ) {
15246 let selections = self.selections.disjoint_anchors();
15247
15248 let lines = if lines == 0 {
15249 EditorSettings::get_global(cx).expand_excerpt_lines
15250 } else {
15251 lines
15252 };
15253
15254 self.buffer.update(cx, |buffer, cx| {
15255 let snapshot = buffer.snapshot(cx);
15256 let mut excerpt_ids = selections
15257 .iter()
15258 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15259 .collect::<Vec<_>>();
15260 excerpt_ids.sort();
15261 excerpt_ids.dedup();
15262 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15263 })
15264 }
15265
15266 pub fn expand_excerpt(
15267 &mut self,
15268 excerpt: ExcerptId,
15269 direction: ExpandExcerptDirection,
15270 window: &mut Window,
15271 cx: &mut Context<Self>,
15272 ) {
15273 let current_scroll_position = self.scroll_position(cx);
15274 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15275 let mut should_scroll_up = false;
15276
15277 if direction == ExpandExcerptDirection::Down {
15278 let multi_buffer = self.buffer.read(cx);
15279 let snapshot = multi_buffer.snapshot(cx);
15280 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15281 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15282 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15283 {
15284 let buffer_snapshot = buffer.read(cx).snapshot();
15285 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15286 let last_row = buffer_snapshot.max_point().row;
15287 let lines_below = last_row.saturating_sub(excerpt_end_row);
15288 should_scroll_up = lines_below >= lines_to_expand;
15289 }
15290 }
15291
15292 self.buffer.update(cx, |buffer, cx| {
15293 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15294 });
15295
15296 if should_scroll_up {
15297 let new_scroll_position =
15298 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15299 self.set_scroll_position(new_scroll_position, window, cx);
15300 }
15301 }
15302
15303 pub fn go_to_singleton_buffer_point(
15304 &mut self,
15305 point: Point,
15306 window: &mut Window,
15307 cx: &mut Context<Self>,
15308 ) {
15309 self.go_to_singleton_buffer_range(point..point, window, cx);
15310 }
15311
15312 pub fn go_to_singleton_buffer_range(
15313 &mut self,
15314 range: Range<Point>,
15315 window: &mut Window,
15316 cx: &mut Context<Self>,
15317 ) {
15318 let multibuffer = self.buffer().read(cx);
15319 let Some(buffer) = multibuffer.as_singleton() else {
15320 return;
15321 };
15322 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15323 return;
15324 };
15325 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15326 return;
15327 };
15328 self.change_selections(
15329 SelectionEffects::default().nav_history(true),
15330 window,
15331 cx,
15332 |s| s.select_anchor_ranges([start..end]),
15333 );
15334 }
15335
15336 pub fn go_to_diagnostic(
15337 &mut self,
15338 action: &GoToDiagnostic,
15339 window: &mut Window,
15340 cx: &mut Context<Self>,
15341 ) {
15342 if !self.diagnostics_enabled() {
15343 return;
15344 }
15345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15346 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15347 }
15348
15349 pub fn go_to_prev_diagnostic(
15350 &mut self,
15351 action: &GoToPreviousDiagnostic,
15352 window: &mut Window,
15353 cx: &mut Context<Self>,
15354 ) {
15355 if !self.diagnostics_enabled() {
15356 return;
15357 }
15358 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15359 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15360 }
15361
15362 pub fn go_to_diagnostic_impl(
15363 &mut self,
15364 direction: Direction,
15365 severity: GoToDiagnosticSeverityFilter,
15366 window: &mut Window,
15367 cx: &mut Context<Self>,
15368 ) {
15369 let buffer = self.buffer.read(cx).snapshot(cx);
15370 let selection = self.selections.newest::<usize>(cx);
15371
15372 let mut active_group_id = None;
15373 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15374 && active_group.active_range.start.to_offset(&buffer) == selection.start
15375 {
15376 active_group_id = Some(active_group.group_id);
15377 }
15378
15379 fn filtered(
15380 snapshot: EditorSnapshot,
15381 severity: GoToDiagnosticSeverityFilter,
15382 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15383 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15384 diagnostics
15385 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15386 .filter(|entry| entry.range.start != entry.range.end)
15387 .filter(|entry| !entry.diagnostic.is_unnecessary)
15388 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15389 }
15390
15391 let snapshot = self.snapshot(window, cx);
15392 let before = filtered(
15393 snapshot.clone(),
15394 severity,
15395 buffer
15396 .diagnostics_in_range(0..selection.start)
15397 .filter(|entry| entry.range.start <= selection.start),
15398 );
15399 let after = filtered(
15400 snapshot,
15401 severity,
15402 buffer
15403 .diagnostics_in_range(selection.start..buffer.len())
15404 .filter(|entry| entry.range.start >= selection.start),
15405 );
15406
15407 let mut found: Option<DiagnosticEntry<usize>> = None;
15408 if direction == Direction::Prev {
15409 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15410 {
15411 for diagnostic in prev_diagnostics.into_iter().rev() {
15412 if diagnostic.range.start != selection.start
15413 || active_group_id
15414 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15415 {
15416 found = Some(diagnostic);
15417 break 'outer;
15418 }
15419 }
15420 }
15421 } else {
15422 for diagnostic in after.chain(before) {
15423 if diagnostic.range.start != selection.start
15424 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15425 {
15426 found = Some(diagnostic);
15427 break;
15428 }
15429 }
15430 }
15431 let Some(next_diagnostic) = found else {
15432 return;
15433 };
15434
15435 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15436 return;
15437 };
15438 self.change_selections(Default::default(), window, cx, |s| {
15439 s.select_ranges(vec![
15440 next_diagnostic.range.start..next_diagnostic.range.start,
15441 ])
15442 });
15443 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15444 self.refresh_edit_prediction(false, true, window, cx);
15445 }
15446
15447 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15448 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15449 let snapshot = self.snapshot(window, cx);
15450 let selection = self.selections.newest::<Point>(cx);
15451 self.go_to_hunk_before_or_after_position(
15452 &snapshot,
15453 selection.head(),
15454 Direction::Next,
15455 window,
15456 cx,
15457 );
15458 }
15459
15460 pub fn go_to_hunk_before_or_after_position(
15461 &mut self,
15462 snapshot: &EditorSnapshot,
15463 position: Point,
15464 direction: Direction,
15465 window: &mut Window,
15466 cx: &mut Context<Editor>,
15467 ) {
15468 let row = if direction == Direction::Next {
15469 self.hunk_after_position(snapshot, position)
15470 .map(|hunk| hunk.row_range.start)
15471 } else {
15472 self.hunk_before_position(snapshot, position)
15473 };
15474
15475 if let Some(row) = row {
15476 let destination = Point::new(row.0, 0);
15477 let autoscroll = Autoscroll::center();
15478
15479 self.unfold_ranges(&[destination..destination], false, false, cx);
15480 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15481 s.select_ranges([destination..destination]);
15482 });
15483 }
15484 }
15485
15486 fn hunk_after_position(
15487 &mut self,
15488 snapshot: &EditorSnapshot,
15489 position: Point,
15490 ) -> Option<MultiBufferDiffHunk> {
15491 snapshot
15492 .buffer_snapshot
15493 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15494 .find(|hunk| hunk.row_range.start.0 > position.row)
15495 .or_else(|| {
15496 snapshot
15497 .buffer_snapshot
15498 .diff_hunks_in_range(Point::zero()..position)
15499 .find(|hunk| hunk.row_range.end.0 < position.row)
15500 })
15501 }
15502
15503 fn go_to_prev_hunk(
15504 &mut self,
15505 _: &GoToPreviousHunk,
15506 window: &mut Window,
15507 cx: &mut Context<Self>,
15508 ) {
15509 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15510 let snapshot = self.snapshot(window, cx);
15511 let selection = self.selections.newest::<Point>(cx);
15512 self.go_to_hunk_before_or_after_position(
15513 &snapshot,
15514 selection.head(),
15515 Direction::Prev,
15516 window,
15517 cx,
15518 );
15519 }
15520
15521 fn hunk_before_position(
15522 &mut self,
15523 snapshot: &EditorSnapshot,
15524 position: Point,
15525 ) -> Option<MultiBufferRow> {
15526 snapshot
15527 .buffer_snapshot
15528 .diff_hunk_before(position)
15529 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15530 }
15531
15532 fn go_to_next_change(
15533 &mut self,
15534 _: &GoToNextChange,
15535 window: &mut Window,
15536 cx: &mut Context<Self>,
15537 ) {
15538 if let Some(selections) = self
15539 .change_list
15540 .next_change(1, Direction::Next)
15541 .map(|s| s.to_vec())
15542 {
15543 self.change_selections(Default::default(), window, cx, |s| {
15544 let map = s.display_map();
15545 s.select_display_ranges(selections.iter().map(|a| {
15546 let point = a.to_display_point(&map);
15547 point..point
15548 }))
15549 })
15550 }
15551 }
15552
15553 fn go_to_previous_change(
15554 &mut self,
15555 _: &GoToPreviousChange,
15556 window: &mut Window,
15557 cx: &mut Context<Self>,
15558 ) {
15559 if let Some(selections) = self
15560 .change_list
15561 .next_change(1, Direction::Prev)
15562 .map(|s| s.to_vec())
15563 {
15564 self.change_selections(Default::default(), window, cx, |s| {
15565 let map = s.display_map();
15566 s.select_display_ranges(selections.iter().map(|a| {
15567 let point = a.to_display_point(&map);
15568 point..point
15569 }))
15570 })
15571 }
15572 }
15573
15574 fn go_to_line<T: 'static>(
15575 &mut self,
15576 position: Anchor,
15577 highlight_color: Option<Hsla>,
15578 window: &mut Window,
15579 cx: &mut Context<Self>,
15580 ) {
15581 let snapshot = self.snapshot(window, cx).display_snapshot;
15582 let position = position.to_point(&snapshot.buffer_snapshot);
15583 let start = snapshot
15584 .buffer_snapshot
15585 .clip_point(Point::new(position.row, 0), Bias::Left);
15586 let end = start + Point::new(1, 0);
15587 let start = snapshot.buffer_snapshot.anchor_before(start);
15588 let end = snapshot.buffer_snapshot.anchor_before(end);
15589
15590 self.highlight_rows::<T>(
15591 start..end,
15592 highlight_color
15593 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15594 Default::default(),
15595 cx,
15596 );
15597
15598 if self.buffer.read(cx).is_singleton() {
15599 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15600 }
15601 }
15602
15603 pub fn go_to_definition(
15604 &mut self,
15605 _: &GoToDefinition,
15606 window: &mut Window,
15607 cx: &mut Context<Self>,
15608 ) -> Task<Result<Navigated>> {
15609 let definition =
15610 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15611 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15612 cx.spawn_in(window, async move |editor, cx| {
15613 if definition.await? == Navigated::Yes {
15614 return Ok(Navigated::Yes);
15615 }
15616 match fallback_strategy {
15617 GoToDefinitionFallback::None => Ok(Navigated::No),
15618 GoToDefinitionFallback::FindAllReferences => {
15619 match editor.update_in(cx, |editor, window, cx| {
15620 editor.find_all_references(&FindAllReferences, window, cx)
15621 })? {
15622 Some(references) => references.await,
15623 None => Ok(Navigated::No),
15624 }
15625 }
15626 }
15627 })
15628 }
15629
15630 pub fn go_to_declaration(
15631 &mut self,
15632 _: &GoToDeclaration,
15633 window: &mut Window,
15634 cx: &mut Context<Self>,
15635 ) -> Task<Result<Navigated>> {
15636 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15637 }
15638
15639 pub fn go_to_declaration_split(
15640 &mut self,
15641 _: &GoToDeclaration,
15642 window: &mut Window,
15643 cx: &mut Context<Self>,
15644 ) -> Task<Result<Navigated>> {
15645 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15646 }
15647
15648 pub fn go_to_implementation(
15649 &mut self,
15650 _: &GoToImplementation,
15651 window: &mut Window,
15652 cx: &mut Context<Self>,
15653 ) -> Task<Result<Navigated>> {
15654 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15655 }
15656
15657 pub fn go_to_implementation_split(
15658 &mut self,
15659 _: &GoToImplementationSplit,
15660 window: &mut Window,
15661 cx: &mut Context<Self>,
15662 ) -> Task<Result<Navigated>> {
15663 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15664 }
15665
15666 pub fn go_to_type_definition(
15667 &mut self,
15668 _: &GoToTypeDefinition,
15669 window: &mut Window,
15670 cx: &mut Context<Self>,
15671 ) -> Task<Result<Navigated>> {
15672 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15673 }
15674
15675 pub fn go_to_definition_split(
15676 &mut self,
15677 _: &GoToDefinitionSplit,
15678 window: &mut Window,
15679 cx: &mut Context<Self>,
15680 ) -> Task<Result<Navigated>> {
15681 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15682 }
15683
15684 pub fn go_to_type_definition_split(
15685 &mut self,
15686 _: &GoToTypeDefinitionSplit,
15687 window: &mut Window,
15688 cx: &mut Context<Self>,
15689 ) -> Task<Result<Navigated>> {
15690 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15691 }
15692
15693 fn go_to_definition_of_kind(
15694 &mut self,
15695 kind: GotoDefinitionKind,
15696 split: bool,
15697 window: &mut Window,
15698 cx: &mut Context<Self>,
15699 ) -> Task<Result<Navigated>> {
15700 let Some(provider) = self.semantics_provider.clone() else {
15701 return Task::ready(Ok(Navigated::No));
15702 };
15703 let head = self.selections.newest::<usize>(cx).head();
15704 let buffer = self.buffer.read(cx);
15705 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15706 return Task::ready(Ok(Navigated::No));
15707 };
15708 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15709 return Task::ready(Ok(Navigated::No));
15710 };
15711
15712 cx.spawn_in(window, async move |editor, cx| {
15713 let definitions = definitions.await?;
15714 let navigated = editor
15715 .update_in(cx, |editor, window, cx| {
15716 editor.navigate_to_hover_links(
15717 Some(kind),
15718 definitions
15719 .into_iter()
15720 .filter(|location| {
15721 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15722 })
15723 .map(HoverLink::Text)
15724 .collect::<Vec<_>>(),
15725 split,
15726 window,
15727 cx,
15728 )
15729 })?
15730 .await?;
15731 anyhow::Ok(navigated)
15732 })
15733 }
15734
15735 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15736 let selection = self.selections.newest_anchor();
15737 let head = selection.head();
15738 let tail = selection.tail();
15739
15740 let Some((buffer, start_position)) =
15741 self.buffer.read(cx).text_anchor_for_position(head, cx)
15742 else {
15743 return;
15744 };
15745
15746 let end_position = if head != tail {
15747 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15748 return;
15749 };
15750 Some(pos)
15751 } else {
15752 None
15753 };
15754
15755 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15756 let url = if let Some(end_pos) = end_position {
15757 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15758 } else {
15759 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15760 };
15761
15762 if let Some(url) = url {
15763 editor.update(cx, |_, cx| {
15764 cx.open_url(&url);
15765 })
15766 } else {
15767 Ok(())
15768 }
15769 });
15770
15771 url_finder.detach();
15772 }
15773
15774 pub fn open_selected_filename(
15775 &mut self,
15776 _: &OpenSelectedFilename,
15777 window: &mut Window,
15778 cx: &mut Context<Self>,
15779 ) {
15780 let Some(workspace) = self.workspace() else {
15781 return;
15782 };
15783
15784 let position = self.selections.newest_anchor().head();
15785
15786 let Some((buffer, buffer_position)) =
15787 self.buffer.read(cx).text_anchor_for_position(position, cx)
15788 else {
15789 return;
15790 };
15791
15792 let project = self.project.clone();
15793
15794 cx.spawn_in(window, async move |_, cx| {
15795 let result = find_file(&buffer, project, buffer_position, cx).await;
15796
15797 if let Some((_, path)) = result {
15798 workspace
15799 .update_in(cx, |workspace, window, cx| {
15800 workspace.open_resolved_path(path, window, cx)
15801 })?
15802 .await?;
15803 }
15804 anyhow::Ok(())
15805 })
15806 .detach();
15807 }
15808
15809 pub(crate) fn navigate_to_hover_links(
15810 &mut self,
15811 kind: Option<GotoDefinitionKind>,
15812 definitions: Vec<HoverLink>,
15813 split: bool,
15814 window: &mut Window,
15815 cx: &mut Context<Editor>,
15816 ) -> Task<Result<Navigated>> {
15817 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15818 let mut first_url_or_file = None;
15819 let definitions: Vec<_> = definitions
15820 .into_iter()
15821 .filter_map(|def| match def {
15822 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15823 HoverLink::InlayHint(lsp_location, server_id) => {
15824 let computation =
15825 self.compute_target_location(lsp_location, server_id, window, cx);
15826 Some(cx.background_spawn(computation))
15827 }
15828 HoverLink::Url(url) => {
15829 first_url_or_file = Some(Either::Left(url));
15830 None
15831 }
15832 HoverLink::File(path) => {
15833 first_url_or_file = Some(Either::Right(path));
15834 None
15835 }
15836 })
15837 .collect();
15838
15839 let workspace = self.workspace();
15840
15841 cx.spawn_in(window, async move |editor, acx| {
15842 let mut locations: Vec<Location> = future::join_all(definitions)
15843 .await
15844 .into_iter()
15845 .filter_map(|location| location.transpose())
15846 .collect::<Result<_>>()
15847 .context("location tasks")?;
15848
15849 if locations.len() > 1 {
15850 let Some(workspace) = workspace else {
15851 return Ok(Navigated::No);
15852 };
15853
15854 let tab_kind = match kind {
15855 Some(GotoDefinitionKind::Implementation) => "Implementations",
15856 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
15857 Some(GotoDefinitionKind::Declaration) => "Declarations",
15858 Some(GotoDefinitionKind::Type) => "Types",
15859 };
15860 let title = editor
15861 .update_in(acx, |_, _, cx| {
15862 let target = locations
15863 .iter()
15864 .map(|location| {
15865 location
15866 .buffer
15867 .read(cx)
15868 .text_for_range(location.range.clone())
15869 .collect::<String>()
15870 })
15871 .filter(|text| !text.contains('\n'))
15872 .unique()
15873 .take(3)
15874 .join(", ");
15875 if target.is_empty() {
15876 tab_kind.to_owned()
15877 } else {
15878 format!("{tab_kind} for {target}")
15879 }
15880 })
15881 .context("buffer title")?;
15882
15883 let opened = workspace
15884 .update_in(acx, |workspace, window, cx| {
15885 Self::open_locations_in_multibuffer(
15886 workspace,
15887 locations,
15888 title,
15889 split,
15890 MultibufferSelectionMode::First,
15891 window,
15892 cx,
15893 )
15894 })
15895 .is_ok();
15896
15897 anyhow::Ok(Navigated::from_bool(opened))
15898 } else if locations.is_empty() {
15899 // If there is one definition, just open it directly
15900 match first_url_or_file {
15901 Some(Either::Left(url)) => {
15902 acx.update(|_, cx| cx.open_url(&url))?;
15903 Ok(Navigated::Yes)
15904 }
15905 Some(Either::Right(path)) => {
15906 let Some(workspace) = workspace else {
15907 return Ok(Navigated::No);
15908 };
15909
15910 workspace
15911 .update_in(acx, |workspace, window, cx| {
15912 workspace.open_resolved_path(path, window, cx)
15913 })?
15914 .await?;
15915 Ok(Navigated::Yes)
15916 }
15917 None => Ok(Navigated::No),
15918 }
15919 } else {
15920 let Some(workspace) = workspace else {
15921 return Ok(Navigated::No);
15922 };
15923
15924 let target = locations.pop().unwrap();
15925 editor.update_in(acx, |editor, window, cx| {
15926 let pane = workspace.read(cx).active_pane().clone();
15927
15928 let range = target.range.to_point(target.buffer.read(cx));
15929 let range = editor.range_for_match(&range);
15930 let range = collapse_multiline_range(range);
15931
15932 if !split
15933 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15934 {
15935 editor.go_to_singleton_buffer_range(range, window, cx);
15936 } else {
15937 window.defer(cx, move |window, cx| {
15938 let target_editor: Entity<Self> =
15939 workspace.update(cx, |workspace, cx| {
15940 let pane = if split {
15941 workspace.adjacent_pane(window, cx)
15942 } else {
15943 workspace.active_pane().clone()
15944 };
15945
15946 workspace.open_project_item(
15947 pane,
15948 target.buffer.clone(),
15949 true,
15950 true,
15951 window,
15952 cx,
15953 )
15954 });
15955 target_editor.update(cx, |target_editor, cx| {
15956 // When selecting a definition in a different buffer, disable the nav history
15957 // to avoid creating a history entry at the previous cursor location.
15958 pane.update(cx, |pane, _| pane.disable_history());
15959 target_editor.go_to_singleton_buffer_range(range, window, cx);
15960 pane.update(cx, |pane, _| pane.enable_history());
15961 });
15962 });
15963 }
15964 Navigated::Yes
15965 })
15966 }
15967 })
15968 }
15969
15970 fn compute_target_location(
15971 &self,
15972 lsp_location: lsp::Location,
15973 server_id: LanguageServerId,
15974 window: &mut Window,
15975 cx: &mut Context<Self>,
15976 ) -> Task<anyhow::Result<Option<Location>>> {
15977 let Some(project) = self.project.clone() else {
15978 return Task::ready(Ok(None));
15979 };
15980
15981 cx.spawn_in(window, async move |editor, cx| {
15982 let location_task = editor.update(cx, |_, cx| {
15983 project.update(cx, |project, cx| {
15984 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
15985 })
15986 })?;
15987 let location = Some({
15988 let target_buffer_handle = location_task.await.context("open local buffer")?;
15989 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15990 let target_start = target_buffer
15991 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15992 let target_end = target_buffer
15993 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15994 target_buffer.anchor_after(target_start)
15995 ..target_buffer.anchor_before(target_end)
15996 })?;
15997 Location {
15998 buffer: target_buffer_handle,
15999 range,
16000 }
16001 });
16002 Ok(location)
16003 })
16004 }
16005
16006 pub fn find_all_references(
16007 &mut self,
16008 _: &FindAllReferences,
16009 window: &mut Window,
16010 cx: &mut Context<Self>,
16011 ) -> Option<Task<Result<Navigated>>> {
16012 let selection = self.selections.newest::<usize>(cx);
16013 let multi_buffer = self.buffer.read(cx);
16014 let head = selection.head();
16015
16016 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16017 let head_anchor = multi_buffer_snapshot.anchor_at(
16018 head,
16019 if head < selection.tail() {
16020 Bias::Right
16021 } else {
16022 Bias::Left
16023 },
16024 );
16025
16026 match self
16027 .find_all_references_task_sources
16028 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16029 {
16030 Ok(_) => {
16031 log::info!(
16032 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16033 );
16034 return None;
16035 }
16036 Err(i) => {
16037 self.find_all_references_task_sources.insert(i, head_anchor);
16038 }
16039 }
16040
16041 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16042 let workspace = self.workspace()?;
16043 let project = workspace.read(cx).project().clone();
16044 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16045 Some(cx.spawn_in(window, async move |editor, cx| {
16046 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16047 if let Ok(i) = editor
16048 .find_all_references_task_sources
16049 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16050 {
16051 editor.find_all_references_task_sources.remove(i);
16052 }
16053 });
16054
16055 let locations = references.await?;
16056 if locations.is_empty() {
16057 return anyhow::Ok(Navigated::No);
16058 }
16059
16060 workspace.update_in(cx, |workspace, window, cx| {
16061 let target = locations
16062 .iter()
16063 .map(|location| {
16064 location
16065 .buffer
16066 .read(cx)
16067 .text_for_range(location.range.clone())
16068 .collect::<String>()
16069 })
16070 .filter(|text| !text.contains('\n'))
16071 .unique()
16072 .take(3)
16073 .join(", ");
16074 let title = if target.is_empty() {
16075 "References".to_owned()
16076 } else {
16077 format!("References to {target}")
16078 };
16079 Self::open_locations_in_multibuffer(
16080 workspace,
16081 locations,
16082 title,
16083 false,
16084 MultibufferSelectionMode::First,
16085 window,
16086 cx,
16087 );
16088 Navigated::Yes
16089 })
16090 }))
16091 }
16092
16093 /// Opens a multibuffer with the given project locations in it
16094 pub fn open_locations_in_multibuffer(
16095 workspace: &mut Workspace,
16096 mut locations: Vec<Location>,
16097 title: String,
16098 split: bool,
16099 multibuffer_selection_mode: MultibufferSelectionMode,
16100 window: &mut Window,
16101 cx: &mut Context<Workspace>,
16102 ) {
16103 if locations.is_empty() {
16104 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16105 return;
16106 }
16107
16108 // If there are multiple definitions, open them in a multibuffer
16109 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16110 let mut locations = locations.into_iter().peekable();
16111 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16112 let capability = workspace.project().read(cx).capability();
16113
16114 let excerpt_buffer = cx.new(|cx| {
16115 let mut multibuffer = MultiBuffer::new(capability);
16116 while let Some(location) = locations.next() {
16117 let buffer = location.buffer.read(cx);
16118 let mut ranges_for_buffer = Vec::new();
16119 let range = location.range.to_point(buffer);
16120 ranges_for_buffer.push(range.clone());
16121
16122 while let Some(next_location) = locations.peek() {
16123 if next_location.buffer == location.buffer {
16124 ranges_for_buffer.push(next_location.range.to_point(buffer));
16125 locations.next();
16126 } else {
16127 break;
16128 }
16129 }
16130
16131 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16132 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16133 PathKey::for_buffer(&location.buffer, cx),
16134 location.buffer.clone(),
16135 ranges_for_buffer,
16136 DEFAULT_MULTIBUFFER_CONTEXT,
16137 cx,
16138 );
16139 ranges.extend(new_ranges)
16140 }
16141
16142 multibuffer.with_title(title)
16143 });
16144
16145 let editor = cx.new(|cx| {
16146 Editor::for_multibuffer(
16147 excerpt_buffer,
16148 Some(workspace.project().clone()),
16149 window,
16150 cx,
16151 )
16152 });
16153 editor.update(cx, |editor, cx| {
16154 match multibuffer_selection_mode {
16155 MultibufferSelectionMode::First => {
16156 if let Some(first_range) = ranges.first() {
16157 editor.change_selections(
16158 SelectionEffects::no_scroll(),
16159 window,
16160 cx,
16161 |selections| {
16162 selections.clear_disjoint();
16163 selections
16164 .select_anchor_ranges(std::iter::once(first_range.clone()));
16165 },
16166 );
16167 }
16168 editor.highlight_background::<Self>(
16169 &ranges,
16170 |theme| theme.colors().editor_highlighted_line_background,
16171 cx,
16172 );
16173 }
16174 MultibufferSelectionMode::All => {
16175 editor.change_selections(
16176 SelectionEffects::no_scroll(),
16177 window,
16178 cx,
16179 |selections| {
16180 selections.clear_disjoint();
16181 selections.select_anchor_ranges(ranges);
16182 },
16183 );
16184 }
16185 }
16186 editor.register_buffers_with_language_servers(cx);
16187 });
16188
16189 let item = Box::new(editor);
16190 let item_id = item.item_id();
16191
16192 if split {
16193 workspace.split_item(SplitDirection::Right, item, window, cx);
16194 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16195 let (preview_item_id, preview_item_idx) =
16196 workspace.active_pane().read_with(cx, |pane, _| {
16197 (pane.preview_item_id(), pane.preview_item_idx())
16198 });
16199
16200 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16201
16202 if let Some(preview_item_id) = preview_item_id {
16203 workspace.active_pane().update(cx, |pane, cx| {
16204 pane.remove_item(preview_item_id, false, false, window, cx);
16205 });
16206 }
16207 } else {
16208 workspace.add_item_to_active_pane(item, None, true, window, cx);
16209 }
16210 workspace.active_pane().update(cx, |pane, cx| {
16211 pane.set_preview_item_id(Some(item_id), cx);
16212 });
16213 }
16214
16215 pub fn rename(
16216 &mut self,
16217 _: &Rename,
16218 window: &mut Window,
16219 cx: &mut Context<Self>,
16220 ) -> Option<Task<Result<()>>> {
16221 use language::ToOffset as _;
16222
16223 let provider = self.semantics_provider.clone()?;
16224 let selection = self.selections.newest_anchor().clone();
16225 let (cursor_buffer, cursor_buffer_position) = self
16226 .buffer
16227 .read(cx)
16228 .text_anchor_for_position(selection.head(), cx)?;
16229 let (tail_buffer, cursor_buffer_position_end) = self
16230 .buffer
16231 .read(cx)
16232 .text_anchor_for_position(selection.tail(), cx)?;
16233 if tail_buffer != cursor_buffer {
16234 return None;
16235 }
16236
16237 let snapshot = cursor_buffer.read(cx).snapshot();
16238 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16239 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16240 let prepare_rename = provider
16241 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16242 .unwrap_or_else(|| Task::ready(Ok(None)));
16243 drop(snapshot);
16244
16245 Some(cx.spawn_in(window, async move |this, cx| {
16246 let rename_range = if let Some(range) = prepare_rename.await? {
16247 Some(range)
16248 } else {
16249 this.update(cx, |this, cx| {
16250 let buffer = this.buffer.read(cx).snapshot(cx);
16251 let mut buffer_highlights = this
16252 .document_highlights_for_position(selection.head(), &buffer)
16253 .filter(|highlight| {
16254 highlight.start.excerpt_id == selection.head().excerpt_id
16255 && highlight.end.excerpt_id == selection.head().excerpt_id
16256 });
16257 buffer_highlights
16258 .next()
16259 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16260 })?
16261 };
16262 if let Some(rename_range) = rename_range {
16263 this.update_in(cx, |this, window, cx| {
16264 let snapshot = cursor_buffer.read(cx).snapshot();
16265 let rename_buffer_range = rename_range.to_offset(&snapshot);
16266 let cursor_offset_in_rename_range =
16267 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16268 let cursor_offset_in_rename_range_end =
16269 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16270
16271 this.take_rename(false, window, cx);
16272 let buffer = this.buffer.read(cx).read(cx);
16273 let cursor_offset = selection.head().to_offset(&buffer);
16274 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16275 let rename_end = rename_start + rename_buffer_range.len();
16276 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16277 let mut old_highlight_id = None;
16278 let old_name: Arc<str> = buffer
16279 .chunks(rename_start..rename_end, true)
16280 .map(|chunk| {
16281 if old_highlight_id.is_none() {
16282 old_highlight_id = chunk.syntax_highlight_id;
16283 }
16284 chunk.text
16285 })
16286 .collect::<String>()
16287 .into();
16288
16289 drop(buffer);
16290
16291 // Position the selection in the rename editor so that it matches the current selection.
16292 this.show_local_selections = false;
16293 let rename_editor = cx.new(|cx| {
16294 let mut editor = Editor::single_line(window, cx);
16295 editor.buffer.update(cx, |buffer, cx| {
16296 buffer.edit([(0..0, old_name.clone())], None, cx)
16297 });
16298 let rename_selection_range = match cursor_offset_in_rename_range
16299 .cmp(&cursor_offset_in_rename_range_end)
16300 {
16301 Ordering::Equal => {
16302 editor.select_all(&SelectAll, window, cx);
16303 return editor;
16304 }
16305 Ordering::Less => {
16306 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16307 }
16308 Ordering::Greater => {
16309 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16310 }
16311 };
16312 if rename_selection_range.end > old_name.len() {
16313 editor.select_all(&SelectAll, window, cx);
16314 } else {
16315 editor.change_selections(Default::default(), window, cx, |s| {
16316 s.select_ranges([rename_selection_range]);
16317 });
16318 }
16319 editor
16320 });
16321 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16322 if e == &EditorEvent::Focused {
16323 cx.emit(EditorEvent::FocusedIn)
16324 }
16325 })
16326 .detach();
16327
16328 let write_highlights =
16329 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16330 let read_highlights =
16331 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16332 let ranges = write_highlights
16333 .iter()
16334 .flat_map(|(_, ranges)| ranges.iter())
16335 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16336 .cloned()
16337 .collect();
16338
16339 this.highlight_text::<Rename>(
16340 ranges,
16341 HighlightStyle {
16342 fade_out: Some(0.6),
16343 ..Default::default()
16344 },
16345 cx,
16346 );
16347 let rename_focus_handle = rename_editor.focus_handle(cx);
16348 window.focus(&rename_focus_handle);
16349 let block_id = this.insert_blocks(
16350 [BlockProperties {
16351 style: BlockStyle::Flex,
16352 placement: BlockPlacement::Below(range.start),
16353 height: Some(1),
16354 render: Arc::new({
16355 let rename_editor = rename_editor.clone();
16356 move |cx: &mut BlockContext| {
16357 let mut text_style = cx.editor_style.text.clone();
16358 if let Some(highlight_style) = old_highlight_id
16359 .and_then(|h| h.style(&cx.editor_style.syntax))
16360 {
16361 text_style = text_style.highlight(highlight_style);
16362 }
16363 div()
16364 .block_mouse_except_scroll()
16365 .pl(cx.anchor_x)
16366 .child(EditorElement::new(
16367 &rename_editor,
16368 EditorStyle {
16369 background: cx.theme().system().transparent,
16370 local_player: cx.editor_style.local_player,
16371 text: text_style,
16372 scrollbar_width: cx.editor_style.scrollbar_width,
16373 syntax: cx.editor_style.syntax.clone(),
16374 status: cx.editor_style.status.clone(),
16375 inlay_hints_style: HighlightStyle {
16376 font_weight: Some(FontWeight::BOLD),
16377 ..make_inlay_hints_style(cx.app)
16378 },
16379 edit_prediction_styles: make_suggestion_styles(
16380 cx.app,
16381 ),
16382 ..EditorStyle::default()
16383 },
16384 ))
16385 .into_any_element()
16386 }
16387 }),
16388 priority: 0,
16389 }],
16390 Some(Autoscroll::fit()),
16391 cx,
16392 )[0];
16393 this.pending_rename = Some(RenameState {
16394 range,
16395 old_name,
16396 editor: rename_editor,
16397 block_id,
16398 });
16399 })?;
16400 }
16401
16402 Ok(())
16403 }))
16404 }
16405
16406 pub fn confirm_rename(
16407 &mut self,
16408 _: &ConfirmRename,
16409 window: &mut Window,
16410 cx: &mut Context<Self>,
16411 ) -> Option<Task<Result<()>>> {
16412 let rename = self.take_rename(false, window, cx)?;
16413 let workspace = self.workspace()?.downgrade();
16414 let (buffer, start) = self
16415 .buffer
16416 .read(cx)
16417 .text_anchor_for_position(rename.range.start, cx)?;
16418 let (end_buffer, _) = self
16419 .buffer
16420 .read(cx)
16421 .text_anchor_for_position(rename.range.end, cx)?;
16422 if buffer != end_buffer {
16423 return None;
16424 }
16425
16426 let old_name = rename.old_name;
16427 let new_name = rename.editor.read(cx).text(cx);
16428
16429 let rename = self.semantics_provider.as_ref()?.perform_rename(
16430 &buffer,
16431 start,
16432 new_name.clone(),
16433 cx,
16434 )?;
16435
16436 Some(cx.spawn_in(window, async move |editor, cx| {
16437 let project_transaction = rename.await?;
16438 Self::open_project_transaction(
16439 &editor,
16440 workspace,
16441 project_transaction,
16442 format!("Rename: {} → {}", old_name, new_name),
16443 cx,
16444 )
16445 .await?;
16446
16447 editor.update(cx, |editor, cx| {
16448 editor.refresh_document_highlights(cx);
16449 })?;
16450 Ok(())
16451 }))
16452 }
16453
16454 fn take_rename(
16455 &mut self,
16456 moving_cursor: bool,
16457 window: &mut Window,
16458 cx: &mut Context<Self>,
16459 ) -> Option<RenameState> {
16460 let rename = self.pending_rename.take()?;
16461 if rename.editor.focus_handle(cx).is_focused(window) {
16462 window.focus(&self.focus_handle);
16463 }
16464
16465 self.remove_blocks(
16466 [rename.block_id].into_iter().collect(),
16467 Some(Autoscroll::fit()),
16468 cx,
16469 );
16470 self.clear_highlights::<Rename>(cx);
16471 self.show_local_selections = true;
16472
16473 if moving_cursor {
16474 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16475 editor.selections.newest::<usize>(cx).head()
16476 });
16477
16478 // Update the selection to match the position of the selection inside
16479 // the rename editor.
16480 let snapshot = self.buffer.read(cx).read(cx);
16481 let rename_range = rename.range.to_offset(&snapshot);
16482 let cursor_in_editor = snapshot
16483 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16484 .min(rename_range.end);
16485 drop(snapshot);
16486
16487 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16488 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16489 });
16490 } else {
16491 self.refresh_document_highlights(cx);
16492 }
16493
16494 Some(rename)
16495 }
16496
16497 pub fn pending_rename(&self) -> Option<&RenameState> {
16498 self.pending_rename.as_ref()
16499 }
16500
16501 fn format(
16502 &mut self,
16503 _: &Format,
16504 window: &mut Window,
16505 cx: &mut Context<Self>,
16506 ) -> Option<Task<Result<()>>> {
16507 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16508
16509 let project = match &self.project {
16510 Some(project) => project.clone(),
16511 None => return None,
16512 };
16513
16514 Some(self.perform_format(
16515 project,
16516 FormatTrigger::Manual,
16517 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16518 window,
16519 cx,
16520 ))
16521 }
16522
16523 fn format_selections(
16524 &mut self,
16525 _: &FormatSelections,
16526 window: &mut Window,
16527 cx: &mut Context<Self>,
16528 ) -> Option<Task<Result<()>>> {
16529 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16530
16531 let project = match &self.project {
16532 Some(project) => project.clone(),
16533 None => return None,
16534 };
16535
16536 let ranges = self
16537 .selections
16538 .all_adjusted(cx)
16539 .into_iter()
16540 .map(|selection| selection.range())
16541 .collect_vec();
16542
16543 Some(self.perform_format(
16544 project,
16545 FormatTrigger::Manual,
16546 FormatTarget::Ranges(ranges),
16547 window,
16548 cx,
16549 ))
16550 }
16551
16552 fn perform_format(
16553 &mut self,
16554 project: Entity<Project>,
16555 trigger: FormatTrigger,
16556 target: FormatTarget,
16557 window: &mut Window,
16558 cx: &mut Context<Self>,
16559 ) -> Task<Result<()>> {
16560 let buffer = self.buffer.clone();
16561 let (buffers, target) = match target {
16562 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16563 FormatTarget::Ranges(selection_ranges) => {
16564 let multi_buffer = buffer.read(cx);
16565 let snapshot = multi_buffer.read(cx);
16566 let mut buffers = HashSet::default();
16567 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16568 BTreeMap::new();
16569 for selection_range in selection_ranges {
16570 for (buffer, buffer_range, _) in
16571 snapshot.range_to_buffer_ranges(selection_range)
16572 {
16573 let buffer_id = buffer.remote_id();
16574 let start = buffer.anchor_before(buffer_range.start);
16575 let end = buffer.anchor_after(buffer_range.end);
16576 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16577 buffer_id_to_ranges
16578 .entry(buffer_id)
16579 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16580 .or_insert_with(|| vec![start..end]);
16581 }
16582 }
16583 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16584 }
16585 };
16586
16587 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16588 let selections_prev = transaction_id_prev
16589 .and_then(|transaction_id_prev| {
16590 // default to selections as they were after the last edit, if we have them,
16591 // instead of how they are now.
16592 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16593 // will take you back to where you made the last edit, instead of staying where you scrolled
16594 self.selection_history
16595 .transaction(transaction_id_prev)
16596 .map(|t| t.0.clone())
16597 })
16598 .unwrap_or_else(|| self.selections.disjoint_anchors());
16599
16600 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16601 let format = project.update(cx, |project, cx| {
16602 project.format(buffers, target, true, trigger, cx)
16603 });
16604
16605 cx.spawn_in(window, async move |editor, cx| {
16606 let transaction = futures::select_biased! {
16607 transaction = format.log_err().fuse() => transaction,
16608 () = timeout => {
16609 log::warn!("timed out waiting for formatting");
16610 None
16611 }
16612 };
16613
16614 buffer
16615 .update(cx, |buffer, cx| {
16616 if let Some(transaction) = transaction
16617 && !buffer.is_singleton()
16618 {
16619 buffer.push_transaction(&transaction.0, cx);
16620 }
16621 cx.notify();
16622 })
16623 .ok();
16624
16625 if let Some(transaction_id_now) =
16626 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16627 {
16628 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16629 if has_new_transaction {
16630 _ = editor.update(cx, |editor, _| {
16631 editor
16632 .selection_history
16633 .insert_transaction(transaction_id_now, selections_prev);
16634 });
16635 }
16636 }
16637
16638 Ok(())
16639 })
16640 }
16641
16642 fn organize_imports(
16643 &mut self,
16644 _: &OrganizeImports,
16645 window: &mut Window,
16646 cx: &mut Context<Self>,
16647 ) -> Option<Task<Result<()>>> {
16648 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16649 let project = match &self.project {
16650 Some(project) => project.clone(),
16651 None => return None,
16652 };
16653 Some(self.perform_code_action_kind(
16654 project,
16655 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16656 window,
16657 cx,
16658 ))
16659 }
16660
16661 fn perform_code_action_kind(
16662 &mut self,
16663 project: Entity<Project>,
16664 kind: CodeActionKind,
16665 window: &mut Window,
16666 cx: &mut Context<Self>,
16667 ) -> Task<Result<()>> {
16668 let buffer = self.buffer.clone();
16669 let buffers = buffer.read(cx).all_buffers();
16670 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16671 let apply_action = project.update(cx, |project, cx| {
16672 project.apply_code_action_kind(buffers, kind, true, cx)
16673 });
16674 cx.spawn_in(window, async move |_, cx| {
16675 let transaction = futures::select_biased! {
16676 () = timeout => {
16677 log::warn!("timed out waiting for executing code action");
16678 None
16679 }
16680 transaction = apply_action.log_err().fuse() => transaction,
16681 };
16682 buffer
16683 .update(cx, |buffer, cx| {
16684 // check if we need this
16685 if let Some(transaction) = transaction
16686 && !buffer.is_singleton()
16687 {
16688 buffer.push_transaction(&transaction.0, cx);
16689 }
16690 cx.notify();
16691 })
16692 .ok();
16693 Ok(())
16694 })
16695 }
16696
16697 pub fn restart_language_server(
16698 &mut self,
16699 _: &RestartLanguageServer,
16700 _: &mut Window,
16701 cx: &mut Context<Self>,
16702 ) {
16703 if let Some(project) = self.project.clone() {
16704 self.buffer.update(cx, |multi_buffer, cx| {
16705 project.update(cx, |project, cx| {
16706 project.restart_language_servers_for_buffers(
16707 multi_buffer.all_buffers().into_iter().collect(),
16708 HashSet::default(),
16709 cx,
16710 );
16711 });
16712 })
16713 }
16714 }
16715
16716 pub fn stop_language_server(
16717 &mut self,
16718 _: &StopLanguageServer,
16719 _: &mut Window,
16720 cx: &mut Context<Self>,
16721 ) {
16722 if let Some(project) = self.project.clone() {
16723 self.buffer.update(cx, |multi_buffer, cx| {
16724 project.update(cx, |project, cx| {
16725 project.stop_language_servers_for_buffers(
16726 multi_buffer.all_buffers().into_iter().collect(),
16727 HashSet::default(),
16728 cx,
16729 );
16730 cx.emit(project::Event::RefreshInlayHints);
16731 });
16732 });
16733 }
16734 }
16735
16736 fn cancel_language_server_work(
16737 workspace: &mut Workspace,
16738 _: &actions::CancelLanguageServerWork,
16739 _: &mut Window,
16740 cx: &mut Context<Workspace>,
16741 ) {
16742 let project = workspace.project();
16743 let buffers = workspace
16744 .active_item(cx)
16745 .and_then(|item| item.act_as::<Editor>(cx))
16746 .map_or(HashSet::default(), |editor| {
16747 editor.read(cx).buffer.read(cx).all_buffers()
16748 });
16749 project.update(cx, |project, cx| {
16750 project.cancel_language_server_work_for_buffers(buffers, cx);
16751 });
16752 }
16753
16754 fn show_character_palette(
16755 &mut self,
16756 _: &ShowCharacterPalette,
16757 window: &mut Window,
16758 _: &mut Context<Self>,
16759 ) {
16760 window.show_character_palette();
16761 }
16762
16763 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16764 if !self.diagnostics_enabled() {
16765 return;
16766 }
16767
16768 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16769 let buffer = self.buffer.read(cx).snapshot(cx);
16770 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16771 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16772 let is_valid = buffer
16773 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16774 .any(|entry| {
16775 entry.diagnostic.is_primary
16776 && !entry.range.is_empty()
16777 && entry.range.start == primary_range_start
16778 && entry.diagnostic.message == active_diagnostics.active_message
16779 });
16780
16781 if !is_valid {
16782 self.dismiss_diagnostics(cx);
16783 }
16784 }
16785 }
16786
16787 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16788 match &self.active_diagnostics {
16789 ActiveDiagnostic::Group(group) => Some(group),
16790 _ => None,
16791 }
16792 }
16793
16794 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16795 if !self.diagnostics_enabled() {
16796 return;
16797 }
16798 self.dismiss_diagnostics(cx);
16799 self.active_diagnostics = ActiveDiagnostic::All;
16800 }
16801
16802 fn activate_diagnostics(
16803 &mut self,
16804 buffer_id: BufferId,
16805 diagnostic: DiagnosticEntry<usize>,
16806 window: &mut Window,
16807 cx: &mut Context<Self>,
16808 ) {
16809 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16810 return;
16811 }
16812 self.dismiss_diagnostics(cx);
16813 let snapshot = self.snapshot(window, cx);
16814 let buffer = self.buffer.read(cx).snapshot(cx);
16815 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16816 return;
16817 };
16818
16819 let diagnostic_group = buffer
16820 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16821 .collect::<Vec<_>>();
16822
16823 let blocks =
16824 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16825
16826 let blocks = self.display_map.update(cx, |display_map, cx| {
16827 display_map.insert_blocks(blocks, cx).into_iter().collect()
16828 });
16829 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16830 active_range: buffer.anchor_before(diagnostic.range.start)
16831 ..buffer.anchor_after(diagnostic.range.end),
16832 active_message: diagnostic.diagnostic.message.clone(),
16833 group_id: diagnostic.diagnostic.group_id,
16834 blocks,
16835 });
16836 cx.notify();
16837 }
16838
16839 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16840 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16841 return;
16842 };
16843
16844 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16845 if let ActiveDiagnostic::Group(group) = prev {
16846 self.display_map.update(cx, |display_map, cx| {
16847 display_map.remove_blocks(group.blocks, cx);
16848 });
16849 cx.notify();
16850 }
16851 }
16852
16853 /// Disable inline diagnostics rendering for this editor.
16854 pub fn disable_inline_diagnostics(&mut self) {
16855 self.inline_diagnostics_enabled = false;
16856 self.inline_diagnostics_update = Task::ready(());
16857 self.inline_diagnostics.clear();
16858 }
16859
16860 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16861 self.diagnostics_enabled = false;
16862 self.dismiss_diagnostics(cx);
16863 self.inline_diagnostics_update = Task::ready(());
16864 self.inline_diagnostics.clear();
16865 }
16866
16867 pub fn diagnostics_enabled(&self) -> bool {
16868 self.diagnostics_enabled && self.mode.is_full()
16869 }
16870
16871 pub fn inline_diagnostics_enabled(&self) -> bool {
16872 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16873 }
16874
16875 pub fn show_inline_diagnostics(&self) -> bool {
16876 self.show_inline_diagnostics
16877 }
16878
16879 pub fn toggle_inline_diagnostics(
16880 &mut self,
16881 _: &ToggleInlineDiagnostics,
16882 window: &mut Window,
16883 cx: &mut Context<Editor>,
16884 ) {
16885 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16886 self.refresh_inline_diagnostics(false, window, cx);
16887 }
16888
16889 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16890 self.diagnostics_max_severity = severity;
16891 self.display_map.update(cx, |display_map, _| {
16892 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16893 });
16894 }
16895
16896 pub fn toggle_diagnostics(
16897 &mut self,
16898 _: &ToggleDiagnostics,
16899 window: &mut Window,
16900 cx: &mut Context<Editor>,
16901 ) {
16902 if !self.diagnostics_enabled() {
16903 return;
16904 }
16905
16906 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16907 EditorSettings::get_global(cx)
16908 .diagnostics_max_severity
16909 .filter(|severity| severity != &DiagnosticSeverity::Off)
16910 .unwrap_or(DiagnosticSeverity::Hint)
16911 } else {
16912 DiagnosticSeverity::Off
16913 };
16914 self.set_max_diagnostics_severity(new_severity, cx);
16915 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16916 self.active_diagnostics = ActiveDiagnostic::None;
16917 self.inline_diagnostics_update = Task::ready(());
16918 self.inline_diagnostics.clear();
16919 } else {
16920 self.refresh_inline_diagnostics(false, window, cx);
16921 }
16922
16923 cx.notify();
16924 }
16925
16926 pub fn toggle_minimap(
16927 &mut self,
16928 _: &ToggleMinimap,
16929 window: &mut Window,
16930 cx: &mut Context<Editor>,
16931 ) {
16932 if self.supports_minimap(cx) {
16933 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16934 }
16935 }
16936
16937 fn refresh_inline_diagnostics(
16938 &mut self,
16939 debounce: bool,
16940 window: &mut Window,
16941 cx: &mut Context<Self>,
16942 ) {
16943 let max_severity = ProjectSettings::get_global(cx)
16944 .diagnostics
16945 .inline
16946 .max_severity
16947 .unwrap_or(self.diagnostics_max_severity);
16948
16949 if !self.inline_diagnostics_enabled()
16950 || !self.show_inline_diagnostics
16951 || max_severity == DiagnosticSeverity::Off
16952 {
16953 self.inline_diagnostics_update = Task::ready(());
16954 self.inline_diagnostics.clear();
16955 return;
16956 }
16957
16958 let debounce_ms = ProjectSettings::get_global(cx)
16959 .diagnostics
16960 .inline
16961 .update_debounce_ms;
16962 let debounce = if debounce && debounce_ms > 0 {
16963 Some(Duration::from_millis(debounce_ms))
16964 } else {
16965 None
16966 };
16967 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16968 if let Some(debounce) = debounce {
16969 cx.background_executor().timer(debounce).await;
16970 }
16971 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16972 editor
16973 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16974 .ok()
16975 }) else {
16976 return;
16977 };
16978
16979 let new_inline_diagnostics = cx
16980 .background_spawn(async move {
16981 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16982 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16983 let message = diagnostic_entry
16984 .diagnostic
16985 .message
16986 .split_once('\n')
16987 .map(|(line, _)| line)
16988 .map(SharedString::new)
16989 .unwrap_or_else(|| {
16990 SharedString::from(diagnostic_entry.diagnostic.message)
16991 });
16992 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16993 let (Ok(i) | Err(i)) = inline_diagnostics
16994 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16995 inline_diagnostics.insert(
16996 i,
16997 (
16998 start_anchor,
16999 InlineDiagnostic {
17000 message,
17001 group_id: diagnostic_entry.diagnostic.group_id,
17002 start: diagnostic_entry.range.start.to_point(&snapshot),
17003 is_primary: diagnostic_entry.diagnostic.is_primary,
17004 severity: diagnostic_entry.diagnostic.severity,
17005 },
17006 ),
17007 );
17008 }
17009 inline_diagnostics
17010 })
17011 .await;
17012
17013 editor
17014 .update(cx, |editor, cx| {
17015 editor.inline_diagnostics = new_inline_diagnostics;
17016 cx.notify();
17017 })
17018 .ok();
17019 });
17020 }
17021
17022 fn pull_diagnostics(
17023 &mut self,
17024 buffer_id: Option<BufferId>,
17025 window: &Window,
17026 cx: &mut Context<Self>,
17027 ) -> Option<()> {
17028 if !self.mode().is_full() {
17029 return None;
17030 }
17031 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17032 .diagnostics
17033 .lsp_pull_diagnostics;
17034 if !pull_diagnostics_settings.enabled {
17035 return None;
17036 }
17037 let project = self.project()?.downgrade();
17038 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17039 let mut buffers = self.buffer.read(cx).all_buffers();
17040 if let Some(buffer_id) = buffer_id {
17041 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17042 }
17043
17044 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17045 cx.background_executor().timer(debounce).await;
17046
17047 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17048 buffers
17049 .into_iter()
17050 .filter_map(|buffer| {
17051 project
17052 .update(cx, |project, cx| {
17053 project.lsp_store().update(cx, |lsp_store, cx| {
17054 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17055 })
17056 })
17057 .ok()
17058 })
17059 .collect::<FuturesUnordered<_>>()
17060 }) else {
17061 return;
17062 };
17063
17064 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17065 match pull_task {
17066 Ok(()) => {
17067 if editor
17068 .update_in(cx, |editor, window, cx| {
17069 editor.update_diagnostics_state(window, cx);
17070 })
17071 .is_err()
17072 {
17073 return;
17074 }
17075 }
17076 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17077 }
17078 }
17079 });
17080
17081 Some(())
17082 }
17083
17084 pub fn set_selections_from_remote(
17085 &mut self,
17086 selections: Vec<Selection<Anchor>>,
17087 pending_selection: Option<Selection<Anchor>>,
17088 window: &mut Window,
17089 cx: &mut Context<Self>,
17090 ) {
17091 let old_cursor_position = self.selections.newest_anchor().head();
17092 self.selections.change_with(cx, |s| {
17093 s.select_anchors(selections);
17094 if let Some(pending_selection) = pending_selection {
17095 s.set_pending(pending_selection, SelectMode::Character);
17096 } else {
17097 s.clear_pending();
17098 }
17099 });
17100 self.selections_did_change(
17101 false,
17102 &old_cursor_position,
17103 SelectionEffects::default(),
17104 window,
17105 cx,
17106 );
17107 }
17108
17109 pub fn transact(
17110 &mut self,
17111 window: &mut Window,
17112 cx: &mut Context<Self>,
17113 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17114 ) -> Option<TransactionId> {
17115 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17116 this.start_transaction_at(Instant::now(), window, cx);
17117 update(this, window, cx);
17118 this.end_transaction_at(Instant::now(), cx)
17119 })
17120 }
17121
17122 pub fn start_transaction_at(
17123 &mut self,
17124 now: Instant,
17125 window: &mut Window,
17126 cx: &mut Context<Self>,
17127 ) -> Option<TransactionId> {
17128 self.end_selection(window, cx);
17129 if let Some(tx_id) = self
17130 .buffer
17131 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17132 {
17133 self.selection_history
17134 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17135 cx.emit(EditorEvent::TransactionBegun {
17136 transaction_id: tx_id,
17137 });
17138 Some(tx_id)
17139 } else {
17140 None
17141 }
17142 }
17143
17144 pub fn end_transaction_at(
17145 &mut self,
17146 now: Instant,
17147 cx: &mut Context<Self>,
17148 ) -> Option<TransactionId> {
17149 if let Some(transaction_id) = self
17150 .buffer
17151 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17152 {
17153 if let Some((_, end_selections)) =
17154 self.selection_history.transaction_mut(transaction_id)
17155 {
17156 *end_selections = Some(self.selections.disjoint_anchors());
17157 } else {
17158 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17159 }
17160
17161 cx.emit(EditorEvent::Edited { transaction_id });
17162 Some(transaction_id)
17163 } else {
17164 None
17165 }
17166 }
17167
17168 pub fn modify_transaction_selection_history(
17169 &mut self,
17170 transaction_id: TransactionId,
17171 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17172 ) -> bool {
17173 self.selection_history
17174 .transaction_mut(transaction_id)
17175 .map(modify)
17176 .is_some()
17177 }
17178
17179 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17180 if self.selection_mark_mode {
17181 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17182 s.move_with(|_, sel| {
17183 sel.collapse_to(sel.head(), SelectionGoal::None);
17184 });
17185 })
17186 }
17187 self.selection_mark_mode = true;
17188 cx.notify();
17189 }
17190
17191 pub fn swap_selection_ends(
17192 &mut self,
17193 _: &actions::SwapSelectionEnds,
17194 window: &mut Window,
17195 cx: &mut Context<Self>,
17196 ) {
17197 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17198 s.move_with(|_, sel| {
17199 if sel.start != sel.end {
17200 sel.reversed = !sel.reversed
17201 }
17202 });
17203 });
17204 self.request_autoscroll(Autoscroll::newest(), cx);
17205 cx.notify();
17206 }
17207
17208 pub fn toggle_focus(
17209 workspace: &mut Workspace,
17210 _: &actions::ToggleFocus,
17211 window: &mut Window,
17212 cx: &mut Context<Workspace>,
17213 ) {
17214 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17215 return;
17216 };
17217 workspace.activate_item(&item, true, true, window, cx);
17218 }
17219
17220 pub fn toggle_fold(
17221 &mut self,
17222 _: &actions::ToggleFold,
17223 window: &mut Window,
17224 cx: &mut Context<Self>,
17225 ) {
17226 if self.is_singleton(cx) {
17227 let selection = self.selections.newest::<Point>(cx);
17228
17229 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17230 let range = if selection.is_empty() {
17231 let point = selection.head().to_display_point(&display_map);
17232 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17233 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17234 .to_point(&display_map);
17235 start..end
17236 } else {
17237 selection.range()
17238 };
17239 if display_map.folds_in_range(range).next().is_some() {
17240 self.unfold_lines(&Default::default(), window, cx)
17241 } else {
17242 self.fold(&Default::default(), window, cx)
17243 }
17244 } else {
17245 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17246 let buffer_ids: HashSet<_> = self
17247 .selections
17248 .disjoint_anchor_ranges()
17249 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17250 .collect();
17251
17252 let should_unfold = buffer_ids
17253 .iter()
17254 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17255
17256 for buffer_id in buffer_ids {
17257 if should_unfold {
17258 self.unfold_buffer(buffer_id, cx);
17259 } else {
17260 self.fold_buffer(buffer_id, cx);
17261 }
17262 }
17263 }
17264 }
17265
17266 pub fn toggle_fold_recursive(
17267 &mut self,
17268 _: &actions::ToggleFoldRecursive,
17269 window: &mut Window,
17270 cx: &mut Context<Self>,
17271 ) {
17272 let selection = self.selections.newest::<Point>(cx);
17273
17274 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17275 let range = if selection.is_empty() {
17276 let point = selection.head().to_display_point(&display_map);
17277 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17278 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17279 .to_point(&display_map);
17280 start..end
17281 } else {
17282 selection.range()
17283 };
17284 if display_map.folds_in_range(range).next().is_some() {
17285 self.unfold_recursive(&Default::default(), window, cx)
17286 } else {
17287 self.fold_recursive(&Default::default(), window, cx)
17288 }
17289 }
17290
17291 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17292 if self.is_singleton(cx) {
17293 let mut to_fold = Vec::new();
17294 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17295 let selections = self.selections.all_adjusted(cx);
17296
17297 for selection in selections {
17298 let range = selection.range().sorted();
17299 let buffer_start_row = range.start.row;
17300
17301 if range.start.row != range.end.row {
17302 let mut found = false;
17303 let mut row = range.start.row;
17304 while row <= range.end.row {
17305 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17306 {
17307 found = true;
17308 row = crease.range().end.row + 1;
17309 to_fold.push(crease);
17310 } else {
17311 row += 1
17312 }
17313 }
17314 if found {
17315 continue;
17316 }
17317 }
17318
17319 for row in (0..=range.start.row).rev() {
17320 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17321 && crease.range().end.row >= buffer_start_row
17322 {
17323 to_fold.push(crease);
17324 if row <= range.start.row {
17325 break;
17326 }
17327 }
17328 }
17329 }
17330
17331 self.fold_creases(to_fold, true, window, cx);
17332 } else {
17333 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17334 let buffer_ids = self
17335 .selections
17336 .disjoint_anchor_ranges()
17337 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17338 .collect::<HashSet<_>>();
17339 for buffer_id in buffer_ids {
17340 self.fold_buffer(buffer_id, cx);
17341 }
17342 }
17343 }
17344
17345 pub fn toggle_fold_all(
17346 &mut self,
17347 _: &actions::ToggleFoldAll,
17348 window: &mut Window,
17349 cx: &mut Context<Self>,
17350 ) {
17351 if self.buffer.read(cx).is_singleton() {
17352 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17353 let has_folds = display_map
17354 .folds_in_range(0..display_map.buffer_snapshot.len())
17355 .next()
17356 .is_some();
17357
17358 if has_folds {
17359 self.unfold_all(&actions::UnfoldAll, window, cx);
17360 } else {
17361 self.fold_all(&actions::FoldAll, window, cx);
17362 }
17363 } else {
17364 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17365 let should_unfold = buffer_ids
17366 .iter()
17367 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17368
17369 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17370 editor
17371 .update_in(cx, |editor, _, cx| {
17372 for buffer_id in buffer_ids {
17373 if should_unfold {
17374 editor.unfold_buffer(buffer_id, cx);
17375 } else {
17376 editor.fold_buffer(buffer_id, cx);
17377 }
17378 }
17379 })
17380 .ok();
17381 });
17382 }
17383 }
17384
17385 fn fold_at_level(
17386 &mut self,
17387 fold_at: &FoldAtLevel,
17388 window: &mut Window,
17389 cx: &mut Context<Self>,
17390 ) {
17391 if !self.buffer.read(cx).is_singleton() {
17392 return;
17393 }
17394
17395 let fold_at_level = fold_at.0;
17396 let snapshot = self.buffer.read(cx).snapshot(cx);
17397 let mut to_fold = Vec::new();
17398 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17399
17400 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17401 while start_row < end_row {
17402 match self
17403 .snapshot(window, cx)
17404 .crease_for_buffer_row(MultiBufferRow(start_row))
17405 {
17406 Some(crease) => {
17407 let nested_start_row = crease.range().start.row + 1;
17408 let nested_end_row = crease.range().end.row;
17409
17410 if current_level < fold_at_level {
17411 stack.push((nested_start_row, nested_end_row, current_level + 1));
17412 } else if current_level == fold_at_level {
17413 to_fold.push(crease);
17414 }
17415
17416 start_row = nested_end_row + 1;
17417 }
17418 None => start_row += 1,
17419 }
17420 }
17421 }
17422
17423 self.fold_creases(to_fold, true, window, cx);
17424 }
17425
17426 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17427 if self.buffer.read(cx).is_singleton() {
17428 let mut fold_ranges = Vec::new();
17429 let snapshot = self.buffer.read(cx).snapshot(cx);
17430
17431 for row in 0..snapshot.max_row().0 {
17432 if let Some(foldable_range) = self
17433 .snapshot(window, cx)
17434 .crease_for_buffer_row(MultiBufferRow(row))
17435 {
17436 fold_ranges.push(foldable_range);
17437 }
17438 }
17439
17440 self.fold_creases(fold_ranges, true, window, cx);
17441 } else {
17442 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17443 editor
17444 .update_in(cx, |editor, _, cx| {
17445 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17446 editor.fold_buffer(buffer_id, cx);
17447 }
17448 })
17449 .ok();
17450 });
17451 }
17452 }
17453
17454 pub fn fold_function_bodies(
17455 &mut self,
17456 _: &actions::FoldFunctionBodies,
17457 window: &mut Window,
17458 cx: &mut Context<Self>,
17459 ) {
17460 let snapshot = self.buffer.read(cx).snapshot(cx);
17461
17462 let ranges = snapshot
17463 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17464 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17465 .collect::<Vec<_>>();
17466
17467 let creases = ranges
17468 .into_iter()
17469 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17470 .collect();
17471
17472 self.fold_creases(creases, true, window, cx);
17473 }
17474
17475 pub fn fold_recursive(
17476 &mut self,
17477 _: &actions::FoldRecursive,
17478 window: &mut Window,
17479 cx: &mut Context<Self>,
17480 ) {
17481 let mut to_fold = Vec::new();
17482 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17483 let selections = self.selections.all_adjusted(cx);
17484
17485 for selection in selections {
17486 let range = selection.range().sorted();
17487 let buffer_start_row = range.start.row;
17488
17489 if range.start.row != range.end.row {
17490 let mut found = false;
17491 for row in range.start.row..=range.end.row {
17492 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17493 found = true;
17494 to_fold.push(crease);
17495 }
17496 }
17497 if found {
17498 continue;
17499 }
17500 }
17501
17502 for row in (0..=range.start.row).rev() {
17503 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17504 if crease.range().end.row >= buffer_start_row {
17505 to_fold.push(crease);
17506 } else {
17507 break;
17508 }
17509 }
17510 }
17511 }
17512
17513 self.fold_creases(to_fold, true, window, cx);
17514 }
17515
17516 pub fn fold_at(
17517 &mut self,
17518 buffer_row: MultiBufferRow,
17519 window: &mut Window,
17520 cx: &mut Context<Self>,
17521 ) {
17522 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17523
17524 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17525 let autoscroll = self
17526 .selections
17527 .all::<Point>(cx)
17528 .iter()
17529 .any(|selection| crease.range().overlaps(&selection.range()));
17530
17531 self.fold_creases(vec![crease], autoscroll, window, cx);
17532 }
17533 }
17534
17535 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17536 if self.is_singleton(cx) {
17537 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17538 let buffer = &display_map.buffer_snapshot;
17539 let selections = self.selections.all::<Point>(cx);
17540 let ranges = selections
17541 .iter()
17542 .map(|s| {
17543 let range = s.display_range(&display_map).sorted();
17544 let mut start = range.start.to_point(&display_map);
17545 let mut end = range.end.to_point(&display_map);
17546 start.column = 0;
17547 end.column = buffer.line_len(MultiBufferRow(end.row));
17548 start..end
17549 })
17550 .collect::<Vec<_>>();
17551
17552 self.unfold_ranges(&ranges, true, true, cx);
17553 } else {
17554 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17555 let buffer_ids = self
17556 .selections
17557 .disjoint_anchor_ranges()
17558 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17559 .collect::<HashSet<_>>();
17560 for buffer_id in buffer_ids {
17561 self.unfold_buffer(buffer_id, cx);
17562 }
17563 }
17564 }
17565
17566 pub fn unfold_recursive(
17567 &mut self,
17568 _: &UnfoldRecursive,
17569 _window: &mut Window,
17570 cx: &mut Context<Self>,
17571 ) {
17572 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17573 let selections = self.selections.all::<Point>(cx);
17574 let ranges = selections
17575 .iter()
17576 .map(|s| {
17577 let mut range = s.display_range(&display_map).sorted();
17578 *range.start.column_mut() = 0;
17579 *range.end.column_mut() = display_map.line_len(range.end.row());
17580 let start = range.start.to_point(&display_map);
17581 let end = range.end.to_point(&display_map);
17582 start..end
17583 })
17584 .collect::<Vec<_>>();
17585
17586 self.unfold_ranges(&ranges, true, true, cx);
17587 }
17588
17589 pub fn unfold_at(
17590 &mut self,
17591 buffer_row: MultiBufferRow,
17592 _window: &mut Window,
17593 cx: &mut Context<Self>,
17594 ) {
17595 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17596
17597 let intersection_range = Point::new(buffer_row.0, 0)
17598 ..Point::new(
17599 buffer_row.0,
17600 display_map.buffer_snapshot.line_len(buffer_row),
17601 );
17602
17603 let autoscroll = self
17604 .selections
17605 .all::<Point>(cx)
17606 .iter()
17607 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17608
17609 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17610 }
17611
17612 pub fn unfold_all(
17613 &mut self,
17614 _: &actions::UnfoldAll,
17615 _window: &mut Window,
17616 cx: &mut Context<Self>,
17617 ) {
17618 if self.buffer.read(cx).is_singleton() {
17619 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17620 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17621 } else {
17622 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17623 editor
17624 .update(cx, |editor, cx| {
17625 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17626 editor.unfold_buffer(buffer_id, cx);
17627 }
17628 })
17629 .ok();
17630 });
17631 }
17632 }
17633
17634 pub fn fold_selected_ranges(
17635 &mut self,
17636 _: &FoldSelectedRanges,
17637 window: &mut Window,
17638 cx: &mut Context<Self>,
17639 ) {
17640 let selections = self.selections.all_adjusted(cx);
17641 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17642 let ranges = selections
17643 .into_iter()
17644 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17645 .collect::<Vec<_>>();
17646 self.fold_creases(ranges, true, window, cx);
17647 }
17648
17649 pub fn fold_ranges<T: ToOffset + Clone>(
17650 &mut self,
17651 ranges: Vec<Range<T>>,
17652 auto_scroll: bool,
17653 window: &mut Window,
17654 cx: &mut Context<Self>,
17655 ) {
17656 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17657 let ranges = ranges
17658 .into_iter()
17659 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17660 .collect::<Vec<_>>();
17661 self.fold_creases(ranges, auto_scroll, window, cx);
17662 }
17663
17664 pub fn fold_creases<T: ToOffset + Clone>(
17665 &mut self,
17666 creases: Vec<Crease<T>>,
17667 auto_scroll: bool,
17668 _window: &mut Window,
17669 cx: &mut Context<Self>,
17670 ) {
17671 if creases.is_empty() {
17672 return;
17673 }
17674
17675 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17676
17677 if auto_scroll {
17678 self.request_autoscroll(Autoscroll::fit(), cx);
17679 }
17680
17681 cx.notify();
17682
17683 self.scrollbar_marker_state.dirty = true;
17684 self.folds_did_change(cx);
17685 }
17686
17687 /// Removes any folds whose ranges intersect any of the given ranges.
17688 pub fn unfold_ranges<T: ToOffset + Clone>(
17689 &mut self,
17690 ranges: &[Range<T>],
17691 inclusive: bool,
17692 auto_scroll: bool,
17693 cx: &mut Context<Self>,
17694 ) {
17695 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17696 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17697 });
17698 self.folds_did_change(cx);
17699 }
17700
17701 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17702 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17703 return;
17704 }
17705 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17706 self.display_map.update(cx, |display_map, cx| {
17707 display_map.fold_buffers([buffer_id], cx)
17708 });
17709 cx.emit(EditorEvent::BufferFoldToggled {
17710 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17711 folded: true,
17712 });
17713 cx.notify();
17714 }
17715
17716 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17717 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17718 return;
17719 }
17720 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17721 self.display_map.update(cx, |display_map, cx| {
17722 display_map.unfold_buffers([buffer_id], cx);
17723 });
17724 cx.emit(EditorEvent::BufferFoldToggled {
17725 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17726 folded: false,
17727 });
17728 cx.notify();
17729 }
17730
17731 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17732 self.display_map.read(cx).is_buffer_folded(buffer)
17733 }
17734
17735 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17736 self.display_map.read(cx).folded_buffers()
17737 }
17738
17739 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17740 self.display_map.update(cx, |display_map, cx| {
17741 display_map.disable_header_for_buffer(buffer_id, cx);
17742 });
17743 cx.notify();
17744 }
17745
17746 /// Removes any folds with the given ranges.
17747 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17748 &mut self,
17749 ranges: &[Range<T>],
17750 type_id: TypeId,
17751 auto_scroll: bool,
17752 cx: &mut Context<Self>,
17753 ) {
17754 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17755 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17756 });
17757 self.folds_did_change(cx);
17758 }
17759
17760 fn remove_folds_with<T: ToOffset + Clone>(
17761 &mut self,
17762 ranges: &[Range<T>],
17763 auto_scroll: bool,
17764 cx: &mut Context<Self>,
17765 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17766 ) {
17767 if ranges.is_empty() {
17768 return;
17769 }
17770
17771 let mut buffers_affected = HashSet::default();
17772 let multi_buffer = self.buffer().read(cx);
17773 for range in ranges {
17774 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17775 buffers_affected.insert(buffer.read(cx).remote_id());
17776 };
17777 }
17778
17779 self.display_map.update(cx, update);
17780
17781 if auto_scroll {
17782 self.request_autoscroll(Autoscroll::fit(), cx);
17783 }
17784
17785 cx.notify();
17786 self.scrollbar_marker_state.dirty = true;
17787 self.active_indent_guides_state.dirty = true;
17788 }
17789
17790 pub fn update_renderer_widths(
17791 &mut self,
17792 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17793 cx: &mut Context<Self>,
17794 ) -> bool {
17795 self.display_map
17796 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17797 }
17798
17799 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17800 self.display_map.read(cx).fold_placeholder.clone()
17801 }
17802
17803 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17804 self.buffer.update(cx, |buffer, cx| {
17805 buffer.set_all_diff_hunks_expanded(cx);
17806 });
17807 }
17808
17809 pub fn expand_all_diff_hunks(
17810 &mut self,
17811 _: &ExpandAllDiffHunks,
17812 _window: &mut Window,
17813 cx: &mut Context<Self>,
17814 ) {
17815 self.buffer.update(cx, |buffer, cx| {
17816 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17817 });
17818 }
17819
17820 pub fn toggle_selected_diff_hunks(
17821 &mut self,
17822 _: &ToggleSelectedDiffHunks,
17823 _window: &mut Window,
17824 cx: &mut Context<Self>,
17825 ) {
17826 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17827 self.toggle_diff_hunks_in_ranges(ranges, cx);
17828 }
17829
17830 pub fn diff_hunks_in_ranges<'a>(
17831 &'a self,
17832 ranges: &'a [Range<Anchor>],
17833 buffer: &'a MultiBufferSnapshot,
17834 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17835 ranges.iter().flat_map(move |range| {
17836 let end_excerpt_id = range.end.excerpt_id;
17837 let range = range.to_point(buffer);
17838 let mut peek_end = range.end;
17839 if range.end.row < buffer.max_row().0 {
17840 peek_end = Point::new(range.end.row + 1, 0);
17841 }
17842 buffer
17843 .diff_hunks_in_range(range.start..peek_end)
17844 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17845 })
17846 }
17847
17848 pub fn has_stageable_diff_hunks_in_ranges(
17849 &self,
17850 ranges: &[Range<Anchor>],
17851 snapshot: &MultiBufferSnapshot,
17852 ) -> bool {
17853 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
17854 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17855 }
17856
17857 pub fn toggle_staged_selected_diff_hunks(
17858 &mut self,
17859 _: &::git::ToggleStaged,
17860 _: &mut Window,
17861 cx: &mut Context<Self>,
17862 ) {
17863 let snapshot = self.buffer.read(cx).snapshot(cx);
17864 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17865 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17866 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17867 }
17868
17869 pub fn set_render_diff_hunk_controls(
17870 &mut self,
17871 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17872 cx: &mut Context<Self>,
17873 ) {
17874 self.render_diff_hunk_controls = render_diff_hunk_controls;
17875 cx.notify();
17876 }
17877
17878 pub fn stage_and_next(
17879 &mut self,
17880 _: &::git::StageAndNext,
17881 window: &mut Window,
17882 cx: &mut Context<Self>,
17883 ) {
17884 self.do_stage_or_unstage_and_next(true, window, cx);
17885 }
17886
17887 pub fn unstage_and_next(
17888 &mut self,
17889 _: &::git::UnstageAndNext,
17890 window: &mut Window,
17891 cx: &mut Context<Self>,
17892 ) {
17893 self.do_stage_or_unstage_and_next(false, window, cx);
17894 }
17895
17896 pub fn stage_or_unstage_diff_hunks(
17897 &mut self,
17898 stage: bool,
17899 ranges: Vec<Range<Anchor>>,
17900 cx: &mut Context<Self>,
17901 ) {
17902 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17903 cx.spawn(async move |this, cx| {
17904 task.await?;
17905 this.update(cx, |this, cx| {
17906 let snapshot = this.buffer.read(cx).snapshot(cx);
17907 let chunk_by = this
17908 .diff_hunks_in_ranges(&ranges, &snapshot)
17909 .chunk_by(|hunk| hunk.buffer_id);
17910 for (buffer_id, hunks) in &chunk_by {
17911 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17912 }
17913 })
17914 })
17915 .detach_and_log_err(cx);
17916 }
17917
17918 fn save_buffers_for_ranges_if_needed(
17919 &mut self,
17920 ranges: &[Range<Anchor>],
17921 cx: &mut Context<Editor>,
17922 ) -> Task<Result<()>> {
17923 let multibuffer = self.buffer.read(cx);
17924 let snapshot = multibuffer.read(cx);
17925 let buffer_ids: HashSet<_> = ranges
17926 .iter()
17927 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17928 .collect();
17929 drop(snapshot);
17930
17931 let mut buffers = HashSet::default();
17932 for buffer_id in buffer_ids {
17933 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17934 let buffer = buffer_entity.read(cx);
17935 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17936 {
17937 buffers.insert(buffer_entity);
17938 }
17939 }
17940 }
17941
17942 if let Some(project) = &self.project {
17943 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17944 } else {
17945 Task::ready(Ok(()))
17946 }
17947 }
17948
17949 fn do_stage_or_unstage_and_next(
17950 &mut self,
17951 stage: bool,
17952 window: &mut Window,
17953 cx: &mut Context<Self>,
17954 ) {
17955 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17956
17957 if ranges.iter().any(|range| range.start != range.end) {
17958 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17959 return;
17960 }
17961
17962 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17963 let snapshot = self.snapshot(window, cx);
17964 let position = self.selections.newest::<Point>(cx).head();
17965 let mut row = snapshot
17966 .buffer_snapshot
17967 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17968 .find(|hunk| hunk.row_range.start.0 > position.row)
17969 .map(|hunk| hunk.row_range.start);
17970
17971 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17972 // Outside of the project diff editor, wrap around to the beginning.
17973 if !all_diff_hunks_expanded {
17974 row = row.or_else(|| {
17975 snapshot
17976 .buffer_snapshot
17977 .diff_hunks_in_range(Point::zero()..position)
17978 .find(|hunk| hunk.row_range.end.0 < position.row)
17979 .map(|hunk| hunk.row_range.start)
17980 });
17981 }
17982
17983 if let Some(row) = row {
17984 let destination = Point::new(row.0, 0);
17985 let autoscroll = Autoscroll::center();
17986
17987 self.unfold_ranges(&[destination..destination], false, false, cx);
17988 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17989 s.select_ranges([destination..destination]);
17990 });
17991 }
17992 }
17993
17994 fn do_stage_or_unstage(
17995 &self,
17996 stage: bool,
17997 buffer_id: BufferId,
17998 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17999 cx: &mut App,
18000 ) -> Option<()> {
18001 let project = self.project()?;
18002 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18003 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18004 let buffer_snapshot = buffer.read(cx).snapshot();
18005 let file_exists = buffer_snapshot
18006 .file()
18007 .is_some_and(|file| file.disk_state().exists());
18008 diff.update(cx, |diff, cx| {
18009 diff.stage_or_unstage_hunks(
18010 stage,
18011 &hunks
18012 .map(|hunk| buffer_diff::DiffHunk {
18013 buffer_range: hunk.buffer_range,
18014 diff_base_byte_range: hunk.diff_base_byte_range,
18015 secondary_status: hunk.secondary_status,
18016 range: Point::zero()..Point::zero(), // unused
18017 })
18018 .collect::<Vec<_>>(),
18019 &buffer_snapshot,
18020 file_exists,
18021 cx,
18022 )
18023 });
18024 None
18025 }
18026
18027 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18028 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18029 self.buffer
18030 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18031 }
18032
18033 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18034 self.buffer.update(cx, |buffer, cx| {
18035 let ranges = vec![Anchor::min()..Anchor::max()];
18036 if !buffer.all_diff_hunks_expanded()
18037 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18038 {
18039 buffer.collapse_diff_hunks(ranges, cx);
18040 true
18041 } else {
18042 false
18043 }
18044 })
18045 }
18046
18047 fn toggle_diff_hunks_in_ranges(
18048 &mut self,
18049 ranges: Vec<Range<Anchor>>,
18050 cx: &mut Context<Editor>,
18051 ) {
18052 self.buffer.update(cx, |buffer, cx| {
18053 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18054 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18055 })
18056 }
18057
18058 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18059 self.buffer.update(cx, |buffer, cx| {
18060 let snapshot = buffer.snapshot(cx);
18061 let excerpt_id = range.end.excerpt_id;
18062 let point_range = range.to_point(&snapshot);
18063 let expand = !buffer.single_hunk_is_expanded(range, cx);
18064 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18065 })
18066 }
18067
18068 pub(crate) fn apply_all_diff_hunks(
18069 &mut self,
18070 _: &ApplyAllDiffHunks,
18071 window: &mut Window,
18072 cx: &mut Context<Self>,
18073 ) {
18074 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18075
18076 let buffers = self.buffer.read(cx).all_buffers();
18077 for branch_buffer in buffers {
18078 branch_buffer.update(cx, |branch_buffer, cx| {
18079 branch_buffer.merge_into_base(Vec::new(), cx);
18080 });
18081 }
18082
18083 if let Some(project) = self.project.clone() {
18084 self.save(
18085 SaveOptions {
18086 format: true,
18087 autosave: false,
18088 },
18089 project,
18090 window,
18091 cx,
18092 )
18093 .detach_and_log_err(cx);
18094 }
18095 }
18096
18097 pub(crate) fn apply_selected_diff_hunks(
18098 &mut self,
18099 _: &ApplyDiffHunk,
18100 window: &mut Window,
18101 cx: &mut Context<Self>,
18102 ) {
18103 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18104 let snapshot = self.snapshot(window, cx);
18105 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18106 let mut ranges_by_buffer = HashMap::default();
18107 self.transact(window, cx, |editor, _window, cx| {
18108 for hunk in hunks {
18109 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18110 ranges_by_buffer
18111 .entry(buffer.clone())
18112 .or_insert_with(Vec::new)
18113 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18114 }
18115 }
18116
18117 for (buffer, ranges) in ranges_by_buffer {
18118 buffer.update(cx, |buffer, cx| {
18119 buffer.merge_into_base(ranges, cx);
18120 });
18121 }
18122 });
18123
18124 if let Some(project) = self.project.clone() {
18125 self.save(
18126 SaveOptions {
18127 format: true,
18128 autosave: false,
18129 },
18130 project,
18131 window,
18132 cx,
18133 )
18134 .detach_and_log_err(cx);
18135 }
18136 }
18137
18138 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18139 if hovered != self.gutter_hovered {
18140 self.gutter_hovered = hovered;
18141 cx.notify();
18142 }
18143 }
18144
18145 pub fn insert_blocks(
18146 &mut self,
18147 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18148 autoscroll: Option<Autoscroll>,
18149 cx: &mut Context<Self>,
18150 ) -> Vec<CustomBlockId> {
18151 let blocks = self
18152 .display_map
18153 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18154 if let Some(autoscroll) = autoscroll {
18155 self.request_autoscroll(autoscroll, cx);
18156 }
18157 cx.notify();
18158 blocks
18159 }
18160
18161 pub fn resize_blocks(
18162 &mut self,
18163 heights: HashMap<CustomBlockId, u32>,
18164 autoscroll: Option<Autoscroll>,
18165 cx: &mut Context<Self>,
18166 ) {
18167 self.display_map
18168 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18169 if let Some(autoscroll) = autoscroll {
18170 self.request_autoscroll(autoscroll, cx);
18171 }
18172 cx.notify();
18173 }
18174
18175 pub fn replace_blocks(
18176 &mut self,
18177 renderers: HashMap<CustomBlockId, RenderBlock>,
18178 autoscroll: Option<Autoscroll>,
18179 cx: &mut Context<Self>,
18180 ) {
18181 self.display_map
18182 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18183 if let Some(autoscroll) = autoscroll {
18184 self.request_autoscroll(autoscroll, cx);
18185 }
18186 cx.notify();
18187 }
18188
18189 pub fn remove_blocks(
18190 &mut self,
18191 block_ids: HashSet<CustomBlockId>,
18192 autoscroll: Option<Autoscroll>,
18193 cx: &mut Context<Self>,
18194 ) {
18195 self.display_map.update(cx, |display_map, cx| {
18196 display_map.remove_blocks(block_ids, cx)
18197 });
18198 if let Some(autoscroll) = autoscroll {
18199 self.request_autoscroll(autoscroll, cx);
18200 }
18201 cx.notify();
18202 }
18203
18204 pub fn row_for_block(
18205 &self,
18206 block_id: CustomBlockId,
18207 cx: &mut Context<Self>,
18208 ) -> Option<DisplayRow> {
18209 self.display_map
18210 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18211 }
18212
18213 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18214 self.focused_block = Some(focused_block);
18215 }
18216
18217 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18218 self.focused_block.take()
18219 }
18220
18221 pub fn insert_creases(
18222 &mut self,
18223 creases: impl IntoIterator<Item = Crease<Anchor>>,
18224 cx: &mut Context<Self>,
18225 ) -> Vec<CreaseId> {
18226 self.display_map
18227 .update(cx, |map, cx| map.insert_creases(creases, cx))
18228 }
18229
18230 pub fn remove_creases(
18231 &mut self,
18232 ids: impl IntoIterator<Item = CreaseId>,
18233 cx: &mut Context<Self>,
18234 ) -> Vec<(CreaseId, Range<Anchor>)> {
18235 self.display_map
18236 .update(cx, |map, cx| map.remove_creases(ids, cx))
18237 }
18238
18239 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18240 self.display_map
18241 .update(cx, |map, cx| map.snapshot(cx))
18242 .longest_row()
18243 }
18244
18245 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18246 self.display_map
18247 .update(cx, |map, cx| map.snapshot(cx))
18248 .max_point()
18249 }
18250
18251 pub fn text(&self, cx: &App) -> String {
18252 self.buffer.read(cx).read(cx).text()
18253 }
18254
18255 pub fn is_empty(&self, cx: &App) -> bool {
18256 self.buffer.read(cx).read(cx).is_empty()
18257 }
18258
18259 pub fn text_option(&self, cx: &App) -> Option<String> {
18260 let text = self.text(cx);
18261 let text = text.trim();
18262
18263 if text.is_empty() {
18264 return None;
18265 }
18266
18267 Some(text.to_string())
18268 }
18269
18270 pub fn set_text(
18271 &mut self,
18272 text: impl Into<Arc<str>>,
18273 window: &mut Window,
18274 cx: &mut Context<Self>,
18275 ) {
18276 self.transact(window, cx, |this, _, cx| {
18277 this.buffer
18278 .read(cx)
18279 .as_singleton()
18280 .expect("you can only call set_text on editors for singleton buffers")
18281 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18282 });
18283 }
18284
18285 pub fn display_text(&self, cx: &mut App) -> String {
18286 self.display_map
18287 .update(cx, |map, cx| map.snapshot(cx))
18288 .text()
18289 }
18290
18291 fn create_minimap(
18292 &self,
18293 minimap_settings: MinimapSettings,
18294 window: &mut Window,
18295 cx: &mut Context<Self>,
18296 ) -> Option<Entity<Self>> {
18297 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18298 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18299 }
18300
18301 fn initialize_new_minimap(
18302 &self,
18303 minimap_settings: MinimapSettings,
18304 window: &mut Window,
18305 cx: &mut Context<Self>,
18306 ) -> Entity<Self> {
18307 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18308
18309 let mut minimap = Editor::new_internal(
18310 EditorMode::Minimap {
18311 parent: cx.weak_entity(),
18312 },
18313 self.buffer.clone(),
18314 None,
18315 Some(self.display_map.clone()),
18316 window,
18317 cx,
18318 );
18319 minimap.scroll_manager.clone_state(&self.scroll_manager);
18320 minimap.set_text_style_refinement(TextStyleRefinement {
18321 font_size: Some(MINIMAP_FONT_SIZE),
18322 font_weight: Some(MINIMAP_FONT_WEIGHT),
18323 ..Default::default()
18324 });
18325 minimap.update_minimap_configuration(minimap_settings, cx);
18326 cx.new(|_| minimap)
18327 }
18328
18329 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18330 let current_line_highlight = minimap_settings
18331 .current_line_highlight
18332 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18333 self.set_current_line_highlight(Some(current_line_highlight));
18334 }
18335
18336 pub fn minimap(&self) -> Option<&Entity<Self>> {
18337 self.minimap
18338 .as_ref()
18339 .filter(|_| self.minimap_visibility.visible())
18340 }
18341
18342 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18343 let mut wrap_guides = smallvec![];
18344
18345 if self.show_wrap_guides == Some(false) {
18346 return wrap_guides;
18347 }
18348
18349 let settings = self.buffer.read(cx).language_settings(cx);
18350 if settings.show_wrap_guides {
18351 match self.soft_wrap_mode(cx) {
18352 SoftWrap::Column(soft_wrap) => {
18353 wrap_guides.push((soft_wrap as usize, true));
18354 }
18355 SoftWrap::Bounded(soft_wrap) => {
18356 wrap_guides.push((soft_wrap as usize, true));
18357 }
18358 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18359 }
18360 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18361 }
18362
18363 wrap_guides
18364 }
18365
18366 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18367 let settings = self.buffer.read(cx).language_settings(cx);
18368 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18369 match mode {
18370 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18371 SoftWrap::None
18372 }
18373 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18374 language_settings::SoftWrap::PreferredLineLength => {
18375 SoftWrap::Column(settings.preferred_line_length)
18376 }
18377 language_settings::SoftWrap::Bounded => {
18378 SoftWrap::Bounded(settings.preferred_line_length)
18379 }
18380 }
18381 }
18382
18383 pub fn set_soft_wrap_mode(
18384 &mut self,
18385 mode: language_settings::SoftWrap,
18386
18387 cx: &mut Context<Self>,
18388 ) {
18389 self.soft_wrap_mode_override = Some(mode);
18390 cx.notify();
18391 }
18392
18393 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18394 self.hard_wrap = hard_wrap;
18395 cx.notify();
18396 }
18397
18398 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18399 self.text_style_refinement = Some(style);
18400 }
18401
18402 /// called by the Element so we know what style we were most recently rendered with.
18403 pub(crate) fn set_style(
18404 &mut self,
18405 style: EditorStyle,
18406 window: &mut Window,
18407 cx: &mut Context<Self>,
18408 ) {
18409 // We intentionally do not inform the display map about the minimap style
18410 // so that wrapping is not recalculated and stays consistent for the editor
18411 // and its linked minimap.
18412 if !self.mode.is_minimap() {
18413 let rem_size = window.rem_size();
18414 self.display_map.update(cx, |map, cx| {
18415 map.set_font(
18416 style.text.font(),
18417 style.text.font_size.to_pixels(rem_size),
18418 cx,
18419 )
18420 });
18421 }
18422 self.style = Some(style);
18423 }
18424
18425 pub fn style(&self) -> Option<&EditorStyle> {
18426 self.style.as_ref()
18427 }
18428
18429 // Called by the element. This method is not designed to be called outside of the editor
18430 // element's layout code because it does not notify when rewrapping is computed synchronously.
18431 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18432 self.display_map
18433 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18434 }
18435
18436 pub fn set_soft_wrap(&mut self) {
18437 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18438 }
18439
18440 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18441 if self.soft_wrap_mode_override.is_some() {
18442 self.soft_wrap_mode_override.take();
18443 } else {
18444 let soft_wrap = match self.soft_wrap_mode(cx) {
18445 SoftWrap::GitDiff => return,
18446 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18447 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18448 language_settings::SoftWrap::None
18449 }
18450 };
18451 self.soft_wrap_mode_override = Some(soft_wrap);
18452 }
18453 cx.notify();
18454 }
18455
18456 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18457 let Some(workspace) = self.workspace() else {
18458 return;
18459 };
18460 let fs = workspace.read(cx).app_state().fs.clone();
18461 let current_show = TabBarSettings::get_global(cx).show;
18462 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18463 setting.show = Some(!current_show);
18464 });
18465 }
18466
18467 pub fn toggle_indent_guides(
18468 &mut self,
18469 _: &ToggleIndentGuides,
18470 _: &mut Window,
18471 cx: &mut Context<Self>,
18472 ) {
18473 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18474 self.buffer
18475 .read(cx)
18476 .language_settings(cx)
18477 .indent_guides
18478 .enabled
18479 });
18480 self.show_indent_guides = Some(!currently_enabled);
18481 cx.notify();
18482 }
18483
18484 fn should_show_indent_guides(&self) -> Option<bool> {
18485 self.show_indent_guides
18486 }
18487
18488 pub fn toggle_line_numbers(
18489 &mut self,
18490 _: &ToggleLineNumbers,
18491 _: &mut Window,
18492 cx: &mut Context<Self>,
18493 ) {
18494 let mut editor_settings = EditorSettings::get_global(cx).clone();
18495 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18496 EditorSettings::override_global(editor_settings, cx);
18497 }
18498
18499 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18500 if let Some(show_line_numbers) = self.show_line_numbers {
18501 return show_line_numbers;
18502 }
18503 EditorSettings::get_global(cx).gutter.line_numbers
18504 }
18505
18506 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18507 self.use_relative_line_numbers
18508 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18509 }
18510
18511 pub fn toggle_relative_line_numbers(
18512 &mut self,
18513 _: &ToggleRelativeLineNumbers,
18514 _: &mut Window,
18515 cx: &mut Context<Self>,
18516 ) {
18517 let is_relative = self.should_use_relative_line_numbers(cx);
18518 self.set_relative_line_number(Some(!is_relative), cx)
18519 }
18520
18521 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18522 self.use_relative_line_numbers = is_relative;
18523 cx.notify();
18524 }
18525
18526 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18527 self.show_gutter = show_gutter;
18528 cx.notify();
18529 }
18530
18531 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18532 self.show_scrollbars = ScrollbarAxes {
18533 horizontal: show,
18534 vertical: show,
18535 };
18536 cx.notify();
18537 }
18538
18539 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18540 self.show_scrollbars.vertical = show;
18541 cx.notify();
18542 }
18543
18544 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18545 self.show_scrollbars.horizontal = show;
18546 cx.notify();
18547 }
18548
18549 pub fn set_minimap_visibility(
18550 &mut self,
18551 minimap_visibility: MinimapVisibility,
18552 window: &mut Window,
18553 cx: &mut Context<Self>,
18554 ) {
18555 if self.minimap_visibility != minimap_visibility {
18556 if minimap_visibility.visible() && self.minimap.is_none() {
18557 let minimap_settings = EditorSettings::get_global(cx).minimap;
18558 self.minimap =
18559 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18560 }
18561 self.minimap_visibility = minimap_visibility;
18562 cx.notify();
18563 }
18564 }
18565
18566 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18567 self.set_show_scrollbars(false, cx);
18568 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18569 }
18570
18571 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18572 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18573 }
18574
18575 /// Normally the text in full mode and auto height editors is padded on the
18576 /// left side by roughly half a character width for improved hit testing.
18577 ///
18578 /// Use this method to disable this for cases where this is not wanted (e.g.
18579 /// if you want to align the editor text with some other text above or below)
18580 /// or if you want to add this padding to single-line editors.
18581 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18582 self.offset_content = offset_content;
18583 cx.notify();
18584 }
18585
18586 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18587 self.show_line_numbers = Some(show_line_numbers);
18588 cx.notify();
18589 }
18590
18591 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18592 self.disable_expand_excerpt_buttons = true;
18593 cx.notify();
18594 }
18595
18596 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18597 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18598 cx.notify();
18599 }
18600
18601 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18602 self.show_code_actions = Some(show_code_actions);
18603 cx.notify();
18604 }
18605
18606 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18607 self.show_runnables = Some(show_runnables);
18608 cx.notify();
18609 }
18610
18611 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18612 self.show_breakpoints = Some(show_breakpoints);
18613 cx.notify();
18614 }
18615
18616 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18617 if self.display_map.read(cx).masked != masked {
18618 self.display_map.update(cx, |map, _| map.masked = masked);
18619 }
18620 cx.notify()
18621 }
18622
18623 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18624 self.show_wrap_guides = Some(show_wrap_guides);
18625 cx.notify();
18626 }
18627
18628 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18629 self.show_indent_guides = Some(show_indent_guides);
18630 cx.notify();
18631 }
18632
18633 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18634 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18635 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
18636 && let Some(dir) = file.abs_path(cx).parent()
18637 {
18638 return Some(dir.to_owned());
18639 }
18640
18641 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18642 return Some(project_path.path.to_path_buf());
18643 }
18644 }
18645
18646 None
18647 }
18648
18649 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18650 self.active_excerpt(cx)?
18651 .1
18652 .read(cx)
18653 .file()
18654 .and_then(|f| f.as_local())
18655 }
18656
18657 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18658 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18659 let buffer = buffer.read(cx);
18660 if let Some(project_path) = buffer.project_path(cx) {
18661 let project = self.project()?.read(cx);
18662 project.absolute_path(&project_path, cx)
18663 } else {
18664 buffer
18665 .file()
18666 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18667 }
18668 })
18669 }
18670
18671 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18672 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18673 let project_path = buffer.read(cx).project_path(cx)?;
18674 let project = self.project()?.read(cx);
18675 let entry = project.entry_for_path(&project_path, cx)?;
18676 let path = entry.path.to_path_buf();
18677 Some(path)
18678 })
18679 }
18680
18681 pub fn reveal_in_finder(
18682 &mut self,
18683 _: &RevealInFileManager,
18684 _window: &mut Window,
18685 cx: &mut Context<Self>,
18686 ) {
18687 if let Some(target) = self.target_file(cx) {
18688 cx.reveal_path(&target.abs_path(cx));
18689 }
18690 }
18691
18692 pub fn copy_path(
18693 &mut self,
18694 _: &zed_actions::workspace::CopyPath,
18695 _window: &mut Window,
18696 cx: &mut Context<Self>,
18697 ) {
18698 if let Some(path) = self.target_file_abs_path(cx)
18699 && let Some(path) = path.to_str()
18700 {
18701 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18702 }
18703 }
18704
18705 pub fn copy_relative_path(
18706 &mut self,
18707 _: &zed_actions::workspace::CopyRelativePath,
18708 _window: &mut Window,
18709 cx: &mut Context<Self>,
18710 ) {
18711 if let Some(path) = self.target_file_path(cx)
18712 && let Some(path) = path.to_str()
18713 {
18714 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18715 }
18716 }
18717
18718 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18719 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18720 buffer.read(cx).project_path(cx)
18721 } else {
18722 None
18723 }
18724 }
18725
18726 // Returns true if the editor handled a go-to-line request
18727 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18728 maybe!({
18729 let breakpoint_store = self.breakpoint_store.as_ref()?;
18730
18731 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18732 else {
18733 self.clear_row_highlights::<ActiveDebugLine>();
18734 return None;
18735 };
18736
18737 let position = active_stack_frame.position;
18738 let buffer_id = position.buffer_id?;
18739 let snapshot = self
18740 .project
18741 .as_ref()?
18742 .read(cx)
18743 .buffer_for_id(buffer_id, cx)?
18744 .read(cx)
18745 .snapshot();
18746
18747 let mut handled = false;
18748 for (id, ExcerptRange { context, .. }) in
18749 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18750 {
18751 if context.start.cmp(&position, &snapshot).is_ge()
18752 || context.end.cmp(&position, &snapshot).is_lt()
18753 {
18754 continue;
18755 }
18756 let snapshot = self.buffer.read(cx).snapshot(cx);
18757 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18758
18759 handled = true;
18760 self.clear_row_highlights::<ActiveDebugLine>();
18761
18762 self.go_to_line::<ActiveDebugLine>(
18763 multibuffer_anchor,
18764 Some(cx.theme().colors().editor_debugger_active_line_background),
18765 window,
18766 cx,
18767 );
18768
18769 cx.notify();
18770 }
18771
18772 handled.then_some(())
18773 })
18774 .is_some()
18775 }
18776
18777 pub fn copy_file_name_without_extension(
18778 &mut self,
18779 _: &CopyFileNameWithoutExtension,
18780 _: &mut Window,
18781 cx: &mut Context<Self>,
18782 ) {
18783 if let Some(file) = self.target_file(cx)
18784 && let Some(file_stem) = file.path().file_stem()
18785 && let Some(name) = file_stem.to_str()
18786 {
18787 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18788 }
18789 }
18790
18791 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18792 if let Some(file) = self.target_file(cx)
18793 && let Some(file_name) = file.path().file_name()
18794 && let Some(name) = file_name.to_str()
18795 {
18796 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18797 }
18798 }
18799
18800 pub fn toggle_git_blame(
18801 &mut self,
18802 _: &::git::Blame,
18803 window: &mut Window,
18804 cx: &mut Context<Self>,
18805 ) {
18806 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18807
18808 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18809 self.start_git_blame(true, window, cx);
18810 }
18811
18812 cx.notify();
18813 }
18814
18815 pub fn toggle_git_blame_inline(
18816 &mut self,
18817 _: &ToggleGitBlameInline,
18818 window: &mut Window,
18819 cx: &mut Context<Self>,
18820 ) {
18821 self.toggle_git_blame_inline_internal(true, window, cx);
18822 cx.notify();
18823 }
18824
18825 pub fn open_git_blame_commit(
18826 &mut self,
18827 _: &OpenGitBlameCommit,
18828 window: &mut Window,
18829 cx: &mut Context<Self>,
18830 ) {
18831 self.open_git_blame_commit_internal(window, cx);
18832 }
18833
18834 fn open_git_blame_commit_internal(
18835 &mut self,
18836 window: &mut Window,
18837 cx: &mut Context<Self>,
18838 ) -> Option<()> {
18839 let blame = self.blame.as_ref()?;
18840 let snapshot = self.snapshot(window, cx);
18841 let cursor = self.selections.newest::<Point>(cx).head();
18842 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18843 let blame_entry = blame
18844 .update(cx, |blame, cx| {
18845 blame
18846 .blame_for_rows(
18847 &[RowInfo {
18848 buffer_id: Some(buffer.remote_id()),
18849 buffer_row: Some(point.row),
18850 ..Default::default()
18851 }],
18852 cx,
18853 )
18854 .next()
18855 })
18856 .flatten()?;
18857 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18858 let repo = blame.read(cx).repository(cx)?;
18859 let workspace = self.workspace()?.downgrade();
18860 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18861 None
18862 }
18863
18864 pub fn git_blame_inline_enabled(&self) -> bool {
18865 self.git_blame_inline_enabled
18866 }
18867
18868 pub fn toggle_selection_menu(
18869 &mut self,
18870 _: &ToggleSelectionMenu,
18871 _: &mut Window,
18872 cx: &mut Context<Self>,
18873 ) {
18874 self.show_selection_menu = self
18875 .show_selection_menu
18876 .map(|show_selections_menu| !show_selections_menu)
18877 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18878
18879 cx.notify();
18880 }
18881
18882 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18883 self.show_selection_menu
18884 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18885 }
18886
18887 fn start_git_blame(
18888 &mut self,
18889 user_triggered: bool,
18890 window: &mut Window,
18891 cx: &mut Context<Self>,
18892 ) {
18893 if let Some(project) = self.project() {
18894 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18895 return;
18896 };
18897
18898 if buffer.read(cx).file().is_none() {
18899 return;
18900 }
18901
18902 let focused = self.focus_handle(cx).contains_focused(window, cx);
18903
18904 let project = project.clone();
18905 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18906 self.blame_subscription =
18907 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18908 self.blame = Some(blame);
18909 }
18910 }
18911
18912 fn toggle_git_blame_inline_internal(
18913 &mut self,
18914 user_triggered: bool,
18915 window: &mut Window,
18916 cx: &mut Context<Self>,
18917 ) {
18918 if self.git_blame_inline_enabled {
18919 self.git_blame_inline_enabled = false;
18920 self.show_git_blame_inline = false;
18921 self.show_git_blame_inline_delay_task.take();
18922 } else {
18923 self.git_blame_inline_enabled = true;
18924 self.start_git_blame_inline(user_triggered, window, cx);
18925 }
18926
18927 cx.notify();
18928 }
18929
18930 fn start_git_blame_inline(
18931 &mut self,
18932 user_triggered: bool,
18933 window: &mut Window,
18934 cx: &mut Context<Self>,
18935 ) {
18936 self.start_git_blame(user_triggered, window, cx);
18937
18938 if ProjectSettings::get_global(cx)
18939 .git
18940 .inline_blame_delay()
18941 .is_some()
18942 {
18943 self.start_inline_blame_timer(window, cx);
18944 } else {
18945 self.show_git_blame_inline = true
18946 }
18947 }
18948
18949 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18950 self.blame.as_ref()
18951 }
18952
18953 pub fn show_git_blame_gutter(&self) -> bool {
18954 self.show_git_blame_gutter
18955 }
18956
18957 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18958 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18959 }
18960
18961 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18962 self.show_git_blame_inline
18963 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18964 && !self.newest_selection_head_on_empty_line(cx)
18965 && self.has_blame_entries(cx)
18966 }
18967
18968 fn has_blame_entries(&self, cx: &App) -> bool {
18969 self.blame()
18970 .is_some_and(|blame| blame.read(cx).has_generated_entries())
18971 }
18972
18973 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18974 let cursor_anchor = self.selections.newest_anchor().head();
18975
18976 let snapshot = self.buffer.read(cx).snapshot(cx);
18977 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18978
18979 snapshot.line_len(buffer_row) == 0
18980 }
18981
18982 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18983 let buffer_and_selection = maybe!({
18984 let selection = self.selections.newest::<Point>(cx);
18985 let selection_range = selection.range();
18986
18987 let multi_buffer = self.buffer().read(cx);
18988 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18989 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18990
18991 let (buffer, range, _) = if selection.reversed {
18992 buffer_ranges.first()
18993 } else {
18994 buffer_ranges.last()
18995 }?;
18996
18997 let selection = text::ToPoint::to_point(&range.start, buffer).row
18998 ..text::ToPoint::to_point(&range.end, buffer).row;
18999 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19000 });
19001
19002 let Some((buffer, selection)) = buffer_and_selection else {
19003 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19004 };
19005
19006 let Some(project) = self.project() else {
19007 return Task::ready(Err(anyhow!("editor does not have project")));
19008 };
19009
19010 project.update(cx, |project, cx| {
19011 project.get_permalink_to_line(&buffer, selection, cx)
19012 })
19013 }
19014
19015 pub fn copy_permalink_to_line(
19016 &mut self,
19017 _: &CopyPermalinkToLine,
19018 window: &mut Window,
19019 cx: &mut Context<Self>,
19020 ) {
19021 let permalink_task = self.get_permalink_to_line(cx);
19022 let workspace = self.workspace();
19023
19024 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19025 Ok(permalink) => {
19026 cx.update(|_, cx| {
19027 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19028 })
19029 .ok();
19030 }
19031 Err(err) => {
19032 let message = format!("Failed to copy permalink: {err}");
19033
19034 anyhow::Result::<()>::Err(err).log_err();
19035
19036 if let Some(workspace) = workspace {
19037 workspace
19038 .update_in(cx, |workspace, _, cx| {
19039 struct CopyPermalinkToLine;
19040
19041 workspace.show_toast(
19042 Toast::new(
19043 NotificationId::unique::<CopyPermalinkToLine>(),
19044 message,
19045 ),
19046 cx,
19047 )
19048 })
19049 .ok();
19050 }
19051 }
19052 })
19053 .detach();
19054 }
19055
19056 pub fn copy_file_location(
19057 &mut self,
19058 _: &CopyFileLocation,
19059 _: &mut Window,
19060 cx: &mut Context<Self>,
19061 ) {
19062 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19063 if let Some(file) = self.target_file(cx)
19064 && let Some(path) = file.path().to_str()
19065 {
19066 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19067 }
19068 }
19069
19070 pub fn open_permalink_to_line(
19071 &mut self,
19072 _: &OpenPermalinkToLine,
19073 window: &mut Window,
19074 cx: &mut Context<Self>,
19075 ) {
19076 let permalink_task = self.get_permalink_to_line(cx);
19077 let workspace = self.workspace();
19078
19079 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19080 Ok(permalink) => {
19081 cx.update(|_, cx| {
19082 cx.open_url(permalink.as_ref());
19083 })
19084 .ok();
19085 }
19086 Err(err) => {
19087 let message = format!("Failed to open permalink: {err}");
19088
19089 anyhow::Result::<()>::Err(err).log_err();
19090
19091 if let Some(workspace) = workspace {
19092 workspace
19093 .update(cx, |workspace, cx| {
19094 struct OpenPermalinkToLine;
19095
19096 workspace.show_toast(
19097 Toast::new(
19098 NotificationId::unique::<OpenPermalinkToLine>(),
19099 message,
19100 ),
19101 cx,
19102 )
19103 })
19104 .ok();
19105 }
19106 }
19107 })
19108 .detach();
19109 }
19110
19111 pub fn insert_uuid_v4(
19112 &mut self,
19113 _: &InsertUuidV4,
19114 window: &mut Window,
19115 cx: &mut Context<Self>,
19116 ) {
19117 self.insert_uuid(UuidVersion::V4, window, cx);
19118 }
19119
19120 pub fn insert_uuid_v7(
19121 &mut self,
19122 _: &InsertUuidV7,
19123 window: &mut Window,
19124 cx: &mut Context<Self>,
19125 ) {
19126 self.insert_uuid(UuidVersion::V7, window, cx);
19127 }
19128
19129 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19130 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19131 self.transact(window, cx, |this, window, cx| {
19132 let edits = this
19133 .selections
19134 .all::<Point>(cx)
19135 .into_iter()
19136 .map(|selection| {
19137 let uuid = match version {
19138 UuidVersion::V4 => uuid::Uuid::new_v4(),
19139 UuidVersion::V7 => uuid::Uuid::now_v7(),
19140 };
19141
19142 (selection.range(), uuid.to_string())
19143 });
19144 this.edit(edits, cx);
19145 this.refresh_edit_prediction(true, false, window, cx);
19146 });
19147 }
19148
19149 pub fn open_selections_in_multibuffer(
19150 &mut self,
19151 _: &OpenSelectionsInMultibuffer,
19152 window: &mut Window,
19153 cx: &mut Context<Self>,
19154 ) {
19155 let multibuffer = self.buffer.read(cx);
19156
19157 let Some(buffer) = multibuffer.as_singleton() else {
19158 return;
19159 };
19160
19161 let Some(workspace) = self.workspace() else {
19162 return;
19163 };
19164
19165 let title = multibuffer.title(cx).to_string();
19166
19167 let locations = self
19168 .selections
19169 .all_anchors(cx)
19170 .iter()
19171 .map(|selection| Location {
19172 buffer: buffer.clone(),
19173 range: selection.start.text_anchor..selection.end.text_anchor,
19174 })
19175 .collect::<Vec<_>>();
19176
19177 cx.spawn_in(window, async move |_, cx| {
19178 workspace.update_in(cx, |workspace, window, cx| {
19179 Self::open_locations_in_multibuffer(
19180 workspace,
19181 locations,
19182 format!("Selections for '{title}'"),
19183 false,
19184 MultibufferSelectionMode::All,
19185 window,
19186 cx,
19187 );
19188 })
19189 })
19190 .detach();
19191 }
19192
19193 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19194 /// last highlight added will be used.
19195 ///
19196 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19197 pub fn highlight_rows<T: 'static>(
19198 &mut self,
19199 range: Range<Anchor>,
19200 color: Hsla,
19201 options: RowHighlightOptions,
19202 cx: &mut Context<Self>,
19203 ) {
19204 let snapshot = self.buffer().read(cx).snapshot(cx);
19205 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19206 let ix = row_highlights.binary_search_by(|highlight| {
19207 Ordering::Equal
19208 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19209 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19210 });
19211
19212 if let Err(mut ix) = ix {
19213 let index = post_inc(&mut self.highlight_order);
19214
19215 // If this range intersects with the preceding highlight, then merge it with
19216 // the preceding highlight. Otherwise insert a new highlight.
19217 let mut merged = false;
19218 if ix > 0 {
19219 let prev_highlight = &mut row_highlights[ix - 1];
19220 if prev_highlight
19221 .range
19222 .end
19223 .cmp(&range.start, &snapshot)
19224 .is_ge()
19225 {
19226 ix -= 1;
19227 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19228 prev_highlight.range.end = range.end;
19229 }
19230 merged = true;
19231 prev_highlight.index = index;
19232 prev_highlight.color = color;
19233 prev_highlight.options = options;
19234 }
19235 }
19236
19237 if !merged {
19238 row_highlights.insert(
19239 ix,
19240 RowHighlight {
19241 range,
19242 index,
19243 color,
19244 options,
19245 type_id: TypeId::of::<T>(),
19246 },
19247 );
19248 }
19249
19250 // If any of the following highlights intersect with this one, merge them.
19251 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19252 let highlight = &row_highlights[ix];
19253 if next_highlight
19254 .range
19255 .start
19256 .cmp(&highlight.range.end, &snapshot)
19257 .is_le()
19258 {
19259 if next_highlight
19260 .range
19261 .end
19262 .cmp(&highlight.range.end, &snapshot)
19263 .is_gt()
19264 {
19265 row_highlights[ix].range.end = next_highlight.range.end;
19266 }
19267 row_highlights.remove(ix + 1);
19268 } else {
19269 break;
19270 }
19271 }
19272 }
19273 }
19274
19275 /// Remove any highlighted row ranges of the given type that intersect the
19276 /// given ranges.
19277 pub fn remove_highlighted_rows<T: 'static>(
19278 &mut self,
19279 ranges_to_remove: Vec<Range<Anchor>>,
19280 cx: &mut Context<Self>,
19281 ) {
19282 let snapshot = self.buffer().read(cx).snapshot(cx);
19283 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19284 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19285 row_highlights.retain(|highlight| {
19286 while let Some(range_to_remove) = ranges_to_remove.peek() {
19287 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19288 Ordering::Less | Ordering::Equal => {
19289 ranges_to_remove.next();
19290 }
19291 Ordering::Greater => {
19292 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19293 Ordering::Less | Ordering::Equal => {
19294 return false;
19295 }
19296 Ordering::Greater => break,
19297 }
19298 }
19299 }
19300 }
19301
19302 true
19303 })
19304 }
19305
19306 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19307 pub fn clear_row_highlights<T: 'static>(&mut self) {
19308 self.highlighted_rows.remove(&TypeId::of::<T>());
19309 }
19310
19311 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19312 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19313 self.highlighted_rows
19314 .get(&TypeId::of::<T>())
19315 .map_or(&[] as &[_], |vec| vec.as_slice())
19316 .iter()
19317 .map(|highlight| (highlight.range.clone(), highlight.color))
19318 }
19319
19320 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19321 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19322 /// Allows to ignore certain kinds of highlights.
19323 pub fn highlighted_display_rows(
19324 &self,
19325 window: &mut Window,
19326 cx: &mut App,
19327 ) -> BTreeMap<DisplayRow, LineHighlight> {
19328 let snapshot = self.snapshot(window, cx);
19329 let mut used_highlight_orders = HashMap::default();
19330 self.highlighted_rows
19331 .iter()
19332 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19333 .fold(
19334 BTreeMap::<DisplayRow, LineHighlight>::new(),
19335 |mut unique_rows, highlight| {
19336 let start = highlight.range.start.to_display_point(&snapshot);
19337 let end = highlight.range.end.to_display_point(&snapshot);
19338 let start_row = start.row().0;
19339 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19340 && end.column() == 0
19341 {
19342 end.row().0.saturating_sub(1)
19343 } else {
19344 end.row().0
19345 };
19346 for row in start_row..=end_row {
19347 let used_index =
19348 used_highlight_orders.entry(row).or_insert(highlight.index);
19349 if highlight.index >= *used_index {
19350 *used_index = highlight.index;
19351 unique_rows.insert(
19352 DisplayRow(row),
19353 LineHighlight {
19354 include_gutter: highlight.options.include_gutter,
19355 border: None,
19356 background: highlight.color.into(),
19357 type_id: Some(highlight.type_id),
19358 },
19359 );
19360 }
19361 }
19362 unique_rows
19363 },
19364 )
19365 }
19366
19367 pub fn highlighted_display_row_for_autoscroll(
19368 &self,
19369 snapshot: &DisplaySnapshot,
19370 ) -> Option<DisplayRow> {
19371 self.highlighted_rows
19372 .values()
19373 .flat_map(|highlighted_rows| highlighted_rows.iter())
19374 .filter_map(|highlight| {
19375 if highlight.options.autoscroll {
19376 Some(highlight.range.start.to_display_point(snapshot).row())
19377 } else {
19378 None
19379 }
19380 })
19381 .min()
19382 }
19383
19384 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19385 self.highlight_background::<SearchWithinRange>(
19386 ranges,
19387 |colors| colors.colors().editor_document_highlight_read_background,
19388 cx,
19389 )
19390 }
19391
19392 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19393 self.breadcrumb_header = Some(new_header);
19394 }
19395
19396 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19397 self.clear_background_highlights::<SearchWithinRange>(cx);
19398 }
19399
19400 pub fn highlight_background<T: 'static>(
19401 &mut self,
19402 ranges: &[Range<Anchor>],
19403 color_fetcher: fn(&Theme) -> Hsla,
19404 cx: &mut Context<Self>,
19405 ) {
19406 self.background_highlights.insert(
19407 HighlightKey::Type(TypeId::of::<T>()),
19408 (color_fetcher, Arc::from(ranges)),
19409 );
19410 self.scrollbar_marker_state.dirty = true;
19411 cx.notify();
19412 }
19413
19414 pub fn highlight_background_key<T: 'static>(
19415 &mut self,
19416 key: usize,
19417 ranges: &[Range<Anchor>],
19418 color_fetcher: fn(&Theme) -> Hsla,
19419 cx: &mut Context<Self>,
19420 ) {
19421 self.background_highlights.insert(
19422 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19423 (color_fetcher, Arc::from(ranges)),
19424 );
19425 self.scrollbar_marker_state.dirty = true;
19426 cx.notify();
19427 }
19428
19429 pub fn clear_background_highlights<T: 'static>(
19430 &mut self,
19431 cx: &mut Context<Self>,
19432 ) -> Option<BackgroundHighlight> {
19433 let text_highlights = self
19434 .background_highlights
19435 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19436 if !text_highlights.1.is_empty() {
19437 self.scrollbar_marker_state.dirty = true;
19438 cx.notify();
19439 }
19440 Some(text_highlights)
19441 }
19442
19443 pub fn highlight_gutter<T: 'static>(
19444 &mut self,
19445 ranges: impl Into<Vec<Range<Anchor>>>,
19446 color_fetcher: fn(&App) -> Hsla,
19447 cx: &mut Context<Self>,
19448 ) {
19449 self.gutter_highlights
19450 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19451 cx.notify();
19452 }
19453
19454 pub fn clear_gutter_highlights<T: 'static>(
19455 &mut self,
19456 cx: &mut Context<Self>,
19457 ) -> Option<GutterHighlight> {
19458 cx.notify();
19459 self.gutter_highlights.remove(&TypeId::of::<T>())
19460 }
19461
19462 pub fn insert_gutter_highlight<T: 'static>(
19463 &mut self,
19464 range: Range<Anchor>,
19465 color_fetcher: fn(&App) -> Hsla,
19466 cx: &mut Context<Self>,
19467 ) {
19468 let snapshot = self.buffer().read(cx).snapshot(cx);
19469 let mut highlights = self
19470 .gutter_highlights
19471 .remove(&TypeId::of::<T>())
19472 .map(|(_, highlights)| highlights)
19473 .unwrap_or_default();
19474 let ix = highlights.binary_search_by(|highlight| {
19475 Ordering::Equal
19476 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19477 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19478 });
19479 if let Err(ix) = ix {
19480 highlights.insert(ix, range);
19481 }
19482 self.gutter_highlights
19483 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19484 }
19485
19486 pub fn remove_gutter_highlights<T: 'static>(
19487 &mut self,
19488 ranges_to_remove: Vec<Range<Anchor>>,
19489 cx: &mut Context<Self>,
19490 ) {
19491 let snapshot = self.buffer().read(cx).snapshot(cx);
19492 let Some((color_fetcher, mut gutter_highlights)) =
19493 self.gutter_highlights.remove(&TypeId::of::<T>())
19494 else {
19495 return;
19496 };
19497 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19498 gutter_highlights.retain(|highlight| {
19499 while let Some(range_to_remove) = ranges_to_remove.peek() {
19500 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19501 Ordering::Less | Ordering::Equal => {
19502 ranges_to_remove.next();
19503 }
19504 Ordering::Greater => {
19505 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19506 Ordering::Less | Ordering::Equal => {
19507 return false;
19508 }
19509 Ordering::Greater => break,
19510 }
19511 }
19512 }
19513 }
19514
19515 true
19516 });
19517 self.gutter_highlights
19518 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19519 }
19520
19521 #[cfg(feature = "test-support")]
19522 pub fn all_text_highlights(
19523 &self,
19524 window: &mut Window,
19525 cx: &mut Context<Self>,
19526 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19527 let snapshot = self.snapshot(window, cx);
19528 self.display_map.update(cx, |display_map, _| {
19529 display_map
19530 .all_text_highlights()
19531 .map(|highlight| {
19532 let (style, ranges) = highlight.as_ref();
19533 (
19534 *style,
19535 ranges
19536 .iter()
19537 .map(|range| range.clone().to_display_points(&snapshot))
19538 .collect(),
19539 )
19540 })
19541 .collect()
19542 })
19543 }
19544
19545 #[cfg(feature = "test-support")]
19546 pub fn all_text_background_highlights(
19547 &self,
19548 window: &mut Window,
19549 cx: &mut Context<Self>,
19550 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19551 let snapshot = self.snapshot(window, cx);
19552 let buffer = &snapshot.buffer_snapshot;
19553 let start = buffer.anchor_before(0);
19554 let end = buffer.anchor_after(buffer.len());
19555 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19556 }
19557
19558 #[cfg(feature = "test-support")]
19559 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19560 let snapshot = self.buffer().read(cx).snapshot(cx);
19561
19562 let highlights = self
19563 .background_highlights
19564 .get(&HighlightKey::Type(TypeId::of::<
19565 items::BufferSearchHighlights,
19566 >()));
19567
19568 if let Some((_color, ranges)) = highlights {
19569 ranges
19570 .iter()
19571 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19572 .collect_vec()
19573 } else {
19574 vec![]
19575 }
19576 }
19577
19578 fn document_highlights_for_position<'a>(
19579 &'a self,
19580 position: Anchor,
19581 buffer: &'a MultiBufferSnapshot,
19582 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19583 let read_highlights = self
19584 .background_highlights
19585 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19586 .map(|h| &h.1);
19587 let write_highlights = self
19588 .background_highlights
19589 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19590 .map(|h| &h.1);
19591 let left_position = position.bias_left(buffer);
19592 let right_position = position.bias_right(buffer);
19593 read_highlights
19594 .into_iter()
19595 .chain(write_highlights)
19596 .flat_map(move |ranges| {
19597 let start_ix = match ranges.binary_search_by(|probe| {
19598 let cmp = probe.end.cmp(&left_position, buffer);
19599 if cmp.is_ge() {
19600 Ordering::Greater
19601 } else {
19602 Ordering::Less
19603 }
19604 }) {
19605 Ok(i) | Err(i) => i,
19606 };
19607
19608 ranges[start_ix..]
19609 .iter()
19610 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19611 })
19612 }
19613
19614 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19615 self.background_highlights
19616 .get(&HighlightKey::Type(TypeId::of::<T>()))
19617 .is_some_and(|(_, highlights)| !highlights.is_empty())
19618 }
19619
19620 pub fn background_highlights_in_range(
19621 &self,
19622 search_range: Range<Anchor>,
19623 display_snapshot: &DisplaySnapshot,
19624 theme: &Theme,
19625 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19626 let mut results = Vec::new();
19627 for (color_fetcher, ranges) in self.background_highlights.values() {
19628 let color = color_fetcher(theme);
19629 let start_ix = match ranges.binary_search_by(|probe| {
19630 let cmp = probe
19631 .end
19632 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19633 if cmp.is_gt() {
19634 Ordering::Greater
19635 } else {
19636 Ordering::Less
19637 }
19638 }) {
19639 Ok(i) | Err(i) => i,
19640 };
19641 for range in &ranges[start_ix..] {
19642 if range
19643 .start
19644 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19645 .is_ge()
19646 {
19647 break;
19648 }
19649
19650 let start = range.start.to_display_point(display_snapshot);
19651 let end = range.end.to_display_point(display_snapshot);
19652 results.push((start..end, color))
19653 }
19654 }
19655 results
19656 }
19657
19658 pub fn background_highlight_row_ranges<T: 'static>(
19659 &self,
19660 search_range: Range<Anchor>,
19661 display_snapshot: &DisplaySnapshot,
19662 count: usize,
19663 ) -> Vec<RangeInclusive<DisplayPoint>> {
19664 let mut results = Vec::new();
19665 let Some((_, ranges)) = self
19666 .background_highlights
19667 .get(&HighlightKey::Type(TypeId::of::<T>()))
19668 else {
19669 return vec![];
19670 };
19671
19672 let start_ix = match ranges.binary_search_by(|probe| {
19673 let cmp = probe
19674 .end
19675 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19676 if cmp.is_gt() {
19677 Ordering::Greater
19678 } else {
19679 Ordering::Less
19680 }
19681 }) {
19682 Ok(i) | Err(i) => i,
19683 };
19684 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19685 if let (Some(start_display), Some(end_display)) = (start, end) {
19686 results.push(
19687 start_display.to_display_point(display_snapshot)
19688 ..=end_display.to_display_point(display_snapshot),
19689 );
19690 }
19691 };
19692 let mut start_row: Option<Point> = None;
19693 let mut end_row: Option<Point> = None;
19694 if ranges.len() > count {
19695 return Vec::new();
19696 }
19697 for range in &ranges[start_ix..] {
19698 if range
19699 .start
19700 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19701 .is_ge()
19702 {
19703 break;
19704 }
19705 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19706 if let Some(current_row) = &end_row
19707 && end.row == current_row.row
19708 {
19709 continue;
19710 }
19711 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19712 if start_row.is_none() {
19713 assert_eq!(end_row, None);
19714 start_row = Some(start);
19715 end_row = Some(end);
19716 continue;
19717 }
19718 if let Some(current_end) = end_row.as_mut() {
19719 if start.row > current_end.row + 1 {
19720 push_region(start_row, end_row);
19721 start_row = Some(start);
19722 end_row = Some(end);
19723 } else {
19724 // Merge two hunks.
19725 *current_end = end;
19726 }
19727 } else {
19728 unreachable!();
19729 }
19730 }
19731 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19732 push_region(start_row, end_row);
19733 results
19734 }
19735
19736 pub fn gutter_highlights_in_range(
19737 &self,
19738 search_range: Range<Anchor>,
19739 display_snapshot: &DisplaySnapshot,
19740 cx: &App,
19741 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19742 let mut results = Vec::new();
19743 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19744 let color = color_fetcher(cx);
19745 let start_ix = match ranges.binary_search_by(|probe| {
19746 let cmp = probe
19747 .end
19748 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19749 if cmp.is_gt() {
19750 Ordering::Greater
19751 } else {
19752 Ordering::Less
19753 }
19754 }) {
19755 Ok(i) | Err(i) => i,
19756 };
19757 for range in &ranges[start_ix..] {
19758 if range
19759 .start
19760 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19761 .is_ge()
19762 {
19763 break;
19764 }
19765
19766 let start = range.start.to_display_point(display_snapshot);
19767 let end = range.end.to_display_point(display_snapshot);
19768 results.push((start..end, color))
19769 }
19770 }
19771 results
19772 }
19773
19774 /// Get the text ranges corresponding to the redaction query
19775 pub fn redacted_ranges(
19776 &self,
19777 search_range: Range<Anchor>,
19778 display_snapshot: &DisplaySnapshot,
19779 cx: &App,
19780 ) -> Vec<Range<DisplayPoint>> {
19781 display_snapshot
19782 .buffer_snapshot
19783 .redacted_ranges(search_range, |file| {
19784 if let Some(file) = file {
19785 file.is_private()
19786 && EditorSettings::get(
19787 Some(SettingsLocation {
19788 worktree_id: file.worktree_id(cx),
19789 path: file.path().as_ref(),
19790 }),
19791 cx,
19792 )
19793 .redact_private_values
19794 } else {
19795 false
19796 }
19797 })
19798 .map(|range| {
19799 range.start.to_display_point(display_snapshot)
19800 ..range.end.to_display_point(display_snapshot)
19801 })
19802 .collect()
19803 }
19804
19805 pub fn highlight_text_key<T: 'static>(
19806 &mut self,
19807 key: usize,
19808 ranges: Vec<Range<Anchor>>,
19809 style: HighlightStyle,
19810 cx: &mut Context<Self>,
19811 ) {
19812 self.display_map.update(cx, |map, _| {
19813 map.highlight_text(
19814 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19815 ranges,
19816 style,
19817 );
19818 });
19819 cx.notify();
19820 }
19821
19822 pub fn highlight_text<T: 'static>(
19823 &mut self,
19824 ranges: Vec<Range<Anchor>>,
19825 style: HighlightStyle,
19826 cx: &mut Context<Self>,
19827 ) {
19828 self.display_map.update(cx, |map, _| {
19829 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19830 });
19831 cx.notify();
19832 }
19833
19834 pub(crate) fn highlight_inlays<T: 'static>(
19835 &mut self,
19836 highlights: Vec<InlayHighlight>,
19837 style: HighlightStyle,
19838 cx: &mut Context<Self>,
19839 ) {
19840 self.display_map.update(cx, |map, _| {
19841 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19842 });
19843 cx.notify();
19844 }
19845
19846 pub fn text_highlights<'a, T: 'static>(
19847 &'a self,
19848 cx: &'a App,
19849 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19850 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19851 }
19852
19853 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19854 let cleared = self
19855 .display_map
19856 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19857 if cleared {
19858 cx.notify();
19859 }
19860 }
19861
19862 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19863 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19864 && self.focus_handle.is_focused(window)
19865 }
19866
19867 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19868 self.show_cursor_when_unfocused = is_enabled;
19869 cx.notify();
19870 }
19871
19872 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19873 cx.notify();
19874 }
19875
19876 fn on_debug_session_event(
19877 &mut self,
19878 _session: Entity<Session>,
19879 event: &SessionEvent,
19880 cx: &mut Context<Self>,
19881 ) {
19882 if let SessionEvent::InvalidateInlineValue = event {
19883 self.refresh_inline_values(cx);
19884 }
19885 }
19886
19887 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19888 let Some(project) = self.project.clone() else {
19889 return;
19890 };
19891
19892 if !self.inline_value_cache.enabled {
19893 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19894 self.splice_inlays(&inlays, Vec::new(), cx);
19895 return;
19896 }
19897
19898 let current_execution_position = self
19899 .highlighted_rows
19900 .get(&TypeId::of::<ActiveDebugLine>())
19901 .and_then(|lines| lines.last().map(|line| line.range.end));
19902
19903 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19904 let inline_values = editor
19905 .update(cx, |editor, cx| {
19906 let Some(current_execution_position) = current_execution_position else {
19907 return Some(Task::ready(Ok(Vec::new())));
19908 };
19909
19910 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19911 let snapshot = buffer.snapshot(cx);
19912
19913 let excerpt = snapshot.excerpt_containing(
19914 current_execution_position..current_execution_position,
19915 )?;
19916
19917 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19918 })?;
19919
19920 let range =
19921 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19922
19923 project.inline_values(buffer, range, cx)
19924 })
19925 .ok()
19926 .flatten()?
19927 .await
19928 .context("refreshing debugger inlays")
19929 .log_err()?;
19930
19931 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19932
19933 for (buffer_id, inline_value) in inline_values
19934 .into_iter()
19935 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19936 {
19937 buffer_inline_values
19938 .entry(buffer_id)
19939 .or_default()
19940 .push(inline_value);
19941 }
19942
19943 editor
19944 .update(cx, |editor, cx| {
19945 let snapshot = editor.buffer.read(cx).snapshot(cx);
19946 let mut new_inlays = Vec::default();
19947
19948 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19949 let buffer_id = buffer_snapshot.remote_id();
19950 buffer_inline_values
19951 .get(&buffer_id)
19952 .into_iter()
19953 .flatten()
19954 .for_each(|hint| {
19955 let inlay = Inlay::debugger(
19956 post_inc(&mut editor.next_inlay_id),
19957 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19958 hint.text(),
19959 );
19960 if !inlay.text.chars().contains(&'\n') {
19961 new_inlays.push(inlay);
19962 }
19963 });
19964 }
19965
19966 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19967 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19968
19969 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19970 })
19971 .ok()?;
19972 Some(())
19973 });
19974 }
19975
19976 fn on_buffer_event(
19977 &mut self,
19978 multibuffer: &Entity<MultiBuffer>,
19979 event: &multi_buffer::Event,
19980 window: &mut Window,
19981 cx: &mut Context<Self>,
19982 ) {
19983 match event {
19984 multi_buffer::Event::Edited {
19985 singleton_buffer_edited,
19986 edited_buffer,
19987 } => {
19988 self.scrollbar_marker_state.dirty = true;
19989 self.active_indent_guides_state.dirty = true;
19990 self.refresh_active_diagnostics(cx);
19991 self.refresh_code_actions(window, cx);
19992 self.refresh_selected_text_highlights(true, window, cx);
19993 self.refresh_single_line_folds(window, cx);
19994 refresh_matching_bracket_highlights(self, window, cx);
19995 if self.has_active_edit_prediction() {
19996 self.update_visible_edit_prediction(window, cx);
19997 }
19998 if let Some(project) = self.project.as_ref()
19999 && let Some(edited_buffer) = edited_buffer
20000 {
20001 project.update(cx, |project, cx| {
20002 self.registered_buffers
20003 .entry(edited_buffer.read(cx).remote_id())
20004 .or_insert_with(|| {
20005 project.register_buffer_with_language_servers(edited_buffer, cx)
20006 });
20007 });
20008 }
20009 cx.emit(EditorEvent::BufferEdited);
20010 cx.emit(SearchEvent::MatchesInvalidated);
20011
20012 if let Some(buffer) = edited_buffer {
20013 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20014 }
20015
20016 if *singleton_buffer_edited {
20017 if let Some(buffer) = edited_buffer
20018 && buffer.read(cx).file().is_none()
20019 {
20020 cx.emit(EditorEvent::TitleChanged);
20021 }
20022 if let Some(project) = &self.project {
20023 #[allow(clippy::mutable_key_type)]
20024 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20025 multibuffer
20026 .all_buffers()
20027 .into_iter()
20028 .filter_map(|buffer| {
20029 buffer.update(cx, |buffer, cx| {
20030 let language = buffer.language()?;
20031 let should_discard = project.update(cx, |project, cx| {
20032 project.is_local()
20033 && !project.has_language_servers_for(buffer, cx)
20034 });
20035 should_discard.not().then_some(language.clone())
20036 })
20037 })
20038 .collect::<HashSet<_>>()
20039 });
20040 if !languages_affected.is_empty() {
20041 self.refresh_inlay_hints(
20042 InlayHintRefreshReason::BufferEdited(languages_affected),
20043 cx,
20044 );
20045 }
20046 }
20047 }
20048
20049 let Some(project) = &self.project else { return };
20050 let (telemetry, is_via_ssh) = {
20051 let project = project.read(cx);
20052 let telemetry = project.client().telemetry().clone();
20053 let is_via_ssh = project.is_via_ssh();
20054 (telemetry, is_via_ssh)
20055 };
20056 refresh_linked_ranges(self, window, cx);
20057 telemetry.log_edit_event("editor", is_via_ssh);
20058 }
20059 multi_buffer::Event::ExcerptsAdded {
20060 buffer,
20061 predecessor,
20062 excerpts,
20063 } => {
20064 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20065 let buffer_id = buffer.read(cx).remote_id();
20066 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20067 && let Some(project) = &self.project
20068 {
20069 update_uncommitted_diff_for_buffer(
20070 cx.entity(),
20071 project,
20072 [buffer.clone()],
20073 self.buffer.clone(),
20074 cx,
20075 )
20076 .detach();
20077 }
20078 self.update_lsp_data(false, Some(buffer_id), window, cx);
20079 cx.emit(EditorEvent::ExcerptsAdded {
20080 buffer: buffer.clone(),
20081 predecessor: *predecessor,
20082 excerpts: excerpts.clone(),
20083 });
20084 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20085 }
20086 multi_buffer::Event::ExcerptsRemoved {
20087 ids,
20088 removed_buffer_ids,
20089 } => {
20090 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20091 let buffer = self.buffer.read(cx);
20092 self.registered_buffers
20093 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20094 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20095 cx.emit(EditorEvent::ExcerptsRemoved {
20096 ids: ids.clone(),
20097 removed_buffer_ids: removed_buffer_ids.clone(),
20098 });
20099 }
20100 multi_buffer::Event::ExcerptsEdited {
20101 excerpt_ids,
20102 buffer_ids,
20103 } => {
20104 self.display_map.update(cx, |map, cx| {
20105 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20106 });
20107 cx.emit(EditorEvent::ExcerptsEdited {
20108 ids: excerpt_ids.clone(),
20109 });
20110 }
20111 multi_buffer::Event::ExcerptsExpanded { ids } => {
20112 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20113 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20114 }
20115 multi_buffer::Event::Reparsed(buffer_id) => {
20116 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20117 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20118
20119 cx.emit(EditorEvent::Reparsed(*buffer_id));
20120 }
20121 multi_buffer::Event::DiffHunksToggled => {
20122 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20123 }
20124 multi_buffer::Event::LanguageChanged(buffer_id) => {
20125 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20126 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20127 cx.emit(EditorEvent::Reparsed(*buffer_id));
20128 cx.notify();
20129 }
20130 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20131 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20132 multi_buffer::Event::FileHandleChanged
20133 | multi_buffer::Event::Reloaded
20134 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20135 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20136 multi_buffer::Event::DiagnosticsUpdated => {
20137 self.update_diagnostics_state(window, cx);
20138 }
20139 _ => {}
20140 };
20141 }
20142
20143 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20144 if !self.diagnostics_enabled() {
20145 return;
20146 }
20147 self.refresh_active_diagnostics(cx);
20148 self.refresh_inline_diagnostics(true, window, cx);
20149 self.scrollbar_marker_state.dirty = true;
20150 cx.notify();
20151 }
20152
20153 pub fn start_temporary_diff_override(&mut self) {
20154 self.load_diff_task.take();
20155 self.temporary_diff_override = true;
20156 }
20157
20158 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20159 self.temporary_diff_override = false;
20160 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20161 self.buffer.update(cx, |buffer, cx| {
20162 buffer.set_all_diff_hunks_collapsed(cx);
20163 });
20164
20165 if let Some(project) = self.project.clone() {
20166 self.load_diff_task = Some(
20167 update_uncommitted_diff_for_buffer(
20168 cx.entity(),
20169 &project,
20170 self.buffer.read(cx).all_buffers(),
20171 self.buffer.clone(),
20172 cx,
20173 )
20174 .shared(),
20175 );
20176 }
20177 }
20178
20179 fn on_display_map_changed(
20180 &mut self,
20181 _: Entity<DisplayMap>,
20182 _: &mut Window,
20183 cx: &mut Context<Self>,
20184 ) {
20185 cx.notify();
20186 }
20187
20188 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20189 if self.diagnostics_enabled() {
20190 let new_severity = EditorSettings::get_global(cx)
20191 .diagnostics_max_severity
20192 .unwrap_or(DiagnosticSeverity::Hint);
20193 self.set_max_diagnostics_severity(new_severity, cx);
20194 }
20195 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20196 self.update_edit_prediction_settings(cx);
20197 self.refresh_edit_prediction(true, false, window, cx);
20198 self.refresh_inline_values(cx);
20199 self.refresh_inlay_hints(
20200 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20201 self.selections.newest_anchor().head(),
20202 &self.buffer.read(cx).snapshot(cx),
20203 cx,
20204 )),
20205 cx,
20206 );
20207
20208 let old_cursor_shape = self.cursor_shape;
20209 let old_show_breadcrumbs = self.show_breadcrumbs;
20210
20211 {
20212 let editor_settings = EditorSettings::get_global(cx);
20213 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20214 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20215 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20216 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20217 }
20218
20219 if old_cursor_shape != self.cursor_shape {
20220 cx.emit(EditorEvent::CursorShapeChanged);
20221 }
20222
20223 if old_show_breadcrumbs != self.show_breadcrumbs {
20224 cx.emit(EditorEvent::BreadcrumbsChanged);
20225 }
20226
20227 let project_settings = ProjectSettings::get_global(cx);
20228 self.serialize_dirty_buffers =
20229 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20230
20231 if self.mode.is_full() {
20232 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20233 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20234 if self.show_inline_diagnostics != show_inline_diagnostics {
20235 self.show_inline_diagnostics = show_inline_diagnostics;
20236 self.refresh_inline_diagnostics(false, window, cx);
20237 }
20238
20239 if self.git_blame_inline_enabled != inline_blame_enabled {
20240 self.toggle_git_blame_inline_internal(false, window, cx);
20241 }
20242
20243 let minimap_settings = EditorSettings::get_global(cx).minimap;
20244 if self.minimap_visibility != MinimapVisibility::Disabled {
20245 if self.minimap_visibility.settings_visibility()
20246 != minimap_settings.minimap_enabled()
20247 {
20248 self.set_minimap_visibility(
20249 MinimapVisibility::for_mode(self.mode(), cx),
20250 window,
20251 cx,
20252 );
20253 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20254 minimap_entity.update(cx, |minimap_editor, cx| {
20255 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20256 })
20257 }
20258 }
20259 }
20260
20261 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20262 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20263 }) {
20264 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20265 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20266 }
20267 self.refresh_colors(false, None, window, cx);
20268 }
20269
20270 cx.notify();
20271 }
20272
20273 pub fn set_searchable(&mut self, searchable: bool) {
20274 self.searchable = searchable;
20275 }
20276
20277 pub fn searchable(&self) -> bool {
20278 self.searchable
20279 }
20280
20281 fn open_proposed_changes_editor(
20282 &mut self,
20283 _: &OpenProposedChangesEditor,
20284 window: &mut Window,
20285 cx: &mut Context<Self>,
20286 ) {
20287 let Some(workspace) = self.workspace() else {
20288 cx.propagate();
20289 return;
20290 };
20291
20292 let selections = self.selections.all::<usize>(cx);
20293 let multi_buffer = self.buffer.read(cx);
20294 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20295 let mut new_selections_by_buffer = HashMap::default();
20296 for selection in selections {
20297 for (buffer, range, _) in
20298 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20299 {
20300 let mut range = range.to_point(buffer);
20301 range.start.column = 0;
20302 range.end.column = buffer.line_len(range.end.row);
20303 new_selections_by_buffer
20304 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20305 .or_insert(Vec::new())
20306 .push(range)
20307 }
20308 }
20309
20310 let proposed_changes_buffers = new_selections_by_buffer
20311 .into_iter()
20312 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20313 .collect::<Vec<_>>();
20314 let proposed_changes_editor = cx.new(|cx| {
20315 ProposedChangesEditor::new(
20316 "Proposed changes",
20317 proposed_changes_buffers,
20318 self.project.clone(),
20319 window,
20320 cx,
20321 )
20322 });
20323
20324 window.defer(cx, move |window, cx| {
20325 workspace.update(cx, |workspace, cx| {
20326 workspace.active_pane().update(cx, |pane, cx| {
20327 pane.add_item(
20328 Box::new(proposed_changes_editor),
20329 true,
20330 true,
20331 None,
20332 window,
20333 cx,
20334 );
20335 });
20336 });
20337 });
20338 }
20339
20340 pub fn open_excerpts_in_split(
20341 &mut self,
20342 _: &OpenExcerptsSplit,
20343 window: &mut Window,
20344 cx: &mut Context<Self>,
20345 ) {
20346 self.open_excerpts_common(None, true, window, cx)
20347 }
20348
20349 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20350 self.open_excerpts_common(None, false, window, cx)
20351 }
20352
20353 fn open_excerpts_common(
20354 &mut self,
20355 jump_data: Option<JumpData>,
20356 split: bool,
20357 window: &mut Window,
20358 cx: &mut Context<Self>,
20359 ) {
20360 let Some(workspace) = self.workspace() else {
20361 cx.propagate();
20362 return;
20363 };
20364
20365 if self.buffer.read(cx).is_singleton() {
20366 cx.propagate();
20367 return;
20368 }
20369
20370 let mut new_selections_by_buffer = HashMap::default();
20371 match &jump_data {
20372 Some(JumpData::MultiBufferPoint {
20373 excerpt_id,
20374 position,
20375 anchor,
20376 line_offset_from_top,
20377 }) => {
20378 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20379 if let Some(buffer) = multi_buffer_snapshot
20380 .buffer_id_for_excerpt(*excerpt_id)
20381 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20382 {
20383 let buffer_snapshot = buffer.read(cx).snapshot();
20384 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20385 language::ToPoint::to_point(anchor, &buffer_snapshot)
20386 } else {
20387 buffer_snapshot.clip_point(*position, Bias::Left)
20388 };
20389 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20390 new_selections_by_buffer.insert(
20391 buffer,
20392 (
20393 vec![jump_to_offset..jump_to_offset],
20394 Some(*line_offset_from_top),
20395 ),
20396 );
20397 }
20398 }
20399 Some(JumpData::MultiBufferRow {
20400 row,
20401 line_offset_from_top,
20402 }) => {
20403 let point = MultiBufferPoint::new(row.0, 0);
20404 if let Some((buffer, buffer_point, _)) =
20405 self.buffer.read(cx).point_to_buffer_point(point, cx)
20406 {
20407 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20408 new_selections_by_buffer
20409 .entry(buffer)
20410 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20411 .0
20412 .push(buffer_offset..buffer_offset)
20413 }
20414 }
20415 None => {
20416 let selections = self.selections.all::<usize>(cx);
20417 let multi_buffer = self.buffer.read(cx);
20418 for selection in selections {
20419 for (snapshot, range, _, anchor) in multi_buffer
20420 .snapshot(cx)
20421 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20422 {
20423 if let Some(anchor) = anchor {
20424 // selection is in a deleted hunk
20425 let Some(buffer_id) = anchor.buffer_id else {
20426 continue;
20427 };
20428 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20429 continue;
20430 };
20431 let offset = text::ToOffset::to_offset(
20432 &anchor.text_anchor,
20433 &buffer_handle.read(cx).snapshot(),
20434 );
20435 let range = offset..offset;
20436 new_selections_by_buffer
20437 .entry(buffer_handle)
20438 .or_insert((Vec::new(), None))
20439 .0
20440 .push(range)
20441 } else {
20442 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20443 else {
20444 continue;
20445 };
20446 new_selections_by_buffer
20447 .entry(buffer_handle)
20448 .or_insert((Vec::new(), None))
20449 .0
20450 .push(range)
20451 }
20452 }
20453 }
20454 }
20455 }
20456
20457 new_selections_by_buffer
20458 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20459
20460 if new_selections_by_buffer.is_empty() {
20461 return;
20462 }
20463
20464 // We defer the pane interaction because we ourselves are a workspace item
20465 // and activating a new item causes the pane to call a method on us reentrantly,
20466 // which panics if we're on the stack.
20467 window.defer(cx, move |window, cx| {
20468 workspace.update(cx, |workspace, cx| {
20469 let pane = if split {
20470 workspace.adjacent_pane(window, cx)
20471 } else {
20472 workspace.active_pane().clone()
20473 };
20474
20475 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20476 let editor = buffer
20477 .read(cx)
20478 .file()
20479 .is_none()
20480 .then(|| {
20481 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20482 // so `workspace.open_project_item` will never find them, always opening a new editor.
20483 // Instead, we try to activate the existing editor in the pane first.
20484 let (editor, pane_item_index) =
20485 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20486 let editor = item.downcast::<Editor>()?;
20487 let singleton_buffer =
20488 editor.read(cx).buffer().read(cx).as_singleton()?;
20489 if singleton_buffer == buffer {
20490 Some((editor, i))
20491 } else {
20492 None
20493 }
20494 })?;
20495 pane.update(cx, |pane, cx| {
20496 pane.activate_item(pane_item_index, true, true, window, cx)
20497 });
20498 Some(editor)
20499 })
20500 .flatten()
20501 .unwrap_or_else(|| {
20502 workspace.open_project_item::<Self>(
20503 pane.clone(),
20504 buffer,
20505 true,
20506 true,
20507 window,
20508 cx,
20509 )
20510 });
20511
20512 editor.update(cx, |editor, cx| {
20513 let autoscroll = match scroll_offset {
20514 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20515 None => Autoscroll::newest(),
20516 };
20517 let nav_history = editor.nav_history.take();
20518 editor.change_selections(
20519 SelectionEffects::scroll(autoscroll),
20520 window,
20521 cx,
20522 |s| {
20523 s.select_ranges(ranges);
20524 },
20525 );
20526 editor.nav_history = nav_history;
20527 });
20528 }
20529 })
20530 });
20531 }
20532
20533 // For now, don't allow opening excerpts in buffers that aren't backed by
20534 // regular project files.
20535 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20536 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20537 }
20538
20539 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20540 let snapshot = self.buffer.read(cx).read(cx);
20541 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20542 Some(
20543 ranges
20544 .iter()
20545 .map(move |range| {
20546 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20547 })
20548 .collect(),
20549 )
20550 }
20551
20552 fn selection_replacement_ranges(
20553 &self,
20554 range: Range<OffsetUtf16>,
20555 cx: &mut App,
20556 ) -> Vec<Range<OffsetUtf16>> {
20557 let selections = self.selections.all::<OffsetUtf16>(cx);
20558 let newest_selection = selections
20559 .iter()
20560 .max_by_key(|selection| selection.id)
20561 .unwrap();
20562 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20563 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20564 let snapshot = self.buffer.read(cx).read(cx);
20565 selections
20566 .into_iter()
20567 .map(|mut selection| {
20568 selection.start.0 =
20569 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20570 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20571 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20572 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20573 })
20574 .collect()
20575 }
20576
20577 fn report_editor_event(
20578 &self,
20579 reported_event: ReportEditorEvent,
20580 file_extension: Option<String>,
20581 cx: &App,
20582 ) {
20583 if cfg!(any(test, feature = "test-support")) {
20584 return;
20585 }
20586
20587 let Some(project) = &self.project else { return };
20588
20589 // If None, we are in a file without an extension
20590 let file = self
20591 .buffer
20592 .read(cx)
20593 .as_singleton()
20594 .and_then(|b| b.read(cx).file());
20595 let file_extension = file_extension.or(file
20596 .as_ref()
20597 .and_then(|file| Path::new(file.file_name(cx)).extension())
20598 .and_then(|e| e.to_str())
20599 .map(|a| a.to_string()));
20600
20601 let vim_mode = vim_enabled(cx);
20602
20603 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20604 let copilot_enabled = edit_predictions_provider
20605 == language::language_settings::EditPredictionProvider::Copilot;
20606 let copilot_enabled_for_language = self
20607 .buffer
20608 .read(cx)
20609 .language_settings(cx)
20610 .show_edit_predictions;
20611
20612 let project = project.read(cx);
20613 let event_type = reported_event.event_type();
20614
20615 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20616 telemetry::event!(
20617 event_type,
20618 type = if auto_saved {"autosave"} else {"manual"},
20619 file_extension,
20620 vim_mode,
20621 copilot_enabled,
20622 copilot_enabled_for_language,
20623 edit_predictions_provider,
20624 is_via_ssh = project.is_via_ssh(),
20625 );
20626 } else {
20627 telemetry::event!(
20628 event_type,
20629 file_extension,
20630 vim_mode,
20631 copilot_enabled,
20632 copilot_enabled_for_language,
20633 edit_predictions_provider,
20634 is_via_ssh = project.is_via_ssh(),
20635 );
20636 };
20637 }
20638
20639 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20640 /// with each line being an array of {text, highlight} objects.
20641 fn copy_highlight_json(
20642 &mut self,
20643 _: &CopyHighlightJson,
20644 window: &mut Window,
20645 cx: &mut Context<Self>,
20646 ) {
20647 #[derive(Serialize)]
20648 struct Chunk<'a> {
20649 text: String,
20650 highlight: Option<&'a str>,
20651 }
20652
20653 let snapshot = self.buffer.read(cx).snapshot(cx);
20654 let range = self
20655 .selected_text_range(false, window, cx)
20656 .and_then(|selection| {
20657 if selection.range.is_empty() {
20658 None
20659 } else {
20660 Some(selection.range)
20661 }
20662 })
20663 .unwrap_or_else(|| 0..snapshot.len());
20664
20665 let chunks = snapshot.chunks(range, true);
20666 let mut lines = Vec::new();
20667 let mut line: VecDeque<Chunk> = VecDeque::new();
20668
20669 let Some(style) = self.style.as_ref() else {
20670 return;
20671 };
20672
20673 for chunk in chunks {
20674 let highlight = chunk
20675 .syntax_highlight_id
20676 .and_then(|id| id.name(&style.syntax));
20677 let mut chunk_lines = chunk.text.split('\n').peekable();
20678 while let Some(text) = chunk_lines.next() {
20679 let mut merged_with_last_token = false;
20680 if let Some(last_token) = line.back_mut()
20681 && last_token.highlight == highlight
20682 {
20683 last_token.text.push_str(text);
20684 merged_with_last_token = true;
20685 }
20686
20687 if !merged_with_last_token {
20688 line.push_back(Chunk {
20689 text: text.into(),
20690 highlight,
20691 });
20692 }
20693
20694 if chunk_lines.peek().is_some() {
20695 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20696 line.pop_front();
20697 }
20698 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20699 line.pop_back();
20700 }
20701
20702 lines.push(mem::take(&mut line));
20703 }
20704 }
20705 }
20706
20707 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20708 return;
20709 };
20710 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20711 }
20712
20713 pub fn open_context_menu(
20714 &mut self,
20715 _: &OpenContextMenu,
20716 window: &mut Window,
20717 cx: &mut Context<Self>,
20718 ) {
20719 self.request_autoscroll(Autoscroll::newest(), cx);
20720 let position = self.selections.newest_display(cx).start;
20721 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20722 }
20723
20724 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20725 &self.inlay_hint_cache
20726 }
20727
20728 pub fn replay_insert_event(
20729 &mut self,
20730 text: &str,
20731 relative_utf16_range: Option<Range<isize>>,
20732 window: &mut Window,
20733 cx: &mut Context<Self>,
20734 ) {
20735 if !self.input_enabled {
20736 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20737 return;
20738 }
20739 if let Some(relative_utf16_range) = relative_utf16_range {
20740 let selections = self.selections.all::<OffsetUtf16>(cx);
20741 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20742 let new_ranges = selections.into_iter().map(|range| {
20743 let start = OffsetUtf16(
20744 range
20745 .head()
20746 .0
20747 .saturating_add_signed(relative_utf16_range.start),
20748 );
20749 let end = OffsetUtf16(
20750 range
20751 .head()
20752 .0
20753 .saturating_add_signed(relative_utf16_range.end),
20754 );
20755 start..end
20756 });
20757 s.select_ranges(new_ranges);
20758 });
20759 }
20760
20761 self.handle_input(text, window, cx);
20762 }
20763
20764 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20765 let Some(provider) = self.semantics_provider.as_ref() else {
20766 return false;
20767 };
20768
20769 let mut supports = false;
20770 self.buffer().update(cx, |this, cx| {
20771 this.for_each_buffer(|buffer| {
20772 supports |= provider.supports_inlay_hints(buffer, cx);
20773 });
20774 });
20775
20776 supports
20777 }
20778
20779 pub fn is_focused(&self, window: &Window) -> bool {
20780 self.focus_handle.is_focused(window)
20781 }
20782
20783 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20784 cx.emit(EditorEvent::Focused);
20785
20786 if let Some(descendant) = self
20787 .last_focused_descendant
20788 .take()
20789 .and_then(|descendant| descendant.upgrade())
20790 {
20791 window.focus(&descendant);
20792 } else {
20793 if let Some(blame) = self.blame.as_ref() {
20794 blame.update(cx, GitBlame::focus)
20795 }
20796
20797 self.blink_manager.update(cx, BlinkManager::enable);
20798 self.show_cursor_names(window, cx);
20799 self.buffer.update(cx, |buffer, cx| {
20800 buffer.finalize_last_transaction(cx);
20801 if self.leader_id.is_none() {
20802 buffer.set_active_selections(
20803 &self.selections.disjoint_anchors(),
20804 self.selections.line_mode,
20805 self.cursor_shape,
20806 cx,
20807 );
20808 }
20809 });
20810 }
20811 }
20812
20813 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20814 cx.emit(EditorEvent::FocusedIn)
20815 }
20816
20817 fn handle_focus_out(
20818 &mut self,
20819 event: FocusOutEvent,
20820 _window: &mut Window,
20821 cx: &mut Context<Self>,
20822 ) {
20823 if event.blurred != self.focus_handle {
20824 self.last_focused_descendant = Some(event.blurred);
20825 }
20826 self.selection_drag_state = SelectionDragState::None;
20827 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20828 }
20829
20830 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20831 self.blink_manager.update(cx, BlinkManager::disable);
20832 self.buffer
20833 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20834
20835 if let Some(blame) = self.blame.as_ref() {
20836 blame.update(cx, GitBlame::blur)
20837 }
20838 if !self.hover_state.focused(window, cx) {
20839 hide_hover(self, cx);
20840 }
20841 if !self
20842 .context_menu
20843 .borrow()
20844 .as_ref()
20845 .is_some_and(|context_menu| context_menu.focused(window, cx))
20846 {
20847 self.hide_context_menu(window, cx);
20848 }
20849 self.discard_edit_prediction(false, cx);
20850 cx.emit(EditorEvent::Blurred);
20851 cx.notify();
20852 }
20853
20854 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20855 let mut pending: String = window
20856 .pending_input_keystrokes()
20857 .into_iter()
20858 .flatten()
20859 .filter_map(|keystroke| {
20860 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20861 keystroke.key_char.clone()
20862 } else {
20863 None
20864 }
20865 })
20866 .collect();
20867
20868 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20869 pending = "".to_string();
20870 }
20871
20872 let existing_pending = self
20873 .text_highlights::<PendingInput>(cx)
20874 .map(|(_, ranges)| ranges.to_vec());
20875 if existing_pending.is_none() && pending.is_empty() {
20876 return;
20877 }
20878 let transaction =
20879 self.transact(window, cx, |this, window, cx| {
20880 let selections = this.selections.all::<usize>(cx);
20881 let edits = selections
20882 .iter()
20883 .map(|selection| (selection.end..selection.end, pending.clone()));
20884 this.edit(edits, cx);
20885 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20886 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20887 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20888 }));
20889 });
20890 if let Some(existing_ranges) = existing_pending {
20891 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20892 this.edit(edits, cx);
20893 }
20894 });
20895
20896 let snapshot = self.snapshot(window, cx);
20897 let ranges = self
20898 .selections
20899 .all::<usize>(cx)
20900 .into_iter()
20901 .map(|selection| {
20902 snapshot.buffer_snapshot.anchor_after(selection.end)
20903 ..snapshot
20904 .buffer_snapshot
20905 .anchor_before(selection.end + pending.len())
20906 })
20907 .collect();
20908
20909 if pending.is_empty() {
20910 self.clear_highlights::<PendingInput>(cx);
20911 } else {
20912 self.highlight_text::<PendingInput>(
20913 ranges,
20914 HighlightStyle {
20915 underline: Some(UnderlineStyle {
20916 thickness: px(1.),
20917 color: None,
20918 wavy: false,
20919 }),
20920 ..Default::default()
20921 },
20922 cx,
20923 );
20924 }
20925
20926 self.ime_transaction = self.ime_transaction.or(transaction);
20927 if let Some(transaction) = self.ime_transaction {
20928 self.buffer.update(cx, |buffer, cx| {
20929 buffer.group_until_transaction(transaction, cx);
20930 });
20931 }
20932
20933 if self.text_highlights::<PendingInput>(cx).is_none() {
20934 self.ime_transaction.take();
20935 }
20936 }
20937
20938 pub fn register_action_renderer(
20939 &mut self,
20940 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20941 ) -> Subscription {
20942 let id = self.next_editor_action_id.post_inc();
20943 self.editor_actions
20944 .borrow_mut()
20945 .insert(id, Box::new(listener));
20946
20947 let editor_actions = self.editor_actions.clone();
20948 Subscription::new(move || {
20949 editor_actions.borrow_mut().remove(&id);
20950 })
20951 }
20952
20953 pub fn register_action<A: Action>(
20954 &mut self,
20955 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20956 ) -> Subscription {
20957 let id = self.next_editor_action_id.post_inc();
20958 let listener = Arc::new(listener);
20959 self.editor_actions.borrow_mut().insert(
20960 id,
20961 Box::new(move |_, window, _| {
20962 let listener = listener.clone();
20963 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20964 let action = action.downcast_ref().unwrap();
20965 if phase == DispatchPhase::Bubble {
20966 listener(action, window, cx)
20967 }
20968 })
20969 }),
20970 );
20971
20972 let editor_actions = self.editor_actions.clone();
20973 Subscription::new(move || {
20974 editor_actions.borrow_mut().remove(&id);
20975 })
20976 }
20977
20978 pub fn file_header_size(&self) -> u32 {
20979 FILE_HEADER_HEIGHT
20980 }
20981
20982 pub fn restore(
20983 &mut self,
20984 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20985 window: &mut Window,
20986 cx: &mut Context<Self>,
20987 ) {
20988 let workspace = self.workspace();
20989 let project = self.project();
20990 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20991 let mut tasks = Vec::new();
20992 for (buffer_id, changes) in revert_changes {
20993 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20994 buffer.update(cx, |buffer, cx| {
20995 buffer.edit(
20996 changes
20997 .into_iter()
20998 .map(|(range, text)| (range, text.to_string())),
20999 None,
21000 cx,
21001 );
21002 });
21003
21004 if let Some(project) =
21005 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21006 {
21007 project.update(cx, |project, cx| {
21008 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21009 })
21010 }
21011 }
21012 }
21013 tasks
21014 });
21015 cx.spawn_in(window, async move |_, cx| {
21016 for (buffer, task) in save_tasks {
21017 let result = task.await;
21018 if result.is_err() {
21019 let Some(path) = buffer
21020 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21021 .ok()
21022 else {
21023 continue;
21024 };
21025 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21026 let Some(task) = cx
21027 .update_window_entity(workspace, |workspace, window, cx| {
21028 workspace
21029 .open_path_preview(path, None, false, false, false, window, cx)
21030 })
21031 .ok()
21032 else {
21033 continue;
21034 };
21035 task.await.log_err();
21036 }
21037 }
21038 }
21039 })
21040 .detach();
21041 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21042 selections.refresh()
21043 });
21044 }
21045
21046 pub fn to_pixel_point(
21047 &self,
21048 source: multi_buffer::Anchor,
21049 editor_snapshot: &EditorSnapshot,
21050 window: &mut Window,
21051 ) -> Option<gpui::Point<Pixels>> {
21052 let source_point = source.to_display_point(editor_snapshot);
21053 self.display_to_pixel_point(source_point, editor_snapshot, window)
21054 }
21055
21056 pub fn display_to_pixel_point(
21057 &self,
21058 source: DisplayPoint,
21059 editor_snapshot: &EditorSnapshot,
21060 window: &mut Window,
21061 ) -> Option<gpui::Point<Pixels>> {
21062 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21063 let text_layout_details = self.text_layout_details(window);
21064 let scroll_top = text_layout_details
21065 .scroll_anchor
21066 .scroll_position(editor_snapshot)
21067 .y;
21068
21069 if source.row().as_f32() < scroll_top.floor() {
21070 return None;
21071 }
21072 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21073 let source_y = line_height * (source.row().as_f32() - scroll_top);
21074 Some(gpui::Point::new(source_x, source_y))
21075 }
21076
21077 pub fn has_visible_completions_menu(&self) -> bool {
21078 !self.edit_prediction_preview_is_active()
21079 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21080 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21081 })
21082 }
21083
21084 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21085 if self.mode.is_minimap() {
21086 return;
21087 }
21088 self.addons
21089 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21090 }
21091
21092 pub fn unregister_addon<T: Addon>(&mut self) {
21093 self.addons.remove(&std::any::TypeId::of::<T>());
21094 }
21095
21096 pub fn addon<T: Addon>(&self) -> Option<&T> {
21097 let type_id = std::any::TypeId::of::<T>();
21098 self.addons
21099 .get(&type_id)
21100 .and_then(|item| item.to_any().downcast_ref::<T>())
21101 }
21102
21103 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21104 let type_id = std::any::TypeId::of::<T>();
21105 self.addons
21106 .get_mut(&type_id)
21107 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21108 }
21109
21110 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21111 let text_layout_details = self.text_layout_details(window);
21112 let style = &text_layout_details.editor_style;
21113 let font_id = window.text_system().resolve_font(&style.text.font());
21114 let font_size = style.text.font_size.to_pixels(window.rem_size());
21115 let line_height = style.text.line_height_in_pixels(window.rem_size());
21116 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21117 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21118
21119 CharacterDimensions {
21120 em_width,
21121 em_advance,
21122 line_height,
21123 }
21124 }
21125
21126 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21127 self.load_diff_task.clone()
21128 }
21129
21130 fn read_metadata_from_db(
21131 &mut self,
21132 item_id: u64,
21133 workspace_id: WorkspaceId,
21134 window: &mut Window,
21135 cx: &mut Context<Editor>,
21136 ) {
21137 if self.is_singleton(cx)
21138 && !self.mode.is_minimap()
21139 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21140 {
21141 let buffer_snapshot = OnceCell::new();
21142
21143 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21144 && !folds.is_empty()
21145 {
21146 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21147 self.fold_ranges(
21148 folds
21149 .into_iter()
21150 .map(|(start, end)| {
21151 snapshot.clip_offset(start, Bias::Left)
21152 ..snapshot.clip_offset(end, Bias::Right)
21153 })
21154 .collect(),
21155 false,
21156 window,
21157 cx,
21158 );
21159 }
21160
21161 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21162 && !selections.is_empty()
21163 {
21164 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21165 // skip adding the initial selection to selection history
21166 self.selection_history.mode = SelectionHistoryMode::Skipping;
21167 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21168 s.select_ranges(selections.into_iter().map(|(start, end)| {
21169 snapshot.clip_offset(start, Bias::Left)
21170 ..snapshot.clip_offset(end, Bias::Right)
21171 }));
21172 });
21173 self.selection_history.mode = SelectionHistoryMode::Normal;
21174 };
21175 }
21176
21177 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21178 }
21179
21180 fn update_lsp_data(
21181 &mut self,
21182 ignore_cache: bool,
21183 for_buffer: Option<BufferId>,
21184 window: &mut Window,
21185 cx: &mut Context<'_, Self>,
21186 ) {
21187 self.pull_diagnostics(for_buffer, window, cx);
21188 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21189 }
21190}
21191
21192fn vim_enabled(cx: &App) -> bool {
21193 cx.global::<SettingsStore>()
21194 .raw_user_settings()
21195 .get("vim_mode")
21196 == Some(&serde_json::Value::Bool(true))
21197}
21198
21199fn process_completion_for_edit(
21200 completion: &Completion,
21201 intent: CompletionIntent,
21202 buffer: &Entity<Buffer>,
21203 cursor_position: &text::Anchor,
21204 cx: &mut Context<Editor>,
21205) -> CompletionEdit {
21206 let buffer = buffer.read(cx);
21207 let buffer_snapshot = buffer.snapshot();
21208 let (snippet, new_text) = if completion.is_snippet() {
21209 // Workaround for typescript language server issues so that methods don't expand within
21210 // strings and functions with type expressions. The previous point is used because the query
21211 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21212 let mut snippet_source = completion.new_text.clone();
21213 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21214 previous_point.column = previous_point.column.saturating_sub(1);
21215 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21216 && scope.prefers_label_for_snippet_in_completion()
21217 && let Some(label) = completion.label()
21218 && matches!(
21219 completion.kind(),
21220 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21221 )
21222 {
21223 snippet_source = label;
21224 }
21225 match Snippet::parse(&snippet_source).log_err() {
21226 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21227 None => (None, completion.new_text.clone()),
21228 }
21229 } else {
21230 (None, completion.new_text.clone())
21231 };
21232
21233 let mut range_to_replace = {
21234 let replace_range = &completion.replace_range;
21235 if let CompletionSource::Lsp {
21236 insert_range: Some(insert_range),
21237 ..
21238 } = &completion.source
21239 {
21240 debug_assert_eq!(
21241 insert_range.start, replace_range.start,
21242 "insert_range and replace_range should start at the same position"
21243 );
21244 debug_assert!(
21245 insert_range
21246 .start
21247 .cmp(cursor_position, &buffer_snapshot)
21248 .is_le(),
21249 "insert_range should start before or at cursor position"
21250 );
21251 debug_assert!(
21252 replace_range
21253 .start
21254 .cmp(cursor_position, &buffer_snapshot)
21255 .is_le(),
21256 "replace_range should start before or at cursor position"
21257 );
21258
21259 let should_replace = match intent {
21260 CompletionIntent::CompleteWithInsert => false,
21261 CompletionIntent::CompleteWithReplace => true,
21262 CompletionIntent::Complete | CompletionIntent::Compose => {
21263 let insert_mode =
21264 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21265 .completions
21266 .lsp_insert_mode;
21267 match insert_mode {
21268 LspInsertMode::Insert => false,
21269 LspInsertMode::Replace => true,
21270 LspInsertMode::ReplaceSubsequence => {
21271 let mut text_to_replace = buffer.chars_for_range(
21272 buffer.anchor_before(replace_range.start)
21273 ..buffer.anchor_after(replace_range.end),
21274 );
21275 let mut current_needle = text_to_replace.next();
21276 for haystack_ch in completion.label.text.chars() {
21277 if let Some(needle_ch) = current_needle
21278 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21279 {
21280 current_needle = text_to_replace.next();
21281 }
21282 }
21283 current_needle.is_none()
21284 }
21285 LspInsertMode::ReplaceSuffix => {
21286 if replace_range
21287 .end
21288 .cmp(cursor_position, &buffer_snapshot)
21289 .is_gt()
21290 {
21291 let range_after_cursor = *cursor_position..replace_range.end;
21292 let text_after_cursor = buffer
21293 .text_for_range(
21294 buffer.anchor_before(range_after_cursor.start)
21295 ..buffer.anchor_after(range_after_cursor.end),
21296 )
21297 .collect::<String>()
21298 .to_ascii_lowercase();
21299 completion
21300 .label
21301 .text
21302 .to_ascii_lowercase()
21303 .ends_with(&text_after_cursor)
21304 } else {
21305 true
21306 }
21307 }
21308 }
21309 }
21310 };
21311
21312 if should_replace {
21313 replace_range.clone()
21314 } else {
21315 insert_range.clone()
21316 }
21317 } else {
21318 replace_range.clone()
21319 }
21320 };
21321
21322 if range_to_replace
21323 .end
21324 .cmp(cursor_position, &buffer_snapshot)
21325 .is_lt()
21326 {
21327 range_to_replace.end = *cursor_position;
21328 }
21329
21330 CompletionEdit {
21331 new_text,
21332 replace_range: range_to_replace.to_offset(buffer),
21333 snippet,
21334 }
21335}
21336
21337struct CompletionEdit {
21338 new_text: String,
21339 replace_range: Range<usize>,
21340 snippet: Option<Snippet>,
21341}
21342
21343fn insert_extra_newline_brackets(
21344 buffer: &MultiBufferSnapshot,
21345 range: Range<usize>,
21346 language: &language::LanguageScope,
21347) -> bool {
21348 let leading_whitespace_len = buffer
21349 .reversed_chars_at(range.start)
21350 .take_while(|c| c.is_whitespace() && *c != '\n')
21351 .map(|c| c.len_utf8())
21352 .sum::<usize>();
21353 let trailing_whitespace_len = buffer
21354 .chars_at(range.end)
21355 .take_while(|c| c.is_whitespace() && *c != '\n')
21356 .map(|c| c.len_utf8())
21357 .sum::<usize>();
21358 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21359
21360 language.brackets().any(|(pair, enabled)| {
21361 let pair_start = pair.start.trim_end();
21362 let pair_end = pair.end.trim_start();
21363
21364 enabled
21365 && pair.newline
21366 && buffer.contains_str_at(range.end, pair_end)
21367 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21368 })
21369}
21370
21371fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21372 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21373 [(buffer, range, _)] => (*buffer, range.clone()),
21374 _ => return false,
21375 };
21376 let pair = {
21377 let mut result: Option<BracketMatch> = None;
21378
21379 for pair in buffer
21380 .all_bracket_ranges(range.clone())
21381 .filter(move |pair| {
21382 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21383 })
21384 {
21385 let len = pair.close_range.end - pair.open_range.start;
21386
21387 if let Some(existing) = &result {
21388 let existing_len = existing.close_range.end - existing.open_range.start;
21389 if len > existing_len {
21390 continue;
21391 }
21392 }
21393
21394 result = Some(pair);
21395 }
21396
21397 result
21398 };
21399 let Some(pair) = pair else {
21400 return false;
21401 };
21402 pair.newline_only
21403 && buffer
21404 .chars_for_range(pair.open_range.end..range.start)
21405 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21406 .all(|c| c.is_whitespace() && c != '\n')
21407}
21408
21409fn update_uncommitted_diff_for_buffer(
21410 editor: Entity<Editor>,
21411 project: &Entity<Project>,
21412 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21413 buffer: Entity<MultiBuffer>,
21414 cx: &mut App,
21415) -> Task<()> {
21416 let mut tasks = Vec::new();
21417 project.update(cx, |project, cx| {
21418 for buffer in buffers {
21419 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21420 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21421 }
21422 }
21423 });
21424 cx.spawn(async move |cx| {
21425 let diffs = future::join_all(tasks).await;
21426 if editor
21427 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21428 .unwrap_or(false)
21429 {
21430 return;
21431 }
21432
21433 buffer
21434 .update(cx, |buffer, cx| {
21435 for diff in diffs.into_iter().flatten() {
21436 buffer.add_diff(diff, cx);
21437 }
21438 })
21439 .ok();
21440 })
21441}
21442
21443fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21444 let tab_size = tab_size.get() as usize;
21445 let mut width = offset;
21446
21447 for ch in text.chars() {
21448 width += if ch == '\t' {
21449 tab_size - (width % tab_size)
21450 } else {
21451 1
21452 };
21453 }
21454
21455 width - offset
21456}
21457
21458#[cfg(test)]
21459mod tests {
21460 use super::*;
21461
21462 #[test]
21463 fn test_string_size_with_expanded_tabs() {
21464 let nz = |val| NonZeroU32::new(val).unwrap();
21465 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21466 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21467 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21468 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21469 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21470 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21471 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21472 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21473 }
21474}
21475
21476/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21477struct WordBreakingTokenizer<'a> {
21478 input: &'a str,
21479}
21480
21481impl<'a> WordBreakingTokenizer<'a> {
21482 fn new(input: &'a str) -> Self {
21483 Self { input }
21484 }
21485}
21486
21487fn is_char_ideographic(ch: char) -> bool {
21488 use unicode_script::Script::*;
21489 use unicode_script::UnicodeScript;
21490 matches!(ch.script(), Han | Tangut | Yi)
21491}
21492
21493fn is_grapheme_ideographic(text: &str) -> bool {
21494 text.chars().any(is_char_ideographic)
21495}
21496
21497fn is_grapheme_whitespace(text: &str) -> bool {
21498 text.chars().any(|x| x.is_whitespace())
21499}
21500
21501fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21502 text.chars()
21503 .next()
21504 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21505}
21506
21507#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21508enum WordBreakToken<'a> {
21509 Word { token: &'a str, grapheme_len: usize },
21510 InlineWhitespace { token: &'a str, grapheme_len: usize },
21511 Newline,
21512}
21513
21514impl<'a> Iterator for WordBreakingTokenizer<'a> {
21515 /// Yields a span, the count of graphemes in the token, and whether it was
21516 /// whitespace. Note that it also breaks at word boundaries.
21517 type Item = WordBreakToken<'a>;
21518
21519 fn next(&mut self) -> Option<Self::Item> {
21520 use unicode_segmentation::UnicodeSegmentation;
21521 if self.input.is_empty() {
21522 return None;
21523 }
21524
21525 let mut iter = self.input.graphemes(true).peekable();
21526 let mut offset = 0;
21527 let mut grapheme_len = 0;
21528 if let Some(first_grapheme) = iter.next() {
21529 let is_newline = first_grapheme == "\n";
21530 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21531 offset += first_grapheme.len();
21532 grapheme_len += 1;
21533 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21534 if let Some(grapheme) = iter.peek().copied()
21535 && should_stay_with_preceding_ideograph(grapheme)
21536 {
21537 offset += grapheme.len();
21538 grapheme_len += 1;
21539 }
21540 } else {
21541 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21542 let mut next_word_bound = words.peek().copied();
21543 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21544 next_word_bound = words.next();
21545 }
21546 while let Some(grapheme) = iter.peek().copied() {
21547 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21548 break;
21549 };
21550 if is_grapheme_whitespace(grapheme) != is_whitespace
21551 || (grapheme == "\n") != is_newline
21552 {
21553 break;
21554 };
21555 offset += grapheme.len();
21556 grapheme_len += 1;
21557 iter.next();
21558 }
21559 }
21560 let token = &self.input[..offset];
21561 self.input = &self.input[offset..];
21562 if token == "\n" {
21563 Some(WordBreakToken::Newline)
21564 } else if is_whitespace {
21565 Some(WordBreakToken::InlineWhitespace {
21566 token,
21567 grapheme_len,
21568 })
21569 } else {
21570 Some(WordBreakToken::Word {
21571 token,
21572 grapheme_len,
21573 })
21574 }
21575 } else {
21576 None
21577 }
21578 }
21579}
21580
21581#[test]
21582fn test_word_breaking_tokenizer() {
21583 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21584 ("", &[]),
21585 (" ", &[whitespace(" ", 2)]),
21586 ("Ʒ", &[word("Ʒ", 1)]),
21587 ("Ǽ", &[word("Ǽ", 1)]),
21588 ("⋑", &[word("⋑", 1)]),
21589 ("⋑⋑", &[word("⋑⋑", 2)]),
21590 (
21591 "原理,进而",
21592 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21593 ),
21594 (
21595 "hello world",
21596 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21597 ),
21598 (
21599 "hello, world",
21600 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21601 ),
21602 (
21603 " hello world",
21604 &[
21605 whitespace(" ", 2),
21606 word("hello", 5),
21607 whitespace(" ", 1),
21608 word("world", 5),
21609 ],
21610 ),
21611 (
21612 "这是什么 \n 钢笔",
21613 &[
21614 word("这", 1),
21615 word("是", 1),
21616 word("什", 1),
21617 word("么", 1),
21618 whitespace(" ", 1),
21619 newline(),
21620 whitespace(" ", 1),
21621 word("钢", 1),
21622 word("笔", 1),
21623 ],
21624 ),
21625 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21626 ];
21627
21628 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21629 WordBreakToken::Word {
21630 token,
21631 grapheme_len,
21632 }
21633 }
21634
21635 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21636 WordBreakToken::InlineWhitespace {
21637 token,
21638 grapheme_len,
21639 }
21640 }
21641
21642 fn newline() -> WordBreakToken<'static> {
21643 WordBreakToken::Newline
21644 }
21645
21646 for (input, result) in tests {
21647 assert_eq!(
21648 WordBreakingTokenizer::new(input)
21649 .collect::<Vec<_>>()
21650 .as_slice(),
21651 *result,
21652 );
21653 }
21654}
21655
21656fn wrap_with_prefix(
21657 first_line_prefix: String,
21658 subsequent_lines_prefix: String,
21659 unwrapped_text: String,
21660 wrap_column: usize,
21661 tab_size: NonZeroU32,
21662 preserve_existing_whitespace: bool,
21663) -> String {
21664 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21665 let subsequent_lines_prefix_len =
21666 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21667 let mut wrapped_text = String::new();
21668 let mut current_line = first_line_prefix;
21669 let mut is_first_line = true;
21670
21671 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21672 let mut current_line_len = first_line_prefix_len;
21673 let mut in_whitespace = false;
21674 for token in tokenizer {
21675 let have_preceding_whitespace = in_whitespace;
21676 match token {
21677 WordBreakToken::Word {
21678 token,
21679 grapheme_len,
21680 } => {
21681 in_whitespace = false;
21682 let current_prefix_len = if is_first_line {
21683 first_line_prefix_len
21684 } else {
21685 subsequent_lines_prefix_len
21686 };
21687 if current_line_len + grapheme_len > wrap_column
21688 && current_line_len != current_prefix_len
21689 {
21690 wrapped_text.push_str(current_line.trim_end());
21691 wrapped_text.push('\n');
21692 is_first_line = false;
21693 current_line = subsequent_lines_prefix.clone();
21694 current_line_len = subsequent_lines_prefix_len;
21695 }
21696 current_line.push_str(token);
21697 current_line_len += grapheme_len;
21698 }
21699 WordBreakToken::InlineWhitespace {
21700 mut token,
21701 mut grapheme_len,
21702 } => {
21703 in_whitespace = true;
21704 if have_preceding_whitespace && !preserve_existing_whitespace {
21705 continue;
21706 }
21707 if !preserve_existing_whitespace {
21708 token = " ";
21709 grapheme_len = 1;
21710 }
21711 let current_prefix_len = if is_first_line {
21712 first_line_prefix_len
21713 } else {
21714 subsequent_lines_prefix_len
21715 };
21716 if current_line_len + grapheme_len > wrap_column {
21717 wrapped_text.push_str(current_line.trim_end());
21718 wrapped_text.push('\n');
21719 is_first_line = false;
21720 current_line = subsequent_lines_prefix.clone();
21721 current_line_len = subsequent_lines_prefix_len;
21722 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21723 current_line.push_str(token);
21724 current_line_len += grapheme_len;
21725 }
21726 }
21727 WordBreakToken::Newline => {
21728 in_whitespace = true;
21729 let current_prefix_len = if is_first_line {
21730 first_line_prefix_len
21731 } else {
21732 subsequent_lines_prefix_len
21733 };
21734 if preserve_existing_whitespace {
21735 wrapped_text.push_str(current_line.trim_end());
21736 wrapped_text.push('\n');
21737 is_first_line = false;
21738 current_line = subsequent_lines_prefix.clone();
21739 current_line_len = subsequent_lines_prefix_len;
21740 } else if have_preceding_whitespace {
21741 continue;
21742 } else if current_line_len + 1 > wrap_column
21743 && current_line_len != current_prefix_len
21744 {
21745 wrapped_text.push_str(current_line.trim_end());
21746 wrapped_text.push('\n');
21747 is_first_line = false;
21748 current_line = subsequent_lines_prefix.clone();
21749 current_line_len = subsequent_lines_prefix_len;
21750 } else if current_line_len != current_prefix_len {
21751 current_line.push(' ');
21752 current_line_len += 1;
21753 }
21754 }
21755 }
21756 }
21757
21758 if !current_line.is_empty() {
21759 wrapped_text.push_str(¤t_line);
21760 }
21761 wrapped_text
21762}
21763
21764#[test]
21765fn test_wrap_with_prefix() {
21766 assert_eq!(
21767 wrap_with_prefix(
21768 "# ".to_string(),
21769 "# ".to_string(),
21770 "abcdefg".to_string(),
21771 4,
21772 NonZeroU32::new(4).unwrap(),
21773 false,
21774 ),
21775 "# abcdefg"
21776 );
21777 assert_eq!(
21778 wrap_with_prefix(
21779 "".to_string(),
21780 "".to_string(),
21781 "\thello world".to_string(),
21782 8,
21783 NonZeroU32::new(4).unwrap(),
21784 false,
21785 ),
21786 "hello\nworld"
21787 );
21788 assert_eq!(
21789 wrap_with_prefix(
21790 "// ".to_string(),
21791 "// ".to_string(),
21792 "xx \nyy zz aa bb cc".to_string(),
21793 12,
21794 NonZeroU32::new(4).unwrap(),
21795 false,
21796 ),
21797 "// xx yy zz\n// aa bb cc"
21798 );
21799 assert_eq!(
21800 wrap_with_prefix(
21801 String::new(),
21802 String::new(),
21803 "这是什么 \n 钢笔".to_string(),
21804 3,
21805 NonZeroU32::new(4).unwrap(),
21806 false,
21807 ),
21808 "这是什\n么 钢\n笔"
21809 );
21810}
21811
21812pub trait CollaborationHub {
21813 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21814 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21815 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21816}
21817
21818impl CollaborationHub for Entity<Project> {
21819 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21820 self.read(cx).collaborators()
21821 }
21822
21823 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21824 self.read(cx).user_store().read(cx).participant_indices()
21825 }
21826
21827 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21828 let this = self.read(cx);
21829 let user_ids = this.collaborators().values().map(|c| c.user_id);
21830 this.user_store().read(cx).participant_names(user_ids, cx)
21831 }
21832}
21833
21834pub trait SemanticsProvider {
21835 fn hover(
21836 &self,
21837 buffer: &Entity<Buffer>,
21838 position: text::Anchor,
21839 cx: &mut App,
21840 ) -> Option<Task<Vec<project::Hover>>>;
21841
21842 fn inline_values(
21843 &self,
21844 buffer_handle: Entity<Buffer>,
21845 range: Range<text::Anchor>,
21846 cx: &mut App,
21847 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21848
21849 fn inlay_hints(
21850 &self,
21851 buffer_handle: Entity<Buffer>,
21852 range: Range<text::Anchor>,
21853 cx: &mut App,
21854 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21855
21856 fn resolve_inlay_hint(
21857 &self,
21858 hint: InlayHint,
21859 buffer_handle: Entity<Buffer>,
21860 server_id: LanguageServerId,
21861 cx: &mut App,
21862 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21863
21864 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21865
21866 fn document_highlights(
21867 &self,
21868 buffer: &Entity<Buffer>,
21869 position: text::Anchor,
21870 cx: &mut App,
21871 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21872
21873 fn definitions(
21874 &self,
21875 buffer: &Entity<Buffer>,
21876 position: text::Anchor,
21877 kind: GotoDefinitionKind,
21878 cx: &mut App,
21879 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21880
21881 fn range_for_rename(
21882 &self,
21883 buffer: &Entity<Buffer>,
21884 position: text::Anchor,
21885 cx: &mut App,
21886 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21887
21888 fn perform_rename(
21889 &self,
21890 buffer: &Entity<Buffer>,
21891 position: text::Anchor,
21892 new_name: String,
21893 cx: &mut App,
21894 ) -> Option<Task<Result<ProjectTransaction>>>;
21895}
21896
21897pub trait CompletionProvider {
21898 fn completions(
21899 &self,
21900 excerpt_id: ExcerptId,
21901 buffer: &Entity<Buffer>,
21902 buffer_position: text::Anchor,
21903 trigger: CompletionContext,
21904 window: &mut Window,
21905 cx: &mut Context<Editor>,
21906 ) -> Task<Result<Vec<CompletionResponse>>>;
21907
21908 fn resolve_completions(
21909 &self,
21910 _buffer: Entity<Buffer>,
21911 _completion_indices: Vec<usize>,
21912 _completions: Rc<RefCell<Box<[Completion]>>>,
21913 _cx: &mut Context<Editor>,
21914 ) -> Task<Result<bool>> {
21915 Task::ready(Ok(false))
21916 }
21917
21918 fn apply_additional_edits_for_completion(
21919 &self,
21920 _buffer: Entity<Buffer>,
21921 _completions: Rc<RefCell<Box<[Completion]>>>,
21922 _completion_index: usize,
21923 _push_to_history: bool,
21924 _cx: &mut Context<Editor>,
21925 ) -> Task<Result<Option<language::Transaction>>> {
21926 Task::ready(Ok(None))
21927 }
21928
21929 fn is_completion_trigger(
21930 &self,
21931 buffer: &Entity<Buffer>,
21932 position: language::Anchor,
21933 text: &str,
21934 trigger_in_words: bool,
21935 menu_is_open: bool,
21936 cx: &mut Context<Editor>,
21937 ) -> bool;
21938
21939 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21940
21941 fn sort_completions(&self) -> bool {
21942 true
21943 }
21944
21945 fn filter_completions(&self) -> bool {
21946 true
21947 }
21948}
21949
21950pub trait CodeActionProvider {
21951 fn id(&self) -> Arc<str>;
21952
21953 fn code_actions(
21954 &self,
21955 buffer: &Entity<Buffer>,
21956 range: Range<text::Anchor>,
21957 window: &mut Window,
21958 cx: &mut App,
21959 ) -> Task<Result<Vec<CodeAction>>>;
21960
21961 fn apply_code_action(
21962 &self,
21963 buffer_handle: Entity<Buffer>,
21964 action: CodeAction,
21965 excerpt_id: ExcerptId,
21966 push_to_history: bool,
21967 window: &mut Window,
21968 cx: &mut App,
21969 ) -> Task<Result<ProjectTransaction>>;
21970}
21971
21972impl CodeActionProvider for Entity<Project> {
21973 fn id(&self) -> Arc<str> {
21974 "project".into()
21975 }
21976
21977 fn code_actions(
21978 &self,
21979 buffer: &Entity<Buffer>,
21980 range: Range<text::Anchor>,
21981 _window: &mut Window,
21982 cx: &mut App,
21983 ) -> Task<Result<Vec<CodeAction>>> {
21984 self.update(cx, |project, cx| {
21985 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21986 let code_actions = project.code_actions(buffer, range, None, cx);
21987 cx.background_spawn(async move {
21988 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21989 Ok(code_lens_actions
21990 .context("code lens fetch")?
21991 .into_iter()
21992 .chain(code_actions.context("code action fetch")?)
21993 .collect())
21994 })
21995 })
21996 }
21997
21998 fn apply_code_action(
21999 &self,
22000 buffer_handle: Entity<Buffer>,
22001 action: CodeAction,
22002 _excerpt_id: ExcerptId,
22003 push_to_history: bool,
22004 _window: &mut Window,
22005 cx: &mut App,
22006 ) -> Task<Result<ProjectTransaction>> {
22007 self.update(cx, |project, cx| {
22008 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22009 })
22010 }
22011}
22012
22013fn snippet_completions(
22014 project: &Project,
22015 buffer: &Entity<Buffer>,
22016 buffer_position: text::Anchor,
22017 cx: &mut App,
22018) -> Task<Result<CompletionResponse>> {
22019 let languages = buffer.read(cx).languages_at(buffer_position);
22020 let snippet_store = project.snippets().read(cx);
22021
22022 let scopes: Vec<_> = languages
22023 .iter()
22024 .filter_map(|language| {
22025 let language_name = language.lsp_id();
22026 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22027
22028 if snippets.is_empty() {
22029 None
22030 } else {
22031 Some((language.default_scope(), snippets))
22032 }
22033 })
22034 .collect();
22035
22036 if scopes.is_empty() {
22037 return Task::ready(Ok(CompletionResponse {
22038 completions: vec![],
22039 is_incomplete: false,
22040 }));
22041 }
22042
22043 let snapshot = buffer.read(cx).text_snapshot();
22044 let chars: String = snapshot
22045 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22046 .collect();
22047 let executor = cx.background_executor().clone();
22048
22049 cx.background_spawn(async move {
22050 let mut is_incomplete = false;
22051 let mut completions: Vec<Completion> = Vec::new();
22052 for (scope, snippets) in scopes.into_iter() {
22053 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22054 let mut last_word = chars
22055 .chars()
22056 .take_while(|c| classifier.is_word(*c))
22057 .collect::<String>();
22058 last_word = last_word.chars().rev().collect();
22059
22060 if last_word.is_empty() {
22061 return Ok(CompletionResponse {
22062 completions: vec![],
22063 is_incomplete: true,
22064 });
22065 }
22066
22067 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22068 let to_lsp = |point: &text::Anchor| {
22069 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22070 point_to_lsp(end)
22071 };
22072 let lsp_end = to_lsp(&buffer_position);
22073
22074 let candidates = snippets
22075 .iter()
22076 .enumerate()
22077 .flat_map(|(ix, snippet)| {
22078 snippet
22079 .prefix
22080 .iter()
22081 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22082 })
22083 .collect::<Vec<StringMatchCandidate>>();
22084
22085 const MAX_RESULTS: usize = 100;
22086 let mut matches = fuzzy::match_strings(
22087 &candidates,
22088 &last_word,
22089 last_word.chars().any(|c| c.is_uppercase()),
22090 true,
22091 MAX_RESULTS,
22092 &Default::default(),
22093 executor.clone(),
22094 )
22095 .await;
22096
22097 if matches.len() >= MAX_RESULTS {
22098 is_incomplete = true;
22099 }
22100
22101 // Remove all candidates where the query's start does not match the start of any word in the candidate
22102 if let Some(query_start) = last_word.chars().next() {
22103 matches.retain(|string_match| {
22104 split_words(&string_match.string).any(|word| {
22105 // Check that the first codepoint of the word as lowercase matches the first
22106 // codepoint of the query as lowercase
22107 word.chars()
22108 .flat_map(|codepoint| codepoint.to_lowercase())
22109 .zip(query_start.to_lowercase())
22110 .all(|(word_cp, query_cp)| word_cp == query_cp)
22111 })
22112 });
22113 }
22114
22115 let matched_strings = matches
22116 .into_iter()
22117 .map(|m| m.string)
22118 .collect::<HashSet<_>>();
22119
22120 completions.extend(snippets.iter().filter_map(|snippet| {
22121 let matching_prefix = snippet
22122 .prefix
22123 .iter()
22124 .find(|prefix| matched_strings.contains(*prefix))?;
22125 let start = as_offset - last_word.len();
22126 let start = snapshot.anchor_before(start);
22127 let range = start..buffer_position;
22128 let lsp_start = to_lsp(&start);
22129 let lsp_range = lsp::Range {
22130 start: lsp_start,
22131 end: lsp_end,
22132 };
22133 Some(Completion {
22134 replace_range: range,
22135 new_text: snippet.body.clone(),
22136 source: CompletionSource::Lsp {
22137 insert_range: None,
22138 server_id: LanguageServerId(usize::MAX),
22139 resolved: true,
22140 lsp_completion: Box::new(lsp::CompletionItem {
22141 label: snippet.prefix.first().unwrap().clone(),
22142 kind: Some(CompletionItemKind::SNIPPET),
22143 label_details: snippet.description.as_ref().map(|description| {
22144 lsp::CompletionItemLabelDetails {
22145 detail: Some(description.clone()),
22146 description: None,
22147 }
22148 }),
22149 insert_text_format: Some(InsertTextFormat::SNIPPET),
22150 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22151 lsp::InsertReplaceEdit {
22152 new_text: snippet.body.clone(),
22153 insert: lsp_range,
22154 replace: lsp_range,
22155 },
22156 )),
22157 filter_text: Some(snippet.body.clone()),
22158 sort_text: Some(char::MAX.to_string()),
22159 ..lsp::CompletionItem::default()
22160 }),
22161 lsp_defaults: None,
22162 },
22163 label: CodeLabel {
22164 text: matching_prefix.clone(),
22165 runs: Vec::new(),
22166 filter_range: 0..matching_prefix.len(),
22167 },
22168 icon_path: None,
22169 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22170 single_line: snippet.name.clone().into(),
22171 plain_text: snippet
22172 .description
22173 .clone()
22174 .map(|description| description.into()),
22175 }),
22176 insert_text_mode: None,
22177 confirm: None,
22178 })
22179 }))
22180 }
22181
22182 Ok(CompletionResponse {
22183 completions,
22184 is_incomplete,
22185 })
22186 })
22187}
22188
22189impl CompletionProvider for Entity<Project> {
22190 fn completions(
22191 &self,
22192 _excerpt_id: ExcerptId,
22193 buffer: &Entity<Buffer>,
22194 buffer_position: text::Anchor,
22195 options: CompletionContext,
22196 _window: &mut Window,
22197 cx: &mut Context<Editor>,
22198 ) -> Task<Result<Vec<CompletionResponse>>> {
22199 self.update(cx, |project, cx| {
22200 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22201 let project_completions = project.completions(buffer, buffer_position, options, cx);
22202 cx.background_spawn(async move {
22203 let mut responses = project_completions.await?;
22204 let snippets = snippets.await?;
22205 if !snippets.completions.is_empty() {
22206 responses.push(snippets);
22207 }
22208 Ok(responses)
22209 })
22210 })
22211 }
22212
22213 fn resolve_completions(
22214 &self,
22215 buffer: Entity<Buffer>,
22216 completion_indices: Vec<usize>,
22217 completions: Rc<RefCell<Box<[Completion]>>>,
22218 cx: &mut Context<Editor>,
22219 ) -> Task<Result<bool>> {
22220 self.update(cx, |project, cx| {
22221 project.lsp_store().update(cx, |lsp_store, cx| {
22222 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22223 })
22224 })
22225 }
22226
22227 fn apply_additional_edits_for_completion(
22228 &self,
22229 buffer: Entity<Buffer>,
22230 completions: Rc<RefCell<Box<[Completion]>>>,
22231 completion_index: usize,
22232 push_to_history: bool,
22233 cx: &mut Context<Editor>,
22234 ) -> Task<Result<Option<language::Transaction>>> {
22235 self.update(cx, |project, cx| {
22236 project.lsp_store().update(cx, |lsp_store, cx| {
22237 lsp_store.apply_additional_edits_for_completion(
22238 buffer,
22239 completions,
22240 completion_index,
22241 push_to_history,
22242 cx,
22243 )
22244 })
22245 })
22246 }
22247
22248 fn is_completion_trigger(
22249 &self,
22250 buffer: &Entity<Buffer>,
22251 position: language::Anchor,
22252 text: &str,
22253 trigger_in_words: bool,
22254 menu_is_open: bool,
22255 cx: &mut Context<Editor>,
22256 ) -> bool {
22257 let mut chars = text.chars();
22258 let char = if let Some(char) = chars.next() {
22259 char
22260 } else {
22261 return false;
22262 };
22263 if chars.next().is_some() {
22264 return false;
22265 }
22266
22267 let buffer = buffer.read(cx);
22268 let snapshot = buffer.snapshot();
22269 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22270 return false;
22271 }
22272 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22273 if trigger_in_words && classifier.is_word(char) {
22274 return true;
22275 }
22276
22277 buffer.completion_triggers().contains(text)
22278 }
22279}
22280
22281impl SemanticsProvider for Entity<Project> {
22282 fn hover(
22283 &self,
22284 buffer: &Entity<Buffer>,
22285 position: text::Anchor,
22286 cx: &mut App,
22287 ) -> Option<Task<Vec<project::Hover>>> {
22288 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22289 }
22290
22291 fn document_highlights(
22292 &self,
22293 buffer: &Entity<Buffer>,
22294 position: text::Anchor,
22295 cx: &mut App,
22296 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22297 Some(self.update(cx, |project, cx| {
22298 project.document_highlights(buffer, position, cx)
22299 }))
22300 }
22301
22302 fn definitions(
22303 &self,
22304 buffer: &Entity<Buffer>,
22305 position: text::Anchor,
22306 kind: GotoDefinitionKind,
22307 cx: &mut App,
22308 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22309 Some(self.update(cx, |project, cx| match kind {
22310 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22311 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22312 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22313 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22314 }))
22315 }
22316
22317 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22318 self.update(cx, |project, cx| {
22319 if project
22320 .active_debug_session(cx)
22321 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22322 {
22323 return true;
22324 }
22325
22326 buffer.update(cx, |buffer, cx| {
22327 project.any_language_server_supports_inlay_hints(buffer, cx)
22328 })
22329 })
22330 }
22331
22332 fn inline_values(
22333 &self,
22334 buffer_handle: Entity<Buffer>,
22335 range: Range<text::Anchor>,
22336 cx: &mut App,
22337 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22338 self.update(cx, |project, cx| {
22339 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22340
22341 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22342 })
22343 }
22344
22345 fn inlay_hints(
22346 &self,
22347 buffer_handle: Entity<Buffer>,
22348 range: Range<text::Anchor>,
22349 cx: &mut App,
22350 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22351 Some(self.update(cx, |project, cx| {
22352 project.inlay_hints(buffer_handle, range, cx)
22353 }))
22354 }
22355
22356 fn resolve_inlay_hint(
22357 &self,
22358 hint: InlayHint,
22359 buffer_handle: Entity<Buffer>,
22360 server_id: LanguageServerId,
22361 cx: &mut App,
22362 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22363 Some(self.update(cx, |project, cx| {
22364 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22365 }))
22366 }
22367
22368 fn range_for_rename(
22369 &self,
22370 buffer: &Entity<Buffer>,
22371 position: text::Anchor,
22372 cx: &mut App,
22373 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22374 Some(self.update(cx, |project, cx| {
22375 let buffer = buffer.clone();
22376 let task = project.prepare_rename(buffer.clone(), position, cx);
22377 cx.spawn(async move |_, cx| {
22378 Ok(match task.await? {
22379 PrepareRenameResponse::Success(range) => Some(range),
22380 PrepareRenameResponse::InvalidPosition => None,
22381 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22382 // Fallback on using TreeSitter info to determine identifier range
22383 buffer.read_with(cx, |buffer, _| {
22384 let snapshot = buffer.snapshot();
22385 let (range, kind) = snapshot.surrounding_word(position, false);
22386 if kind != Some(CharKind::Word) {
22387 return None;
22388 }
22389 Some(
22390 snapshot.anchor_before(range.start)
22391 ..snapshot.anchor_after(range.end),
22392 )
22393 })?
22394 }
22395 })
22396 })
22397 }))
22398 }
22399
22400 fn perform_rename(
22401 &self,
22402 buffer: &Entity<Buffer>,
22403 position: text::Anchor,
22404 new_name: String,
22405 cx: &mut App,
22406 ) -> Option<Task<Result<ProjectTransaction>>> {
22407 Some(self.update(cx, |project, cx| {
22408 project.perform_rename(buffer.clone(), position, new_name, cx)
22409 }))
22410 }
22411}
22412
22413fn inlay_hint_settings(
22414 location: Anchor,
22415 snapshot: &MultiBufferSnapshot,
22416 cx: &mut Context<Editor>,
22417) -> InlayHintSettings {
22418 let file = snapshot.file_at(location);
22419 let language = snapshot.language_at(location).map(|l| l.name());
22420 language_settings(language, file, cx).inlay_hints
22421}
22422
22423fn consume_contiguous_rows(
22424 contiguous_row_selections: &mut Vec<Selection<Point>>,
22425 selection: &Selection<Point>,
22426 display_map: &DisplaySnapshot,
22427 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22428) -> (MultiBufferRow, MultiBufferRow) {
22429 contiguous_row_selections.push(selection.clone());
22430 let start_row = starting_row(selection, display_map);
22431 let mut end_row = ending_row(selection, display_map);
22432
22433 while let Some(next_selection) = selections.peek() {
22434 if next_selection.start.row <= end_row.0 {
22435 end_row = ending_row(next_selection, display_map);
22436 contiguous_row_selections.push(selections.next().unwrap().clone());
22437 } else {
22438 break;
22439 }
22440 }
22441 (start_row, end_row)
22442}
22443
22444fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22445 if selection.start.column > 0 {
22446 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22447 } else {
22448 MultiBufferRow(selection.start.row)
22449 }
22450}
22451
22452fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22453 if next_selection.end.column > 0 || next_selection.is_empty() {
22454 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22455 } else {
22456 MultiBufferRow(next_selection.end.row)
22457 }
22458}
22459
22460impl EditorSnapshot {
22461 pub fn remote_selections_in_range<'a>(
22462 &'a self,
22463 range: &'a Range<Anchor>,
22464 collaboration_hub: &dyn CollaborationHub,
22465 cx: &'a App,
22466 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22467 let participant_names = collaboration_hub.user_names(cx);
22468 let participant_indices = collaboration_hub.user_participant_indices(cx);
22469 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22470 let collaborators_by_replica_id = collaborators_by_peer_id
22471 .values()
22472 .map(|collaborator| (collaborator.replica_id, collaborator))
22473 .collect::<HashMap<_, _>>();
22474 self.buffer_snapshot
22475 .selections_in_range(range, false)
22476 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22477 if replica_id == AGENT_REPLICA_ID {
22478 Some(RemoteSelection {
22479 replica_id,
22480 selection,
22481 cursor_shape,
22482 line_mode,
22483 collaborator_id: CollaboratorId::Agent,
22484 user_name: Some("Agent".into()),
22485 color: cx.theme().players().agent(),
22486 })
22487 } else {
22488 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22489 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22490 let user_name = participant_names.get(&collaborator.user_id).cloned();
22491 Some(RemoteSelection {
22492 replica_id,
22493 selection,
22494 cursor_shape,
22495 line_mode,
22496 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22497 user_name,
22498 color: if let Some(index) = participant_index {
22499 cx.theme().players().color_for_participant(index.0)
22500 } else {
22501 cx.theme().players().absent()
22502 },
22503 })
22504 }
22505 })
22506 }
22507
22508 pub fn hunks_for_ranges(
22509 &self,
22510 ranges: impl IntoIterator<Item = Range<Point>>,
22511 ) -> Vec<MultiBufferDiffHunk> {
22512 let mut hunks = Vec::new();
22513 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22514 HashMap::default();
22515 for query_range in ranges {
22516 let query_rows =
22517 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22518 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22519 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22520 ) {
22521 // Include deleted hunks that are adjacent to the query range, because
22522 // otherwise they would be missed.
22523 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22524 if hunk.status().is_deleted() {
22525 intersects_range |= hunk.row_range.start == query_rows.end;
22526 intersects_range |= hunk.row_range.end == query_rows.start;
22527 }
22528 if intersects_range {
22529 if !processed_buffer_rows
22530 .entry(hunk.buffer_id)
22531 .or_default()
22532 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22533 {
22534 continue;
22535 }
22536 hunks.push(hunk);
22537 }
22538 }
22539 }
22540
22541 hunks
22542 }
22543
22544 fn display_diff_hunks_for_rows<'a>(
22545 &'a self,
22546 display_rows: Range<DisplayRow>,
22547 folded_buffers: &'a HashSet<BufferId>,
22548 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22549 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22550 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22551
22552 self.buffer_snapshot
22553 .diff_hunks_in_range(buffer_start..buffer_end)
22554 .filter_map(|hunk| {
22555 if folded_buffers.contains(&hunk.buffer_id) {
22556 return None;
22557 }
22558
22559 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22560 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22561
22562 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22563 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22564
22565 let display_hunk = if hunk_display_start.column() != 0 {
22566 DisplayDiffHunk::Folded {
22567 display_row: hunk_display_start.row(),
22568 }
22569 } else {
22570 let mut end_row = hunk_display_end.row();
22571 if hunk_display_end.column() > 0 {
22572 end_row.0 += 1;
22573 }
22574 let is_created_file = hunk.is_created_file();
22575 DisplayDiffHunk::Unfolded {
22576 status: hunk.status(),
22577 diff_base_byte_range: hunk.diff_base_byte_range,
22578 display_row_range: hunk_display_start.row()..end_row,
22579 multi_buffer_range: Anchor::range_in_buffer(
22580 hunk.excerpt_id,
22581 hunk.buffer_id,
22582 hunk.buffer_range,
22583 ),
22584 is_created_file,
22585 }
22586 };
22587
22588 Some(display_hunk)
22589 })
22590 }
22591
22592 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22593 self.display_snapshot.buffer_snapshot.language_at(position)
22594 }
22595
22596 pub fn is_focused(&self) -> bool {
22597 self.is_focused
22598 }
22599
22600 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22601 self.placeholder_text.as_ref()
22602 }
22603
22604 pub fn scroll_position(&self) -> gpui::Point<f32> {
22605 self.scroll_anchor.scroll_position(&self.display_snapshot)
22606 }
22607
22608 fn gutter_dimensions(
22609 &self,
22610 font_id: FontId,
22611 font_size: Pixels,
22612 max_line_number_width: Pixels,
22613 cx: &App,
22614 ) -> Option<GutterDimensions> {
22615 if !self.show_gutter {
22616 return None;
22617 }
22618
22619 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22620 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22621
22622 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22623 matches!(
22624 ProjectSettings::get_global(cx).git.git_gutter,
22625 Some(GitGutterSetting::TrackedFiles)
22626 )
22627 });
22628 let gutter_settings = EditorSettings::get_global(cx).gutter;
22629 let show_line_numbers = self
22630 .show_line_numbers
22631 .unwrap_or(gutter_settings.line_numbers);
22632 let line_gutter_width = if show_line_numbers {
22633 // Avoid flicker-like gutter resizes when the line number gains another digit by
22634 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22635 let min_width_for_number_on_gutter =
22636 ch_advance * gutter_settings.min_line_number_digits as f32;
22637 max_line_number_width.max(min_width_for_number_on_gutter)
22638 } else {
22639 0.0.into()
22640 };
22641
22642 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22643 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22644
22645 let git_blame_entries_width =
22646 self.git_blame_gutter_max_author_length
22647 .map(|max_author_length| {
22648 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22649 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22650
22651 /// The number of characters to dedicate to gaps and margins.
22652 const SPACING_WIDTH: usize = 4;
22653
22654 let max_char_count = max_author_length.min(renderer.max_author_length())
22655 + ::git::SHORT_SHA_LENGTH
22656 + MAX_RELATIVE_TIMESTAMP.len()
22657 + SPACING_WIDTH;
22658
22659 ch_advance * max_char_count
22660 });
22661
22662 let is_singleton = self.buffer_snapshot.is_singleton();
22663
22664 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22665 left_padding += if !is_singleton {
22666 ch_width * 4.0
22667 } else if show_runnables || show_breakpoints {
22668 ch_width * 3.0
22669 } else if show_git_gutter && show_line_numbers {
22670 ch_width * 2.0
22671 } else if show_git_gutter || show_line_numbers {
22672 ch_width
22673 } else {
22674 px(0.)
22675 };
22676
22677 let shows_folds = is_singleton && gutter_settings.folds;
22678
22679 let right_padding = if shows_folds && show_line_numbers {
22680 ch_width * 4.0
22681 } else if shows_folds || (!is_singleton && show_line_numbers) {
22682 ch_width * 3.0
22683 } else if show_line_numbers {
22684 ch_width
22685 } else {
22686 px(0.)
22687 };
22688
22689 Some(GutterDimensions {
22690 left_padding,
22691 right_padding,
22692 width: line_gutter_width + left_padding + right_padding,
22693 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22694 git_blame_entries_width,
22695 })
22696 }
22697
22698 pub fn render_crease_toggle(
22699 &self,
22700 buffer_row: MultiBufferRow,
22701 row_contains_cursor: bool,
22702 editor: Entity<Editor>,
22703 window: &mut Window,
22704 cx: &mut App,
22705 ) -> Option<AnyElement> {
22706 let folded = self.is_line_folded(buffer_row);
22707 let mut is_foldable = false;
22708
22709 if let Some(crease) = self
22710 .crease_snapshot
22711 .query_row(buffer_row, &self.buffer_snapshot)
22712 {
22713 is_foldable = true;
22714 match crease {
22715 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22716 if let Some(render_toggle) = render_toggle {
22717 let toggle_callback =
22718 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22719 if folded {
22720 editor.update(cx, |editor, cx| {
22721 editor.fold_at(buffer_row, window, cx)
22722 });
22723 } else {
22724 editor.update(cx, |editor, cx| {
22725 editor.unfold_at(buffer_row, window, cx)
22726 });
22727 }
22728 });
22729 return Some((render_toggle)(
22730 buffer_row,
22731 folded,
22732 toggle_callback,
22733 window,
22734 cx,
22735 ));
22736 }
22737 }
22738 }
22739 }
22740
22741 is_foldable |= self.starts_indent(buffer_row);
22742
22743 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22744 Some(
22745 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22746 .toggle_state(folded)
22747 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22748 if folded {
22749 this.unfold_at(buffer_row, window, cx);
22750 } else {
22751 this.fold_at(buffer_row, window, cx);
22752 }
22753 }))
22754 .into_any_element(),
22755 )
22756 } else {
22757 None
22758 }
22759 }
22760
22761 pub fn render_crease_trailer(
22762 &self,
22763 buffer_row: MultiBufferRow,
22764 window: &mut Window,
22765 cx: &mut App,
22766 ) -> Option<AnyElement> {
22767 let folded = self.is_line_folded(buffer_row);
22768 if let Crease::Inline { render_trailer, .. } = self
22769 .crease_snapshot
22770 .query_row(buffer_row, &self.buffer_snapshot)?
22771 {
22772 let render_trailer = render_trailer.as_ref()?;
22773 Some(render_trailer(buffer_row, folded, window, cx))
22774 } else {
22775 None
22776 }
22777 }
22778}
22779
22780impl Deref for EditorSnapshot {
22781 type Target = DisplaySnapshot;
22782
22783 fn deref(&self) -> &Self::Target {
22784 &self.display_snapshot
22785 }
22786}
22787
22788#[derive(Clone, Debug, PartialEq, Eq)]
22789pub enum EditorEvent {
22790 InputIgnored {
22791 text: Arc<str>,
22792 },
22793 InputHandled {
22794 utf16_range_to_replace: Option<Range<isize>>,
22795 text: Arc<str>,
22796 },
22797 ExcerptsAdded {
22798 buffer: Entity<Buffer>,
22799 predecessor: ExcerptId,
22800 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22801 },
22802 ExcerptsRemoved {
22803 ids: Vec<ExcerptId>,
22804 removed_buffer_ids: Vec<BufferId>,
22805 },
22806 BufferFoldToggled {
22807 ids: Vec<ExcerptId>,
22808 folded: bool,
22809 },
22810 ExcerptsEdited {
22811 ids: Vec<ExcerptId>,
22812 },
22813 ExcerptsExpanded {
22814 ids: Vec<ExcerptId>,
22815 },
22816 BufferEdited,
22817 Edited {
22818 transaction_id: clock::Lamport,
22819 },
22820 Reparsed(BufferId),
22821 Focused,
22822 FocusedIn,
22823 Blurred,
22824 DirtyChanged,
22825 Saved,
22826 TitleChanged,
22827 DiffBaseChanged,
22828 SelectionsChanged {
22829 local: bool,
22830 },
22831 ScrollPositionChanged {
22832 local: bool,
22833 autoscroll: bool,
22834 },
22835 Closed,
22836 TransactionUndone {
22837 transaction_id: clock::Lamport,
22838 },
22839 TransactionBegun {
22840 transaction_id: clock::Lamport,
22841 },
22842 Reloaded,
22843 CursorShapeChanged,
22844 BreadcrumbsChanged,
22845 PushedToNavHistory {
22846 anchor: Anchor,
22847 is_deactivate: bool,
22848 },
22849}
22850
22851impl EventEmitter<EditorEvent> for Editor {}
22852
22853impl Focusable for Editor {
22854 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22855 self.focus_handle.clone()
22856 }
22857}
22858
22859impl Render for Editor {
22860 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22861 let settings = ThemeSettings::get_global(cx);
22862
22863 let mut text_style = match self.mode {
22864 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
22865 color: cx.theme().colors().editor_foreground,
22866 font_family: settings.ui_font.family.clone(),
22867 font_features: settings.ui_font.features.clone(),
22868 font_fallbacks: settings.ui_font.fallbacks.clone(),
22869 font_size: rems(0.875).into(),
22870 font_weight: settings.ui_font.weight,
22871 line_height: relative(settings.buffer_line_height.value()),
22872 ..Default::default()
22873 },
22874 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22875 color: cx.theme().colors().editor_foreground,
22876 font_family: settings.buffer_font.family.clone(),
22877 font_features: settings.buffer_font.features.clone(),
22878 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22879 font_size: settings.buffer_font_size(cx).into(),
22880 font_weight: settings.buffer_font.weight,
22881 line_height: relative(settings.buffer_line_height.value()),
22882 ..Default::default()
22883 },
22884 };
22885 if let Some(text_style_refinement) = &self.text_style_refinement {
22886 text_style.refine(text_style_refinement)
22887 }
22888
22889 let background = match self.mode {
22890 EditorMode::SingleLine => cx.theme().system().transparent,
22891 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22892 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22893 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22894 };
22895
22896 EditorElement::new(
22897 &cx.entity(),
22898 EditorStyle {
22899 background,
22900 border: cx.theme().colors().border,
22901 local_player: cx.theme().players().local(),
22902 text: text_style,
22903 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22904 syntax: cx.theme().syntax().clone(),
22905 status: cx.theme().status().clone(),
22906 inlay_hints_style: make_inlay_hints_style(cx),
22907 edit_prediction_styles: make_suggestion_styles(cx),
22908 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22909 show_underlines: self.diagnostics_enabled(),
22910 },
22911 )
22912 }
22913}
22914
22915impl EntityInputHandler for Editor {
22916 fn text_for_range(
22917 &mut self,
22918 range_utf16: Range<usize>,
22919 adjusted_range: &mut Option<Range<usize>>,
22920 _: &mut Window,
22921 cx: &mut Context<Self>,
22922 ) -> Option<String> {
22923 let snapshot = self.buffer.read(cx).read(cx);
22924 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22925 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22926 if (start.0..end.0) != range_utf16 {
22927 adjusted_range.replace(start.0..end.0);
22928 }
22929 Some(snapshot.text_for_range(start..end).collect())
22930 }
22931
22932 fn selected_text_range(
22933 &mut self,
22934 ignore_disabled_input: bool,
22935 _: &mut Window,
22936 cx: &mut Context<Self>,
22937 ) -> Option<UTF16Selection> {
22938 // Prevent the IME menu from appearing when holding down an alphabetic key
22939 // while input is disabled.
22940 if !ignore_disabled_input && !self.input_enabled {
22941 return None;
22942 }
22943
22944 let selection = self.selections.newest::<OffsetUtf16>(cx);
22945 let range = selection.range();
22946
22947 Some(UTF16Selection {
22948 range: range.start.0..range.end.0,
22949 reversed: selection.reversed,
22950 })
22951 }
22952
22953 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22954 let snapshot = self.buffer.read(cx).read(cx);
22955 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22956 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22957 }
22958
22959 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22960 self.clear_highlights::<InputComposition>(cx);
22961 self.ime_transaction.take();
22962 }
22963
22964 fn replace_text_in_range(
22965 &mut self,
22966 range_utf16: Option<Range<usize>>,
22967 text: &str,
22968 window: &mut Window,
22969 cx: &mut Context<Self>,
22970 ) {
22971 if !self.input_enabled {
22972 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22973 return;
22974 }
22975
22976 self.transact(window, cx, |this, window, cx| {
22977 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22978 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22979 Some(this.selection_replacement_ranges(range_utf16, cx))
22980 } else {
22981 this.marked_text_ranges(cx)
22982 };
22983
22984 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22985 let newest_selection_id = this.selections.newest_anchor().id;
22986 this.selections
22987 .all::<OffsetUtf16>(cx)
22988 .iter()
22989 .zip(ranges_to_replace.iter())
22990 .find_map(|(selection, range)| {
22991 if selection.id == newest_selection_id {
22992 Some(
22993 (range.start.0 as isize - selection.head().0 as isize)
22994 ..(range.end.0 as isize - selection.head().0 as isize),
22995 )
22996 } else {
22997 None
22998 }
22999 })
23000 });
23001
23002 cx.emit(EditorEvent::InputHandled {
23003 utf16_range_to_replace: range_to_replace,
23004 text: text.into(),
23005 });
23006
23007 if let Some(new_selected_ranges) = new_selected_ranges {
23008 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23009 selections.select_ranges(new_selected_ranges)
23010 });
23011 this.backspace(&Default::default(), window, cx);
23012 }
23013
23014 this.handle_input(text, window, cx);
23015 });
23016
23017 if let Some(transaction) = self.ime_transaction {
23018 self.buffer.update(cx, |buffer, cx| {
23019 buffer.group_until_transaction(transaction, cx);
23020 });
23021 }
23022
23023 self.unmark_text(window, cx);
23024 }
23025
23026 fn replace_and_mark_text_in_range(
23027 &mut self,
23028 range_utf16: Option<Range<usize>>,
23029 text: &str,
23030 new_selected_range_utf16: Option<Range<usize>>,
23031 window: &mut Window,
23032 cx: &mut Context<Self>,
23033 ) {
23034 if !self.input_enabled {
23035 return;
23036 }
23037
23038 let transaction = self.transact(window, cx, |this, window, cx| {
23039 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23040 let snapshot = this.buffer.read(cx).read(cx);
23041 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23042 for marked_range in &mut marked_ranges {
23043 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23044 marked_range.start.0 += relative_range_utf16.start;
23045 marked_range.start =
23046 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23047 marked_range.end =
23048 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23049 }
23050 }
23051 Some(marked_ranges)
23052 } else if let Some(range_utf16) = range_utf16 {
23053 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23054 Some(this.selection_replacement_ranges(range_utf16, cx))
23055 } else {
23056 None
23057 };
23058
23059 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23060 let newest_selection_id = this.selections.newest_anchor().id;
23061 this.selections
23062 .all::<OffsetUtf16>(cx)
23063 .iter()
23064 .zip(ranges_to_replace.iter())
23065 .find_map(|(selection, range)| {
23066 if selection.id == newest_selection_id {
23067 Some(
23068 (range.start.0 as isize - selection.head().0 as isize)
23069 ..(range.end.0 as isize - selection.head().0 as isize),
23070 )
23071 } else {
23072 None
23073 }
23074 })
23075 });
23076
23077 cx.emit(EditorEvent::InputHandled {
23078 utf16_range_to_replace: range_to_replace,
23079 text: text.into(),
23080 });
23081
23082 if let Some(ranges) = ranges_to_replace {
23083 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23084 s.select_ranges(ranges)
23085 });
23086 }
23087
23088 let marked_ranges = {
23089 let snapshot = this.buffer.read(cx).read(cx);
23090 this.selections
23091 .disjoint_anchors()
23092 .iter()
23093 .map(|selection| {
23094 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23095 })
23096 .collect::<Vec<_>>()
23097 };
23098
23099 if text.is_empty() {
23100 this.unmark_text(window, cx);
23101 } else {
23102 this.highlight_text::<InputComposition>(
23103 marked_ranges.clone(),
23104 HighlightStyle {
23105 underline: Some(UnderlineStyle {
23106 thickness: px(1.),
23107 color: None,
23108 wavy: false,
23109 }),
23110 ..Default::default()
23111 },
23112 cx,
23113 );
23114 }
23115
23116 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23117 let use_autoclose = this.use_autoclose;
23118 let use_auto_surround = this.use_auto_surround;
23119 this.set_use_autoclose(false);
23120 this.set_use_auto_surround(false);
23121 this.handle_input(text, window, cx);
23122 this.set_use_autoclose(use_autoclose);
23123 this.set_use_auto_surround(use_auto_surround);
23124
23125 if let Some(new_selected_range) = new_selected_range_utf16 {
23126 let snapshot = this.buffer.read(cx).read(cx);
23127 let new_selected_ranges = marked_ranges
23128 .into_iter()
23129 .map(|marked_range| {
23130 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23131 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23132 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23133 snapshot.clip_offset_utf16(new_start, Bias::Left)
23134 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23135 })
23136 .collect::<Vec<_>>();
23137
23138 drop(snapshot);
23139 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23140 selections.select_ranges(new_selected_ranges)
23141 });
23142 }
23143 });
23144
23145 self.ime_transaction = self.ime_transaction.or(transaction);
23146 if let Some(transaction) = self.ime_transaction {
23147 self.buffer.update(cx, |buffer, cx| {
23148 buffer.group_until_transaction(transaction, cx);
23149 });
23150 }
23151
23152 if self.text_highlights::<InputComposition>(cx).is_none() {
23153 self.ime_transaction.take();
23154 }
23155 }
23156
23157 fn bounds_for_range(
23158 &mut self,
23159 range_utf16: Range<usize>,
23160 element_bounds: gpui::Bounds<Pixels>,
23161 window: &mut Window,
23162 cx: &mut Context<Self>,
23163 ) -> Option<gpui::Bounds<Pixels>> {
23164 let text_layout_details = self.text_layout_details(window);
23165 let CharacterDimensions {
23166 em_width,
23167 em_advance,
23168 line_height,
23169 } = self.character_dimensions(window);
23170
23171 let snapshot = self.snapshot(window, cx);
23172 let scroll_position = snapshot.scroll_position();
23173 let scroll_left = scroll_position.x * em_advance;
23174
23175 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23176 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23177 + self.gutter_dimensions.full_width();
23178 let y = line_height * (start.row().as_f32() - scroll_position.y);
23179
23180 Some(Bounds {
23181 origin: element_bounds.origin + point(x, y),
23182 size: size(em_width, line_height),
23183 })
23184 }
23185
23186 fn character_index_for_point(
23187 &mut self,
23188 point: gpui::Point<Pixels>,
23189 _window: &mut Window,
23190 _cx: &mut Context<Self>,
23191 ) -> Option<usize> {
23192 let position_map = self.last_position_map.as_ref()?;
23193 if !position_map.text_hitbox.contains(&point) {
23194 return None;
23195 }
23196 let display_point = position_map.point_for_position(point).previous_valid;
23197 let anchor = position_map
23198 .snapshot
23199 .display_point_to_anchor(display_point, Bias::Left);
23200 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23201 Some(utf16_offset.0)
23202 }
23203}
23204
23205trait SelectionExt {
23206 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23207 fn spanned_rows(
23208 &self,
23209 include_end_if_at_line_start: bool,
23210 map: &DisplaySnapshot,
23211 ) -> Range<MultiBufferRow>;
23212}
23213
23214impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23215 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23216 let start = self
23217 .start
23218 .to_point(&map.buffer_snapshot)
23219 .to_display_point(map);
23220 let end = self
23221 .end
23222 .to_point(&map.buffer_snapshot)
23223 .to_display_point(map);
23224 if self.reversed {
23225 end..start
23226 } else {
23227 start..end
23228 }
23229 }
23230
23231 fn spanned_rows(
23232 &self,
23233 include_end_if_at_line_start: bool,
23234 map: &DisplaySnapshot,
23235 ) -> Range<MultiBufferRow> {
23236 let start = self.start.to_point(&map.buffer_snapshot);
23237 let mut end = self.end.to_point(&map.buffer_snapshot);
23238 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23239 end.row -= 1;
23240 }
23241
23242 let buffer_start = map.prev_line_boundary(start).0;
23243 let buffer_end = map.next_line_boundary(end).0;
23244 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23245 }
23246}
23247
23248impl<T: InvalidationRegion> InvalidationStack<T> {
23249 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23250 where
23251 S: Clone + ToOffset,
23252 {
23253 while let Some(region) = self.last() {
23254 let all_selections_inside_invalidation_ranges =
23255 if selections.len() == region.ranges().len() {
23256 selections
23257 .iter()
23258 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23259 .all(|(selection, invalidation_range)| {
23260 let head = selection.head().to_offset(buffer);
23261 invalidation_range.start <= head && invalidation_range.end >= head
23262 })
23263 } else {
23264 false
23265 };
23266
23267 if all_selections_inside_invalidation_ranges {
23268 break;
23269 } else {
23270 self.pop();
23271 }
23272 }
23273 }
23274}
23275
23276impl<T> Default for InvalidationStack<T> {
23277 fn default() -> Self {
23278 Self(Default::default())
23279 }
23280}
23281
23282impl<T> Deref for InvalidationStack<T> {
23283 type Target = Vec<T>;
23284
23285 fn deref(&self) -> &Self::Target {
23286 &self.0
23287 }
23288}
23289
23290impl<T> DerefMut for InvalidationStack<T> {
23291 fn deref_mut(&mut self) -> &mut Self::Target {
23292 &mut self.0
23293 }
23294}
23295
23296impl InvalidationRegion for SnippetState {
23297 fn ranges(&self) -> &[Range<Anchor>] {
23298 &self.ranges[self.active_index]
23299 }
23300}
23301
23302fn edit_prediction_edit_text(
23303 current_snapshot: &BufferSnapshot,
23304 edits: &[(Range<Anchor>, String)],
23305 edit_preview: &EditPreview,
23306 include_deletions: bool,
23307 cx: &App,
23308) -> HighlightedText {
23309 let edits = edits
23310 .iter()
23311 .map(|(anchor, text)| {
23312 (
23313 anchor.start.text_anchor..anchor.end.text_anchor,
23314 text.clone(),
23315 )
23316 })
23317 .collect::<Vec<_>>();
23318
23319 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23320}
23321
23322fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23323 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23324 // Just show the raw edit text with basic styling
23325 let mut text = String::new();
23326 let mut highlights = Vec::new();
23327
23328 let insertion_highlight_style = HighlightStyle {
23329 color: Some(cx.theme().colors().text),
23330 ..Default::default()
23331 };
23332
23333 for (_, edit_text) in edits {
23334 let start_offset = text.len();
23335 text.push_str(edit_text);
23336 let end_offset = text.len();
23337
23338 if start_offset < end_offset {
23339 highlights.push((start_offset..end_offset, insertion_highlight_style));
23340 }
23341 }
23342
23343 HighlightedText {
23344 text: text.into(),
23345 highlights,
23346 }
23347}
23348
23349pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23350 match severity {
23351 lsp::DiagnosticSeverity::ERROR => colors.error,
23352 lsp::DiagnosticSeverity::WARNING => colors.warning,
23353 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23354 lsp::DiagnosticSeverity::HINT => colors.info,
23355 _ => colors.ignored,
23356 }
23357}
23358
23359pub fn styled_runs_for_code_label<'a>(
23360 label: &'a CodeLabel,
23361 syntax_theme: &'a theme::SyntaxTheme,
23362) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23363 let fade_out = HighlightStyle {
23364 fade_out: Some(0.35),
23365 ..Default::default()
23366 };
23367
23368 let mut prev_end = label.filter_range.end;
23369 label
23370 .runs
23371 .iter()
23372 .enumerate()
23373 .flat_map(move |(ix, (range, highlight_id))| {
23374 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23375 style
23376 } else {
23377 return Default::default();
23378 };
23379 let mut muted_style = style;
23380 muted_style.highlight(fade_out);
23381
23382 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23383 if range.start >= label.filter_range.end {
23384 if range.start > prev_end {
23385 runs.push((prev_end..range.start, fade_out));
23386 }
23387 runs.push((range.clone(), muted_style));
23388 } else if range.end <= label.filter_range.end {
23389 runs.push((range.clone(), style));
23390 } else {
23391 runs.push((range.start..label.filter_range.end, style));
23392 runs.push((label.filter_range.end..range.end, muted_style));
23393 }
23394 prev_end = cmp::max(prev_end, range.end);
23395
23396 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23397 runs.push((prev_end..label.text.len(), fade_out));
23398 }
23399
23400 runs
23401 })
23402}
23403
23404pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23405 let mut prev_index = 0;
23406 let mut prev_codepoint: Option<char> = None;
23407 text.char_indices()
23408 .chain([(text.len(), '\0')])
23409 .filter_map(move |(index, codepoint)| {
23410 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23411 let is_boundary = index == text.len()
23412 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23413 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23414 if is_boundary {
23415 let chunk = &text[prev_index..index];
23416 prev_index = index;
23417 Some(chunk)
23418 } else {
23419 None
23420 }
23421 })
23422}
23423
23424pub trait RangeToAnchorExt: Sized {
23425 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23426
23427 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23428 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23429 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23430 }
23431}
23432
23433impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23434 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23435 let start_offset = self.start.to_offset(snapshot);
23436 let end_offset = self.end.to_offset(snapshot);
23437 if start_offset == end_offset {
23438 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23439 } else {
23440 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23441 }
23442 }
23443}
23444
23445pub trait RowExt {
23446 fn as_f32(&self) -> f32;
23447
23448 fn next_row(&self) -> Self;
23449
23450 fn previous_row(&self) -> Self;
23451
23452 fn minus(&self, other: Self) -> u32;
23453}
23454
23455impl RowExt for DisplayRow {
23456 fn as_f32(&self) -> f32 {
23457 self.0 as f32
23458 }
23459
23460 fn next_row(&self) -> Self {
23461 Self(self.0 + 1)
23462 }
23463
23464 fn previous_row(&self) -> Self {
23465 Self(self.0.saturating_sub(1))
23466 }
23467
23468 fn minus(&self, other: Self) -> u32 {
23469 self.0 - other.0
23470 }
23471}
23472
23473impl RowExt for MultiBufferRow {
23474 fn as_f32(&self) -> f32 {
23475 self.0 as f32
23476 }
23477
23478 fn next_row(&self) -> Self {
23479 Self(self.0 + 1)
23480 }
23481
23482 fn previous_row(&self) -> Self {
23483 Self(self.0.saturating_sub(1))
23484 }
23485
23486 fn minus(&self, other: Self) -> u32 {
23487 self.0 - other.0
23488 }
23489}
23490
23491trait RowRangeExt {
23492 type Row;
23493
23494 fn len(&self) -> usize;
23495
23496 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23497}
23498
23499impl RowRangeExt for Range<MultiBufferRow> {
23500 type Row = MultiBufferRow;
23501
23502 fn len(&self) -> usize {
23503 (self.end.0 - self.start.0) as usize
23504 }
23505
23506 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23507 (self.start.0..self.end.0).map(MultiBufferRow)
23508 }
23509}
23510
23511impl RowRangeExt for Range<DisplayRow> {
23512 type Row = DisplayRow;
23513
23514 fn len(&self) -> usize {
23515 (self.end.0 - self.start.0) as usize
23516 }
23517
23518 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23519 (self.start.0..self.end.0).map(DisplayRow)
23520 }
23521}
23522
23523/// If select range has more than one line, we
23524/// just point the cursor to range.start.
23525fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23526 if range.start.row == range.end.row {
23527 range
23528 } else {
23529 range.start..range.start
23530 }
23531}
23532pub struct KillRing(ClipboardItem);
23533impl Global for KillRing {}
23534
23535const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23536
23537enum BreakpointPromptEditAction {
23538 Log,
23539 Condition,
23540 HitCondition,
23541}
23542
23543struct BreakpointPromptEditor {
23544 pub(crate) prompt: Entity<Editor>,
23545 editor: WeakEntity<Editor>,
23546 breakpoint_anchor: Anchor,
23547 breakpoint: Breakpoint,
23548 edit_action: BreakpointPromptEditAction,
23549 block_ids: HashSet<CustomBlockId>,
23550 editor_margins: Arc<Mutex<EditorMargins>>,
23551 _subscriptions: Vec<Subscription>,
23552}
23553
23554impl BreakpointPromptEditor {
23555 const MAX_LINES: u8 = 4;
23556
23557 fn new(
23558 editor: WeakEntity<Editor>,
23559 breakpoint_anchor: Anchor,
23560 breakpoint: Breakpoint,
23561 edit_action: BreakpointPromptEditAction,
23562 window: &mut Window,
23563 cx: &mut Context<Self>,
23564 ) -> Self {
23565 let base_text = match edit_action {
23566 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23567 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23568 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23569 }
23570 .map(|msg| msg.to_string())
23571 .unwrap_or_default();
23572
23573 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23574 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23575
23576 let prompt = cx.new(|cx| {
23577 let mut prompt = Editor::new(
23578 EditorMode::AutoHeight {
23579 min_lines: 1,
23580 max_lines: Some(Self::MAX_LINES as usize),
23581 },
23582 buffer,
23583 None,
23584 window,
23585 cx,
23586 );
23587 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23588 prompt.set_show_cursor_when_unfocused(false, cx);
23589 prompt.set_placeholder_text(
23590 match edit_action {
23591 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23592 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23593 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23594 },
23595 cx,
23596 );
23597
23598 prompt
23599 });
23600
23601 Self {
23602 prompt,
23603 editor,
23604 breakpoint_anchor,
23605 breakpoint,
23606 edit_action,
23607 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23608 block_ids: Default::default(),
23609 _subscriptions: vec![],
23610 }
23611 }
23612
23613 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23614 self.block_ids.extend(block_ids)
23615 }
23616
23617 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23618 if let Some(editor) = self.editor.upgrade() {
23619 let message = self
23620 .prompt
23621 .read(cx)
23622 .buffer
23623 .read(cx)
23624 .as_singleton()
23625 .expect("A multi buffer in breakpoint prompt isn't possible")
23626 .read(cx)
23627 .as_rope()
23628 .to_string();
23629
23630 editor.update(cx, |editor, cx| {
23631 editor.edit_breakpoint_at_anchor(
23632 self.breakpoint_anchor,
23633 self.breakpoint.clone(),
23634 match self.edit_action {
23635 BreakpointPromptEditAction::Log => {
23636 BreakpointEditAction::EditLogMessage(message.into())
23637 }
23638 BreakpointPromptEditAction::Condition => {
23639 BreakpointEditAction::EditCondition(message.into())
23640 }
23641 BreakpointPromptEditAction::HitCondition => {
23642 BreakpointEditAction::EditHitCondition(message.into())
23643 }
23644 },
23645 cx,
23646 );
23647
23648 editor.remove_blocks(self.block_ids.clone(), None, cx);
23649 cx.focus_self(window);
23650 });
23651 }
23652 }
23653
23654 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23655 self.editor
23656 .update(cx, |editor, cx| {
23657 editor.remove_blocks(self.block_ids.clone(), None, cx);
23658 window.focus(&editor.focus_handle);
23659 })
23660 .log_err();
23661 }
23662
23663 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23664 let settings = ThemeSettings::get_global(cx);
23665 let text_style = TextStyle {
23666 color: if self.prompt.read(cx).read_only(cx) {
23667 cx.theme().colors().text_disabled
23668 } else {
23669 cx.theme().colors().text
23670 },
23671 font_family: settings.buffer_font.family.clone(),
23672 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23673 font_size: settings.buffer_font_size(cx).into(),
23674 font_weight: settings.buffer_font.weight,
23675 line_height: relative(settings.buffer_line_height.value()),
23676 ..Default::default()
23677 };
23678 EditorElement::new(
23679 &self.prompt,
23680 EditorStyle {
23681 background: cx.theme().colors().editor_background,
23682 local_player: cx.theme().players().local(),
23683 text: text_style,
23684 ..Default::default()
23685 },
23686 )
23687 }
23688}
23689
23690impl Render for BreakpointPromptEditor {
23691 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23692 let editor_margins = *self.editor_margins.lock();
23693 let gutter_dimensions = editor_margins.gutter;
23694 h_flex()
23695 .key_context("Editor")
23696 .bg(cx.theme().colors().editor_background)
23697 .border_y_1()
23698 .border_color(cx.theme().status().info_border)
23699 .size_full()
23700 .py(window.line_height() / 2.5)
23701 .on_action(cx.listener(Self::confirm))
23702 .on_action(cx.listener(Self::cancel))
23703 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23704 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23705 }
23706}
23707
23708impl Focusable for BreakpointPromptEditor {
23709 fn focus_handle(&self, cx: &App) -> FocusHandle {
23710 self.prompt.focus_handle(cx)
23711 }
23712}
23713
23714fn all_edits_insertions_or_deletions(
23715 edits: &Vec<(Range<Anchor>, String)>,
23716 snapshot: &MultiBufferSnapshot,
23717) -> bool {
23718 let mut all_insertions = true;
23719 let mut all_deletions = true;
23720
23721 for (range, new_text) in edits.iter() {
23722 let range_is_empty = range.to_offset(snapshot).is_empty();
23723 let text_is_empty = new_text.is_empty();
23724
23725 if range_is_empty != text_is_empty {
23726 if range_is_empty {
23727 all_deletions = false;
23728 } else {
23729 all_insertions = false;
23730 }
23731 } else {
23732 return false;
23733 }
23734
23735 if !all_insertions && !all_deletions {
23736 return false;
23737 }
23738 }
23739 all_insertions || all_deletions
23740}
23741
23742struct MissingEditPredictionKeybindingTooltip;
23743
23744impl Render for MissingEditPredictionKeybindingTooltip {
23745 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23746 ui::tooltip_container(window, cx, |container, _, cx| {
23747 container
23748 .flex_shrink_0()
23749 .max_w_80()
23750 .min_h(rems_from_px(124.))
23751 .justify_between()
23752 .child(
23753 v_flex()
23754 .flex_1()
23755 .text_ui_sm(cx)
23756 .child(Label::new("Conflict with Accept Keybinding"))
23757 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23758 )
23759 .child(
23760 h_flex()
23761 .pb_1()
23762 .gap_1()
23763 .items_end()
23764 .w_full()
23765 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23766 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23767 }))
23768 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23769 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23770 })),
23771 )
23772 })
23773 }
23774}
23775
23776#[derive(Debug, Clone, Copy, PartialEq)]
23777pub struct LineHighlight {
23778 pub background: Background,
23779 pub border: Option<gpui::Hsla>,
23780 pub include_gutter: bool,
23781 pub type_id: Option<TypeId>,
23782}
23783
23784struct LineManipulationResult {
23785 pub new_text: String,
23786 pub line_count_before: usize,
23787 pub line_count_after: usize,
23788}
23789
23790fn render_diff_hunk_controls(
23791 row: u32,
23792 status: &DiffHunkStatus,
23793 hunk_range: Range<Anchor>,
23794 is_created_file: bool,
23795 line_height: Pixels,
23796 editor: &Entity<Editor>,
23797 _window: &mut Window,
23798 cx: &mut App,
23799) -> AnyElement {
23800 h_flex()
23801 .h(line_height)
23802 .mr_1()
23803 .gap_1()
23804 .px_0p5()
23805 .pb_1()
23806 .border_x_1()
23807 .border_b_1()
23808 .border_color(cx.theme().colors().border_variant)
23809 .rounded_b_lg()
23810 .bg(cx.theme().colors().editor_background)
23811 .gap_1()
23812 .block_mouse_except_scroll()
23813 .shadow_md()
23814 .child(if status.has_secondary_hunk() {
23815 Button::new(("stage", row as u64), "Stage")
23816 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23817 .tooltip({
23818 let focus_handle = editor.focus_handle(cx);
23819 move |window, cx| {
23820 Tooltip::for_action_in(
23821 "Stage Hunk",
23822 &::git::ToggleStaged,
23823 &focus_handle,
23824 window,
23825 cx,
23826 )
23827 }
23828 })
23829 .on_click({
23830 let editor = editor.clone();
23831 move |_event, _window, cx| {
23832 editor.update(cx, |editor, cx| {
23833 editor.stage_or_unstage_diff_hunks(
23834 true,
23835 vec![hunk_range.start..hunk_range.start],
23836 cx,
23837 );
23838 });
23839 }
23840 })
23841 } else {
23842 Button::new(("unstage", row as u64), "Unstage")
23843 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23844 .tooltip({
23845 let focus_handle = editor.focus_handle(cx);
23846 move |window, cx| {
23847 Tooltip::for_action_in(
23848 "Unstage Hunk",
23849 &::git::ToggleStaged,
23850 &focus_handle,
23851 window,
23852 cx,
23853 )
23854 }
23855 })
23856 .on_click({
23857 let editor = editor.clone();
23858 move |_event, _window, cx| {
23859 editor.update(cx, |editor, cx| {
23860 editor.stage_or_unstage_diff_hunks(
23861 false,
23862 vec![hunk_range.start..hunk_range.start],
23863 cx,
23864 );
23865 });
23866 }
23867 })
23868 })
23869 .child(
23870 Button::new(("restore", row as u64), "Restore")
23871 .tooltip({
23872 let focus_handle = editor.focus_handle(cx);
23873 move |window, cx| {
23874 Tooltip::for_action_in(
23875 "Restore Hunk",
23876 &::git::Restore,
23877 &focus_handle,
23878 window,
23879 cx,
23880 )
23881 }
23882 })
23883 .on_click({
23884 let editor = editor.clone();
23885 move |_event, window, cx| {
23886 editor.update(cx, |editor, cx| {
23887 let snapshot = editor.snapshot(window, cx);
23888 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23889 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23890 });
23891 }
23892 })
23893 .disabled(is_created_file),
23894 )
23895 .when(
23896 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23897 |el| {
23898 el.child(
23899 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23900 .shape(IconButtonShape::Square)
23901 .icon_size(IconSize::Small)
23902 // .disabled(!has_multiple_hunks)
23903 .tooltip({
23904 let focus_handle = editor.focus_handle(cx);
23905 move |window, cx| {
23906 Tooltip::for_action_in(
23907 "Next Hunk",
23908 &GoToHunk,
23909 &focus_handle,
23910 window,
23911 cx,
23912 )
23913 }
23914 })
23915 .on_click({
23916 let editor = editor.clone();
23917 move |_event, window, cx| {
23918 editor.update(cx, |editor, cx| {
23919 let snapshot = editor.snapshot(window, cx);
23920 let position =
23921 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23922 editor.go_to_hunk_before_or_after_position(
23923 &snapshot,
23924 position,
23925 Direction::Next,
23926 window,
23927 cx,
23928 );
23929 editor.expand_selected_diff_hunks(cx);
23930 });
23931 }
23932 }),
23933 )
23934 .child(
23935 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23936 .shape(IconButtonShape::Square)
23937 .icon_size(IconSize::Small)
23938 // .disabled(!has_multiple_hunks)
23939 .tooltip({
23940 let focus_handle = editor.focus_handle(cx);
23941 move |window, cx| {
23942 Tooltip::for_action_in(
23943 "Previous Hunk",
23944 &GoToPreviousHunk,
23945 &focus_handle,
23946 window,
23947 cx,
23948 )
23949 }
23950 })
23951 .on_click({
23952 let editor = editor.clone();
23953 move |_event, window, cx| {
23954 editor.update(cx, |editor, cx| {
23955 let snapshot = editor.snapshot(window, cx);
23956 let point =
23957 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23958 editor.go_to_hunk_before_or_after_position(
23959 &snapshot,
23960 point,
23961 Direction::Prev,
23962 window,
23963 cx,
23964 );
23965 editor.expand_selected_diff_hunks(cx);
23966 });
23967 }
23968 }),
23969 )
23970 },
23971 )
23972 .into_any_element()
23973}