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 match *self {
786 Self::Disabled => true,
787 _ => false,
788 }
789 }
790
791 fn settings_visibility(&self) -> bool {
792 match *self {
793 Self::Enabled {
794 setting_configuration,
795 ..
796 } => setting_configuration,
797 _ => false,
798 }
799 }
800
801 fn visible(&self) -> bool {
802 match *self {
803 Self::Enabled {
804 setting_configuration,
805 toggle_override,
806 } => setting_configuration ^ toggle_override,
807 _ => false,
808 }
809 }
810
811 fn toggle_visibility(&self) -> Self {
812 match *self {
813 Self::Enabled {
814 toggle_override,
815 setting_configuration,
816 } => Self::Enabled {
817 setting_configuration,
818 toggle_override: !toggle_override,
819 },
820 Self::Disabled => Self::Disabled,
821 }
822 }
823}
824
825#[derive(Clone, Debug)]
826struct RunnableTasks {
827 templates: Vec<(TaskSourceKind, TaskTemplate)>,
828 offset: multi_buffer::Anchor,
829 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
830 column: u32,
831 // Values of all named captures, including those starting with '_'
832 extra_variables: HashMap<String, String>,
833 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
834 context_range: Range<BufferOffset>,
835}
836
837impl RunnableTasks {
838 fn resolve<'a>(
839 &'a self,
840 cx: &'a task::TaskContext,
841 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
842 self.templates.iter().filter_map(|(kind, template)| {
843 template
844 .resolve_task(&kind.to_id_base(), cx)
845 .map(|task| (kind.clone(), task))
846 })
847 }
848}
849
850#[derive(Clone)]
851pub struct ResolvedTasks {
852 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
853 position: Anchor,
854}
855
856#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
857struct BufferOffset(usize);
858
859// Addons allow storing per-editor state in other crates (e.g. Vim)
860pub trait Addon: 'static {
861 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
862
863 fn render_buffer_header_controls(
864 &self,
865 _: &ExcerptInfo,
866 _: &Window,
867 _: &App,
868 ) -> Option<AnyElement> {
869 None
870 }
871
872 fn to_any(&self) -> &dyn std::any::Any;
873
874 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
875 None
876 }
877}
878
879struct ChangeLocation {
880 current: Option<Vec<Anchor>>,
881 original: Vec<Anchor>,
882}
883impl ChangeLocation {
884 fn locations(&self) -> &[Anchor] {
885 self.current.as_ref().unwrap_or(&self.original)
886 }
887}
888
889/// A set of caret positions, registered when the editor was edited.
890pub struct ChangeList {
891 changes: Vec<ChangeLocation>,
892 /// Currently "selected" change.
893 position: Option<usize>,
894}
895
896impl ChangeList {
897 pub fn new() -> Self {
898 Self {
899 changes: Vec::new(),
900 position: None,
901 }
902 }
903
904 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
905 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
906 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
907 if self.changes.is_empty() {
908 return None;
909 }
910
911 let prev = self.position.unwrap_or(self.changes.len());
912 let next = if direction == Direction::Prev {
913 prev.saturating_sub(count)
914 } else {
915 (prev + count).min(self.changes.len() - 1)
916 };
917 self.position = Some(next);
918 self.changes.get(next).map(|change| change.locations())
919 }
920
921 /// Adds a new change to the list, resetting the change list position.
922 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
923 self.position.take();
924 if let Some(last) = self.changes.last_mut()
925 && group
926 {
927 last.current = Some(new_positions)
928 } else {
929 self.changes.push(ChangeLocation {
930 original: new_positions,
931 current: None,
932 });
933 }
934 }
935
936 pub fn last(&self) -> Option<&[Anchor]> {
937 self.changes.last().map(|change| change.locations())
938 }
939
940 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
941 self.changes.last().map(|change| change.original.as_slice())
942 }
943
944 pub fn invert_last_group(&mut self) {
945 if let Some(last) = self.changes.last_mut()
946 && let Some(current) = last.current.as_mut()
947 {
948 mem::swap(&mut last.original, current);
949 }
950 }
951}
952
953#[derive(Clone)]
954struct InlineBlamePopoverState {
955 scroll_handle: ScrollHandle,
956 commit_message: Option<ParsedCommitMessage>,
957 markdown: Entity<Markdown>,
958}
959
960struct InlineBlamePopover {
961 position: gpui::Point<Pixels>,
962 hide_task: Option<Task<()>>,
963 popover_bounds: Option<Bounds<Pixels>>,
964 popover_state: InlineBlamePopoverState,
965 keyboard_grace: bool,
966}
967
968enum SelectionDragState {
969 /// State when no drag related activity is detected.
970 None,
971 /// State when the mouse is down on a selection that is about to be dragged.
972 ReadyToDrag {
973 selection: Selection<Anchor>,
974 click_position: gpui::Point<Pixels>,
975 mouse_down_time: Instant,
976 },
977 /// State when the mouse is dragging the selection in the editor.
978 Dragging {
979 selection: Selection<Anchor>,
980 drop_cursor: Selection<Anchor>,
981 hide_drop_cursor: bool,
982 },
983}
984
985enum ColumnarSelectionState {
986 FromMouse {
987 selection_tail: Anchor,
988 display_point: Option<DisplayPoint>,
989 },
990 FromSelection {
991 selection_tail: Anchor,
992 },
993}
994
995/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
996/// a breakpoint on them.
997#[derive(Clone, Copy, Debug, PartialEq, Eq)]
998struct PhantomBreakpointIndicator {
999 display_row: DisplayRow,
1000 /// There's a small debounce between hovering over the line and showing the indicator.
1001 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1002 is_active: bool,
1003 collides_with_existing_breakpoint: bool,
1004}
1005
1006/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1007///
1008/// See the [module level documentation](self) for more information.
1009pub struct Editor {
1010 focus_handle: FocusHandle,
1011 last_focused_descendant: Option<WeakFocusHandle>,
1012 /// The text buffer being edited
1013 buffer: Entity<MultiBuffer>,
1014 /// Map of how text in the buffer should be displayed.
1015 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1016 pub display_map: Entity<DisplayMap>,
1017 pub selections: SelectionsCollection,
1018 pub scroll_manager: ScrollManager,
1019 /// When inline assist editors are linked, they all render cursors because
1020 /// typing enters text into each of them, even the ones that aren't focused.
1021 pub(crate) show_cursor_when_unfocused: bool,
1022 columnar_selection_state: Option<ColumnarSelectionState>,
1023 add_selections_state: Option<AddSelectionsState>,
1024 select_next_state: Option<SelectNextState>,
1025 select_prev_state: Option<SelectNextState>,
1026 selection_history: SelectionHistory,
1027 defer_selection_effects: bool,
1028 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1029 autoclose_regions: Vec<AutocloseRegion>,
1030 snippet_stack: InvalidationStack<SnippetState>,
1031 select_syntax_node_history: SelectSyntaxNodeHistory,
1032 ime_transaction: Option<TransactionId>,
1033 pub diagnostics_max_severity: DiagnosticSeverity,
1034 active_diagnostics: ActiveDiagnostic,
1035 show_inline_diagnostics: bool,
1036 inline_diagnostics_update: Task<()>,
1037 inline_diagnostics_enabled: bool,
1038 diagnostics_enabled: bool,
1039 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1040 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1041 hard_wrap: Option<usize>,
1042 project: Option<Entity<Project>>,
1043 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1044 completion_provider: Option<Rc<dyn CompletionProvider>>,
1045 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1046 blink_manager: Entity<BlinkManager>,
1047 show_cursor_names: bool,
1048 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1049 pub show_local_selections: bool,
1050 mode: EditorMode,
1051 show_breadcrumbs: bool,
1052 show_gutter: bool,
1053 show_scrollbars: ScrollbarAxes,
1054 minimap_visibility: MinimapVisibility,
1055 offset_content: bool,
1056 disable_expand_excerpt_buttons: bool,
1057 show_line_numbers: Option<bool>,
1058 use_relative_line_numbers: Option<bool>,
1059 show_git_diff_gutter: Option<bool>,
1060 show_code_actions: Option<bool>,
1061 show_runnables: Option<bool>,
1062 show_breakpoints: Option<bool>,
1063 show_wrap_guides: Option<bool>,
1064 show_indent_guides: Option<bool>,
1065 placeholder_text: Option<Arc<str>>,
1066 highlight_order: usize,
1067 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1068 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1069 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1070 scrollbar_marker_state: ScrollbarMarkerState,
1071 active_indent_guides_state: ActiveIndentGuidesState,
1072 nav_history: Option<ItemNavHistory>,
1073 context_menu: RefCell<Option<CodeContextMenu>>,
1074 context_menu_options: Option<ContextMenuOptions>,
1075 mouse_context_menu: Option<MouseContextMenu>,
1076 completion_tasks: Vec<(CompletionId, Task<()>)>,
1077 inline_blame_popover: Option<InlineBlamePopover>,
1078 inline_blame_popover_show_task: Option<Task<()>>,
1079 signature_help_state: SignatureHelpState,
1080 auto_signature_help: Option<bool>,
1081 find_all_references_task_sources: Vec<Anchor>,
1082 next_completion_id: CompletionId,
1083 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1084 code_actions_task: Option<Task<Result<()>>>,
1085 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1086 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1087 document_highlights_task: Option<Task<()>>,
1088 linked_editing_range_task: Option<Task<Option<()>>>,
1089 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1090 pending_rename: Option<RenameState>,
1091 searchable: bool,
1092 cursor_shape: CursorShape,
1093 current_line_highlight: Option<CurrentLineHighlight>,
1094 collapse_matches: bool,
1095 autoindent_mode: Option<AutoindentMode>,
1096 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1097 input_enabled: bool,
1098 use_modal_editing: bool,
1099 read_only: bool,
1100 leader_id: Option<CollaboratorId>,
1101 remote_id: Option<ViewId>,
1102 pub hover_state: HoverState,
1103 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1104 gutter_hovered: bool,
1105 hovered_link_state: Option<HoveredLinkState>,
1106 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1107 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1108 active_edit_prediction: Option<EditPredictionState>,
1109 /// Used to prevent flickering as the user types while the menu is open
1110 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1111 edit_prediction_settings: EditPredictionSettings,
1112 edit_predictions_hidden_for_vim_mode: bool,
1113 show_edit_predictions_override: Option<bool>,
1114 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1115 edit_prediction_preview: EditPredictionPreview,
1116 edit_prediction_indent_conflict: bool,
1117 edit_prediction_requires_modifier_in_indent_conflict: bool,
1118 inlay_hint_cache: InlayHintCache,
1119 next_inlay_id: usize,
1120 _subscriptions: Vec<Subscription>,
1121 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1122 gutter_dimensions: GutterDimensions,
1123 style: Option<EditorStyle>,
1124 text_style_refinement: Option<TextStyleRefinement>,
1125 next_editor_action_id: EditorActionId,
1126 editor_actions: Rc<
1127 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1128 >,
1129 use_autoclose: bool,
1130 use_auto_surround: bool,
1131 auto_replace_emoji_shortcode: bool,
1132 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1133 show_git_blame_gutter: bool,
1134 show_git_blame_inline: bool,
1135 show_git_blame_inline_delay_task: Option<Task<()>>,
1136 git_blame_inline_enabled: bool,
1137 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1138 serialize_dirty_buffers: bool,
1139 show_selection_menu: Option<bool>,
1140 blame: Option<Entity<GitBlame>>,
1141 blame_subscription: Option<Subscription>,
1142 custom_context_menu: Option<
1143 Box<
1144 dyn 'static
1145 + Fn(
1146 &mut Self,
1147 DisplayPoint,
1148 &mut Window,
1149 &mut Context<Self>,
1150 ) -> Option<Entity<ui::ContextMenu>>,
1151 >,
1152 >,
1153 last_bounds: Option<Bounds<Pixels>>,
1154 last_position_map: Option<Rc<PositionMap>>,
1155 expect_bounds_change: Option<Bounds<Pixels>>,
1156 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1157 tasks_update_task: Option<Task<()>>,
1158 breakpoint_store: Option<Entity<BreakpointStore>>,
1159 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1160 hovered_diff_hunk_row: Option<DisplayRow>,
1161 pull_diagnostics_task: Task<()>,
1162 in_project_search: bool,
1163 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1164 breadcrumb_header: Option<String>,
1165 focused_block: Option<FocusedBlock>,
1166 next_scroll_position: NextScrollCursorCenterTopBottom,
1167 addons: HashMap<TypeId, Box<dyn Addon>>,
1168 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1169 load_diff_task: Option<Shared<Task<()>>>,
1170 /// Whether we are temporarily displaying a diff other than git's
1171 temporary_diff_override: bool,
1172 selection_mark_mode: bool,
1173 toggle_fold_multiple_buffers: Task<()>,
1174 _scroll_cursor_center_top_bottom_task: Task<()>,
1175 serialize_selections: Task<()>,
1176 serialize_folds: Task<()>,
1177 mouse_cursor_hidden: bool,
1178 minimap: Option<Entity<Self>>,
1179 hide_mouse_mode: HideMouseMode,
1180 pub change_list: ChangeList,
1181 inline_value_cache: InlineValueCache,
1182 selection_drag_state: SelectionDragState,
1183 next_color_inlay_id: usize,
1184 colors: Option<LspColorData>,
1185 folding_newlines: Task<()>,
1186}
1187
1188#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1189enum NextScrollCursorCenterTopBottom {
1190 #[default]
1191 Center,
1192 Top,
1193 Bottom,
1194}
1195
1196impl NextScrollCursorCenterTopBottom {
1197 fn next(&self) -> Self {
1198 match self {
1199 Self::Center => Self::Top,
1200 Self::Top => Self::Bottom,
1201 Self::Bottom => Self::Center,
1202 }
1203 }
1204}
1205
1206#[derive(Clone)]
1207pub struct EditorSnapshot {
1208 pub mode: EditorMode,
1209 show_gutter: bool,
1210 show_line_numbers: Option<bool>,
1211 show_git_diff_gutter: Option<bool>,
1212 show_code_actions: Option<bool>,
1213 show_runnables: Option<bool>,
1214 show_breakpoints: Option<bool>,
1215 git_blame_gutter_max_author_length: Option<usize>,
1216 pub display_snapshot: DisplaySnapshot,
1217 pub placeholder_text: Option<Arc<str>>,
1218 is_focused: bool,
1219 scroll_anchor: ScrollAnchor,
1220 ongoing_scroll: OngoingScroll,
1221 current_line_highlight: CurrentLineHighlight,
1222 gutter_hovered: bool,
1223}
1224
1225#[derive(Default, Debug, Clone, Copy)]
1226pub struct GutterDimensions {
1227 pub left_padding: Pixels,
1228 pub right_padding: Pixels,
1229 pub width: Pixels,
1230 pub margin: Pixels,
1231 pub git_blame_entries_width: Option<Pixels>,
1232}
1233
1234impl GutterDimensions {
1235 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1236 Self {
1237 margin: Self::default_gutter_margin(font_id, font_size, cx),
1238 ..Default::default()
1239 }
1240 }
1241
1242 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1243 -cx.text_system().descent(font_id, font_size)
1244 }
1245 /// The full width of the space taken up by the gutter.
1246 pub fn full_width(&self) -> Pixels {
1247 self.margin + self.width
1248 }
1249
1250 /// The width of the space reserved for the fold indicators,
1251 /// use alongside 'justify_end' and `gutter_width` to
1252 /// right align content with the line numbers
1253 pub fn fold_area_width(&self) -> Pixels {
1254 self.margin + self.right_padding
1255 }
1256}
1257
1258struct CharacterDimensions {
1259 em_width: Pixels,
1260 em_advance: Pixels,
1261 line_height: Pixels,
1262}
1263
1264#[derive(Debug)]
1265pub struct RemoteSelection {
1266 pub replica_id: ReplicaId,
1267 pub selection: Selection<Anchor>,
1268 pub cursor_shape: CursorShape,
1269 pub collaborator_id: CollaboratorId,
1270 pub line_mode: bool,
1271 pub user_name: Option<SharedString>,
1272 pub color: PlayerColor,
1273}
1274
1275#[derive(Clone, Debug)]
1276struct SelectionHistoryEntry {
1277 selections: Arc<[Selection<Anchor>]>,
1278 select_next_state: Option<SelectNextState>,
1279 select_prev_state: Option<SelectNextState>,
1280 add_selections_state: Option<AddSelectionsState>,
1281}
1282
1283#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1284enum SelectionHistoryMode {
1285 Normal,
1286 Undoing,
1287 Redoing,
1288 Skipping,
1289}
1290
1291#[derive(Clone, PartialEq, Eq, Hash)]
1292struct HoveredCursor {
1293 replica_id: u16,
1294 selection_id: usize,
1295}
1296
1297impl Default for SelectionHistoryMode {
1298 fn default() -> Self {
1299 Self::Normal
1300 }
1301}
1302
1303#[derive(Debug)]
1304/// SelectionEffects controls the side-effects of updating the selection.
1305///
1306/// The default behaviour does "what you mostly want":
1307/// - it pushes to the nav history if the cursor moved by >10 lines
1308/// - it re-triggers completion requests
1309/// - it scrolls to fit
1310///
1311/// You might want to modify these behaviours. For example when doing a "jump"
1312/// like go to definition, we always want to add to nav history; but when scrolling
1313/// in vim mode we never do.
1314///
1315/// Similarly, you might want to disable scrolling if you don't want the viewport to
1316/// move.
1317#[derive(Clone)]
1318pub struct SelectionEffects {
1319 nav_history: Option<bool>,
1320 completions: bool,
1321 scroll: Option<Autoscroll>,
1322}
1323
1324impl Default for SelectionEffects {
1325 fn default() -> Self {
1326 Self {
1327 nav_history: None,
1328 completions: true,
1329 scroll: Some(Autoscroll::fit()),
1330 }
1331 }
1332}
1333impl SelectionEffects {
1334 pub fn scroll(scroll: Autoscroll) -> Self {
1335 Self {
1336 scroll: Some(scroll),
1337 ..Default::default()
1338 }
1339 }
1340
1341 pub fn no_scroll() -> Self {
1342 Self {
1343 scroll: None,
1344 ..Default::default()
1345 }
1346 }
1347
1348 pub fn completions(self, completions: bool) -> Self {
1349 Self {
1350 completions,
1351 ..self
1352 }
1353 }
1354
1355 pub fn nav_history(self, nav_history: bool) -> Self {
1356 Self {
1357 nav_history: Some(nav_history),
1358 ..self
1359 }
1360 }
1361}
1362
1363struct DeferredSelectionEffectsState {
1364 changed: bool,
1365 effects: SelectionEffects,
1366 old_cursor_position: Anchor,
1367 history_entry: SelectionHistoryEntry,
1368}
1369
1370#[derive(Default)]
1371struct SelectionHistory {
1372 #[allow(clippy::type_complexity)]
1373 selections_by_transaction:
1374 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1375 mode: SelectionHistoryMode,
1376 undo_stack: VecDeque<SelectionHistoryEntry>,
1377 redo_stack: VecDeque<SelectionHistoryEntry>,
1378}
1379
1380impl SelectionHistory {
1381 #[track_caller]
1382 fn insert_transaction(
1383 &mut self,
1384 transaction_id: TransactionId,
1385 selections: Arc<[Selection<Anchor>]>,
1386 ) {
1387 if selections.is_empty() {
1388 log::error!(
1389 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1390 std::panic::Location::caller()
1391 );
1392 return;
1393 }
1394 self.selections_by_transaction
1395 .insert(transaction_id, (selections, None));
1396 }
1397
1398 #[allow(clippy::type_complexity)]
1399 fn transaction(
1400 &self,
1401 transaction_id: TransactionId,
1402 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1403 self.selections_by_transaction.get(&transaction_id)
1404 }
1405
1406 #[allow(clippy::type_complexity)]
1407 fn transaction_mut(
1408 &mut self,
1409 transaction_id: TransactionId,
1410 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1411 self.selections_by_transaction.get_mut(&transaction_id)
1412 }
1413
1414 fn push(&mut self, entry: SelectionHistoryEntry) {
1415 if !entry.selections.is_empty() {
1416 match self.mode {
1417 SelectionHistoryMode::Normal => {
1418 self.push_undo(entry);
1419 self.redo_stack.clear();
1420 }
1421 SelectionHistoryMode::Undoing => self.push_redo(entry),
1422 SelectionHistoryMode::Redoing => self.push_undo(entry),
1423 SelectionHistoryMode::Skipping => {}
1424 }
1425 }
1426 }
1427
1428 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1429 if self
1430 .undo_stack
1431 .back()
1432 .map_or(true, |e| e.selections != entry.selections)
1433 {
1434 self.undo_stack.push_back(entry);
1435 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1436 self.undo_stack.pop_front();
1437 }
1438 }
1439 }
1440
1441 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1442 if self
1443 .redo_stack
1444 .back()
1445 .map_or(true, |e| e.selections != entry.selections)
1446 {
1447 self.redo_stack.push_back(entry);
1448 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1449 self.redo_stack.pop_front();
1450 }
1451 }
1452 }
1453}
1454
1455#[derive(Clone, Copy)]
1456pub struct RowHighlightOptions {
1457 pub autoscroll: bool,
1458 pub include_gutter: bool,
1459}
1460
1461impl Default for RowHighlightOptions {
1462 fn default() -> Self {
1463 Self {
1464 autoscroll: Default::default(),
1465 include_gutter: true,
1466 }
1467 }
1468}
1469
1470struct RowHighlight {
1471 index: usize,
1472 range: Range<Anchor>,
1473 color: Hsla,
1474 options: RowHighlightOptions,
1475 type_id: TypeId,
1476}
1477
1478#[derive(Clone, Debug)]
1479struct AddSelectionsState {
1480 groups: Vec<AddSelectionsGroup>,
1481}
1482
1483#[derive(Clone, Debug)]
1484struct AddSelectionsGroup {
1485 above: bool,
1486 stack: Vec<usize>,
1487}
1488
1489#[derive(Clone)]
1490struct SelectNextState {
1491 query: AhoCorasick,
1492 wordwise: bool,
1493 done: bool,
1494}
1495
1496impl std::fmt::Debug for SelectNextState {
1497 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1498 f.debug_struct(std::any::type_name::<Self>())
1499 .field("wordwise", &self.wordwise)
1500 .field("done", &self.done)
1501 .finish()
1502 }
1503}
1504
1505#[derive(Debug)]
1506struct AutocloseRegion {
1507 selection_id: usize,
1508 range: Range<Anchor>,
1509 pair: BracketPair,
1510}
1511
1512#[derive(Debug)]
1513struct SnippetState {
1514 ranges: Vec<Vec<Range<Anchor>>>,
1515 active_index: usize,
1516 choices: Vec<Option<Vec<String>>>,
1517}
1518
1519#[doc(hidden)]
1520pub struct RenameState {
1521 pub range: Range<Anchor>,
1522 pub old_name: Arc<str>,
1523 pub editor: Entity<Editor>,
1524 block_id: CustomBlockId,
1525}
1526
1527struct InvalidationStack<T>(Vec<T>);
1528
1529struct RegisteredEditPredictionProvider {
1530 provider: Arc<dyn EditPredictionProviderHandle>,
1531 _subscription: Subscription,
1532}
1533
1534#[derive(Debug, PartialEq, Eq)]
1535pub struct ActiveDiagnosticGroup {
1536 pub active_range: Range<Anchor>,
1537 pub active_message: String,
1538 pub group_id: usize,
1539 pub blocks: HashSet<CustomBlockId>,
1540}
1541
1542#[derive(Debug, PartialEq, Eq)]
1543
1544pub(crate) enum ActiveDiagnostic {
1545 None,
1546 All,
1547 Group(ActiveDiagnosticGroup),
1548}
1549
1550#[derive(Serialize, Deserialize, Clone, Debug)]
1551pub struct ClipboardSelection {
1552 /// The number of bytes in this selection.
1553 pub len: usize,
1554 /// Whether this was a full-line selection.
1555 pub is_entire_line: bool,
1556 /// The indentation of the first line when this content was originally copied.
1557 pub first_line_indent: u32,
1558}
1559
1560// selections, scroll behavior, was newest selection reversed
1561type SelectSyntaxNodeHistoryState = (
1562 Box<[Selection<usize>]>,
1563 SelectSyntaxNodeScrollBehavior,
1564 bool,
1565);
1566
1567#[derive(Default)]
1568struct SelectSyntaxNodeHistory {
1569 stack: Vec<SelectSyntaxNodeHistoryState>,
1570 // disable temporarily to allow changing selections without losing the stack
1571 pub disable_clearing: bool,
1572}
1573
1574impl SelectSyntaxNodeHistory {
1575 pub fn try_clear(&mut self) {
1576 if !self.disable_clearing {
1577 self.stack.clear();
1578 }
1579 }
1580
1581 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1582 self.stack.push(selection);
1583 }
1584
1585 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1586 self.stack.pop()
1587 }
1588}
1589
1590enum SelectSyntaxNodeScrollBehavior {
1591 CursorTop,
1592 FitSelection,
1593 CursorBottom,
1594}
1595
1596#[derive(Debug)]
1597pub(crate) struct NavigationData {
1598 cursor_anchor: Anchor,
1599 cursor_position: Point,
1600 scroll_anchor: ScrollAnchor,
1601 scroll_top_row: u32,
1602}
1603
1604#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1605pub enum GotoDefinitionKind {
1606 Symbol,
1607 Declaration,
1608 Type,
1609 Implementation,
1610}
1611
1612#[derive(Debug, Clone)]
1613enum InlayHintRefreshReason {
1614 ModifiersChanged(bool),
1615 Toggle(bool),
1616 SettingsChange(InlayHintSettings),
1617 NewLinesShown,
1618 BufferEdited(HashSet<Arc<Language>>),
1619 RefreshRequested,
1620 ExcerptsRemoved(Vec<ExcerptId>),
1621}
1622
1623impl InlayHintRefreshReason {
1624 fn description(&self) -> &'static str {
1625 match self {
1626 Self::ModifiersChanged(_) => "modifiers changed",
1627 Self::Toggle(_) => "toggle",
1628 Self::SettingsChange(_) => "settings change",
1629 Self::NewLinesShown => "new lines shown",
1630 Self::BufferEdited(_) => "buffer edited",
1631 Self::RefreshRequested => "refresh requested",
1632 Self::ExcerptsRemoved(_) => "excerpts removed",
1633 }
1634 }
1635}
1636
1637pub enum FormatTarget {
1638 Buffers(HashSet<Entity<Buffer>>),
1639 Ranges(Vec<Range<MultiBufferPoint>>),
1640}
1641
1642pub(crate) struct FocusedBlock {
1643 id: BlockId,
1644 focus_handle: WeakFocusHandle,
1645}
1646
1647#[derive(Clone)]
1648enum JumpData {
1649 MultiBufferRow {
1650 row: MultiBufferRow,
1651 line_offset_from_top: u32,
1652 },
1653 MultiBufferPoint {
1654 excerpt_id: ExcerptId,
1655 position: Point,
1656 anchor: text::Anchor,
1657 line_offset_from_top: u32,
1658 },
1659}
1660
1661pub enum MultibufferSelectionMode {
1662 First,
1663 All,
1664}
1665
1666#[derive(Clone, Copy, Debug, Default)]
1667pub struct RewrapOptions {
1668 pub override_language_settings: bool,
1669 pub preserve_existing_whitespace: bool,
1670}
1671
1672impl Editor {
1673 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1674 let buffer = cx.new(|cx| Buffer::local("", cx));
1675 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1676 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1677 }
1678
1679 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1680 let buffer = cx.new(|cx| Buffer::local("", cx));
1681 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1682 Self::new(EditorMode::full(), buffer, None, window, cx)
1683 }
1684
1685 pub fn auto_height(
1686 min_lines: usize,
1687 max_lines: usize,
1688 window: &mut Window,
1689 cx: &mut Context<Self>,
1690 ) -> Self {
1691 let buffer = cx.new(|cx| Buffer::local("", cx));
1692 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1693 Self::new(
1694 EditorMode::AutoHeight {
1695 min_lines,
1696 max_lines: Some(max_lines),
1697 },
1698 buffer,
1699 None,
1700 window,
1701 cx,
1702 )
1703 }
1704
1705 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1706 /// The editor grows as tall as needed to fit its content.
1707 pub fn auto_height_unbounded(
1708 min_lines: usize,
1709 window: &mut Window,
1710 cx: &mut Context<Self>,
1711 ) -> Self {
1712 let buffer = cx.new(|cx| Buffer::local("", cx));
1713 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1714 Self::new(
1715 EditorMode::AutoHeight {
1716 min_lines,
1717 max_lines: None,
1718 },
1719 buffer,
1720 None,
1721 window,
1722 cx,
1723 )
1724 }
1725
1726 pub fn for_buffer(
1727 buffer: Entity<Buffer>,
1728 project: Option<Entity<Project>>,
1729 window: &mut Window,
1730 cx: &mut Context<Self>,
1731 ) -> Self {
1732 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1733 Self::new(EditorMode::full(), buffer, project, window, cx)
1734 }
1735
1736 pub fn for_multibuffer(
1737 buffer: Entity<MultiBuffer>,
1738 project: Option<Entity<Project>>,
1739 window: &mut Window,
1740 cx: &mut Context<Self>,
1741 ) -> Self {
1742 Self::new(EditorMode::full(), buffer, project, window, cx)
1743 }
1744
1745 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1746 let mut clone = Self::new(
1747 self.mode.clone(),
1748 self.buffer.clone(),
1749 self.project.clone(),
1750 window,
1751 cx,
1752 );
1753 self.display_map.update(cx, |display_map, cx| {
1754 let snapshot = display_map.snapshot(cx);
1755 clone.display_map.update(cx, |display_map, cx| {
1756 display_map.set_state(&snapshot, cx);
1757 });
1758 });
1759 clone.folds_did_change(cx);
1760 clone.selections.clone_state(&self.selections);
1761 clone.scroll_manager.clone_state(&self.scroll_manager);
1762 clone.searchable = self.searchable;
1763 clone.read_only = self.read_only;
1764 clone
1765 }
1766
1767 pub fn new(
1768 mode: EditorMode,
1769 buffer: Entity<MultiBuffer>,
1770 project: Option<Entity<Project>>,
1771 window: &mut Window,
1772 cx: &mut Context<Self>,
1773 ) -> Self {
1774 Editor::new_internal(mode, buffer, project, None, window, cx)
1775 }
1776
1777 fn new_internal(
1778 mode: EditorMode,
1779 buffer: Entity<MultiBuffer>,
1780 project: Option<Entity<Project>>,
1781 display_map: Option<Entity<DisplayMap>>,
1782 window: &mut Window,
1783 cx: &mut Context<Self>,
1784 ) -> Self {
1785 debug_assert!(
1786 display_map.is_none() || mode.is_minimap(),
1787 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1788 );
1789
1790 let full_mode = mode.is_full();
1791 let is_minimap = mode.is_minimap();
1792 let diagnostics_max_severity = if full_mode {
1793 EditorSettings::get_global(cx)
1794 .diagnostics_max_severity
1795 .unwrap_or(DiagnosticSeverity::Hint)
1796 } else {
1797 DiagnosticSeverity::Off
1798 };
1799 let style = window.text_style();
1800 let font_size = style.font_size.to_pixels(window.rem_size());
1801 let editor = cx.entity().downgrade();
1802 let fold_placeholder = FoldPlaceholder {
1803 constrain_width: true,
1804 render: Arc::new(move |fold_id, fold_range, cx| {
1805 let editor = editor.clone();
1806 div()
1807 .id(fold_id)
1808 .bg(cx.theme().colors().ghost_element_background)
1809 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1810 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1811 .rounded_xs()
1812 .size_full()
1813 .cursor_pointer()
1814 .child("⋯")
1815 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1816 .on_click(move |_, _window, cx| {
1817 editor
1818 .update(cx, |editor, cx| {
1819 editor.unfold_ranges(
1820 &[fold_range.start..fold_range.end],
1821 true,
1822 false,
1823 cx,
1824 );
1825 cx.stop_propagation();
1826 })
1827 .ok();
1828 })
1829 .into_any()
1830 }),
1831 merge_adjacent: true,
1832 ..FoldPlaceholder::default()
1833 };
1834 let display_map = display_map.unwrap_or_else(|| {
1835 cx.new(|cx| {
1836 DisplayMap::new(
1837 buffer.clone(),
1838 style.font(),
1839 font_size,
1840 None,
1841 FILE_HEADER_HEIGHT,
1842 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1843 fold_placeholder,
1844 diagnostics_max_severity,
1845 cx,
1846 )
1847 })
1848 });
1849
1850 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1851
1852 let blink_manager = cx.new(|cx| {
1853 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1854 if is_minimap {
1855 blink_manager.disable(cx);
1856 }
1857 blink_manager
1858 });
1859
1860 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1861 .then(|| language_settings::SoftWrap::None);
1862
1863 let mut project_subscriptions = Vec::new();
1864 if full_mode && let Some(project) = project.as_ref() {
1865 project_subscriptions.push(cx.subscribe_in(
1866 project,
1867 window,
1868 |editor, _, event, window, cx| match event {
1869 project::Event::RefreshCodeLens => {
1870 // we always query lens with actions, without storing them, always refreshing them
1871 }
1872 project::Event::RefreshInlayHints => {
1873 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1874 }
1875 project::Event::LanguageServerAdded(..)
1876 | project::Event::LanguageServerRemoved(..) => {
1877 if editor.tasks_update_task.is_none() {
1878 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1879 }
1880 }
1881 project::Event::SnippetEdit(id, snippet_edits) => {
1882 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1883 let focus_handle = editor.focus_handle(cx);
1884 if focus_handle.is_focused(window) {
1885 let snapshot = buffer.read(cx).snapshot();
1886 for (range, snippet) in snippet_edits {
1887 let editor_range =
1888 language::range_from_lsp(*range).to_offset(&snapshot);
1889 editor
1890 .insert_snippet(
1891 &[editor_range],
1892 snippet.clone(),
1893 window,
1894 cx,
1895 )
1896 .ok();
1897 }
1898 }
1899 }
1900 }
1901 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1902 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1903 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1904 }
1905 }
1906 _ => {}
1907 },
1908 ));
1909 if let Some(task_inventory) = project
1910 .read(cx)
1911 .task_store()
1912 .read(cx)
1913 .task_inventory()
1914 .cloned()
1915 {
1916 project_subscriptions.push(cx.observe_in(
1917 &task_inventory,
1918 window,
1919 |editor, _, window, cx| {
1920 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1921 },
1922 ));
1923 };
1924
1925 project_subscriptions.push(cx.subscribe_in(
1926 &project.read(cx).breakpoint_store(),
1927 window,
1928 |editor, _, event, window, cx| match event {
1929 BreakpointStoreEvent::ClearDebugLines => {
1930 editor.clear_row_highlights::<ActiveDebugLine>();
1931 editor.refresh_inline_values(cx);
1932 }
1933 BreakpointStoreEvent::SetDebugLine => {
1934 if editor.go_to_active_debug_line(window, cx) {
1935 cx.stop_propagation();
1936 }
1937
1938 editor.refresh_inline_values(cx);
1939 }
1940 _ => {}
1941 },
1942 ));
1943 let git_store = project.read(cx).git_store().clone();
1944 let project = project.clone();
1945 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1946 match event {
1947 GitStoreEvent::RepositoryUpdated(
1948 _,
1949 RepositoryEvent::Updated {
1950 new_instance: true, ..
1951 },
1952 _,
1953 ) => {
1954 this.load_diff_task = Some(
1955 update_uncommitted_diff_for_buffer(
1956 cx.entity(),
1957 &project,
1958 this.buffer.read(cx).all_buffers(),
1959 this.buffer.clone(),
1960 cx,
1961 )
1962 .shared(),
1963 );
1964 }
1965 _ => {}
1966 }
1967 }));
1968 }
1969
1970 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1971
1972 let inlay_hint_settings =
1973 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1974 let focus_handle = cx.focus_handle();
1975 if !is_minimap {
1976 cx.on_focus(&focus_handle, window, Self::handle_focus)
1977 .detach();
1978 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1979 .detach();
1980 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1981 .detach();
1982 cx.on_blur(&focus_handle, window, Self::handle_blur)
1983 .detach();
1984 cx.observe_pending_input(window, Self::observe_pending_input)
1985 .detach();
1986 }
1987
1988 let show_indent_guides = if matches!(
1989 mode,
1990 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1991 ) {
1992 Some(false)
1993 } else {
1994 None
1995 };
1996
1997 let breakpoint_store = match (&mode, project.as_ref()) {
1998 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1999 _ => None,
2000 };
2001
2002 let mut code_action_providers = Vec::new();
2003 let mut load_uncommitted_diff = None;
2004 if let Some(project) = project.clone() {
2005 load_uncommitted_diff = Some(
2006 update_uncommitted_diff_for_buffer(
2007 cx.entity(),
2008 &project,
2009 buffer.read(cx).all_buffers(),
2010 buffer.clone(),
2011 cx,
2012 )
2013 .shared(),
2014 );
2015 code_action_providers.push(Rc::new(project) as Rc<_>);
2016 }
2017
2018 let mut editor = Self {
2019 focus_handle,
2020 show_cursor_when_unfocused: false,
2021 last_focused_descendant: None,
2022 buffer: buffer.clone(),
2023 display_map: display_map.clone(),
2024 selections,
2025 scroll_manager: ScrollManager::new(cx),
2026 columnar_selection_state: None,
2027 add_selections_state: None,
2028 select_next_state: None,
2029 select_prev_state: None,
2030 selection_history: SelectionHistory::default(),
2031 defer_selection_effects: false,
2032 deferred_selection_effects_state: None,
2033 autoclose_regions: Vec::new(),
2034 snippet_stack: InvalidationStack::default(),
2035 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2036 ime_transaction: None,
2037 active_diagnostics: ActiveDiagnostic::None,
2038 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2039 inline_diagnostics_update: Task::ready(()),
2040 inline_diagnostics: Vec::new(),
2041 soft_wrap_mode_override,
2042 diagnostics_max_severity,
2043 hard_wrap: None,
2044 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2045 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2046 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2047 project,
2048 blink_manager: blink_manager.clone(),
2049 show_local_selections: true,
2050 show_scrollbars: ScrollbarAxes {
2051 horizontal: full_mode,
2052 vertical: full_mode,
2053 },
2054 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2055 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2056 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2057 show_gutter: full_mode,
2058 show_line_numbers: (!full_mode).then_some(false),
2059 use_relative_line_numbers: None,
2060 disable_expand_excerpt_buttons: !full_mode,
2061 show_git_diff_gutter: None,
2062 show_code_actions: None,
2063 show_runnables: None,
2064 show_breakpoints: None,
2065 show_wrap_guides: None,
2066 show_indent_guides,
2067 placeholder_text: None,
2068 highlight_order: 0,
2069 highlighted_rows: HashMap::default(),
2070 background_highlights: TreeMap::default(),
2071 gutter_highlights: TreeMap::default(),
2072 scrollbar_marker_state: ScrollbarMarkerState::default(),
2073 active_indent_guides_state: ActiveIndentGuidesState::default(),
2074 nav_history: None,
2075 context_menu: RefCell::new(None),
2076 context_menu_options: None,
2077 mouse_context_menu: None,
2078 completion_tasks: Vec::new(),
2079 inline_blame_popover: None,
2080 inline_blame_popover_show_task: None,
2081 signature_help_state: SignatureHelpState::default(),
2082 auto_signature_help: None,
2083 find_all_references_task_sources: Vec::new(),
2084 next_completion_id: 0,
2085 next_inlay_id: 0,
2086 code_action_providers,
2087 available_code_actions: None,
2088 code_actions_task: None,
2089 quick_selection_highlight_task: None,
2090 debounced_selection_highlight_task: None,
2091 document_highlights_task: None,
2092 linked_editing_range_task: None,
2093 pending_rename: None,
2094 searchable: !is_minimap,
2095 cursor_shape: EditorSettings::get_global(cx)
2096 .cursor_shape
2097 .unwrap_or_default(),
2098 current_line_highlight: None,
2099 autoindent_mode: Some(AutoindentMode::EachLine),
2100 collapse_matches: false,
2101 workspace: None,
2102 input_enabled: !is_minimap,
2103 use_modal_editing: full_mode,
2104 read_only: is_minimap,
2105 use_autoclose: true,
2106 use_auto_surround: true,
2107 auto_replace_emoji_shortcode: false,
2108 jsx_tag_auto_close_enabled_in_any_buffer: false,
2109 leader_id: None,
2110 remote_id: None,
2111 hover_state: HoverState::default(),
2112 pending_mouse_down: None,
2113 hovered_link_state: None,
2114 edit_prediction_provider: None,
2115 active_edit_prediction: None,
2116 stale_edit_prediction_in_menu: None,
2117 edit_prediction_preview: EditPredictionPreview::Inactive {
2118 released_too_fast: false,
2119 },
2120 inline_diagnostics_enabled: full_mode,
2121 diagnostics_enabled: full_mode,
2122 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2123 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2124 gutter_hovered: false,
2125 pixel_position_of_newest_cursor: None,
2126 last_bounds: None,
2127 last_position_map: None,
2128 expect_bounds_change: None,
2129 gutter_dimensions: GutterDimensions::default(),
2130 style: None,
2131 show_cursor_names: false,
2132 hovered_cursors: HashMap::default(),
2133 next_editor_action_id: EditorActionId::default(),
2134 editor_actions: Rc::default(),
2135 edit_predictions_hidden_for_vim_mode: false,
2136 show_edit_predictions_override: None,
2137 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2138 edit_prediction_settings: EditPredictionSettings::Disabled,
2139 edit_prediction_indent_conflict: false,
2140 edit_prediction_requires_modifier_in_indent_conflict: true,
2141 custom_context_menu: None,
2142 show_git_blame_gutter: false,
2143 show_git_blame_inline: false,
2144 show_selection_menu: None,
2145 show_git_blame_inline_delay_task: None,
2146 git_blame_inline_enabled: full_mode
2147 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2148 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2149 serialize_dirty_buffers: !is_minimap
2150 && ProjectSettings::get_global(cx)
2151 .session
2152 .restore_unsaved_buffers,
2153 blame: None,
2154 blame_subscription: None,
2155 tasks: BTreeMap::default(),
2156
2157 breakpoint_store,
2158 gutter_breakpoint_indicator: (None, None),
2159 hovered_diff_hunk_row: None,
2160 _subscriptions: (!is_minimap)
2161 .then(|| {
2162 vec![
2163 cx.observe(&buffer, Self::on_buffer_changed),
2164 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2165 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2166 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2167 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2168 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2169 cx.observe_window_activation(window, |editor, window, cx| {
2170 let active = window.is_window_active();
2171 editor.blink_manager.update(cx, |blink_manager, cx| {
2172 if active {
2173 blink_manager.enable(cx);
2174 } else {
2175 blink_manager.disable(cx);
2176 }
2177 });
2178 if active {
2179 editor.show_mouse_cursor(cx);
2180 }
2181 }),
2182 ]
2183 })
2184 .unwrap_or_default(),
2185 tasks_update_task: None,
2186 pull_diagnostics_task: Task::ready(()),
2187 colors: None,
2188 next_color_inlay_id: 0,
2189 linked_edit_ranges: Default::default(),
2190 in_project_search: false,
2191 previous_search_ranges: None,
2192 breadcrumb_header: None,
2193 focused_block: None,
2194 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2195 addons: HashMap::default(),
2196 registered_buffers: HashMap::default(),
2197 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2198 selection_mark_mode: false,
2199 toggle_fold_multiple_buffers: Task::ready(()),
2200 serialize_selections: Task::ready(()),
2201 serialize_folds: Task::ready(()),
2202 text_style_refinement: None,
2203 load_diff_task: load_uncommitted_diff,
2204 temporary_diff_override: false,
2205 mouse_cursor_hidden: false,
2206 minimap: None,
2207 hide_mouse_mode: EditorSettings::get_global(cx)
2208 .hide_mouse
2209 .unwrap_or_default(),
2210 change_list: ChangeList::new(),
2211 mode,
2212 selection_drag_state: SelectionDragState::None,
2213 folding_newlines: Task::ready(()),
2214 };
2215
2216 if is_minimap {
2217 return editor;
2218 }
2219
2220 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2221 editor
2222 ._subscriptions
2223 .push(cx.observe(breakpoints, |_, _, cx| {
2224 cx.notify();
2225 }));
2226 }
2227 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2228 editor._subscriptions.extend(project_subscriptions);
2229
2230 editor._subscriptions.push(cx.subscribe_in(
2231 &cx.entity(),
2232 window,
2233 |editor, _, e: &EditorEvent, window, cx| match e {
2234 EditorEvent::ScrollPositionChanged { local, .. } => {
2235 if *local {
2236 let new_anchor = editor.scroll_manager.anchor();
2237 let snapshot = editor.snapshot(window, cx);
2238 editor.update_restoration_data(cx, move |data| {
2239 data.scroll_position = (
2240 new_anchor.top_row(&snapshot.buffer_snapshot),
2241 new_anchor.offset,
2242 );
2243 });
2244 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2245 editor.inline_blame_popover.take();
2246 }
2247 }
2248 EditorEvent::Edited { .. } => {
2249 if !vim_enabled(cx) {
2250 let (map, selections) = editor.selections.all_adjusted_display(cx);
2251 let pop_state = editor
2252 .change_list
2253 .last()
2254 .map(|previous| {
2255 previous.len() == selections.len()
2256 && previous.iter().enumerate().all(|(ix, p)| {
2257 p.to_display_point(&map).row()
2258 == selections[ix].head().row()
2259 })
2260 })
2261 .unwrap_or(false);
2262 let new_positions = selections
2263 .into_iter()
2264 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2265 .collect();
2266 editor
2267 .change_list
2268 .push_to_change_list(pop_state, new_positions);
2269 }
2270 }
2271 _ => (),
2272 },
2273 ));
2274
2275 if let Some(dap_store) = editor
2276 .project
2277 .as_ref()
2278 .map(|project| project.read(cx).dap_store())
2279 {
2280 let weak_editor = cx.weak_entity();
2281
2282 editor
2283 ._subscriptions
2284 .push(
2285 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2286 let session_entity = cx.entity();
2287 weak_editor
2288 .update(cx, |editor, cx| {
2289 editor._subscriptions.push(
2290 cx.subscribe(&session_entity, Self::on_debug_session_event),
2291 );
2292 })
2293 .ok();
2294 }),
2295 );
2296
2297 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2298 editor
2299 ._subscriptions
2300 .push(cx.subscribe(&session, Self::on_debug_session_event));
2301 }
2302 }
2303
2304 // skip adding the initial selection to selection history
2305 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2306 editor.end_selection(window, cx);
2307 editor.selection_history.mode = SelectionHistoryMode::Normal;
2308
2309 editor.scroll_manager.show_scrollbars(window, cx);
2310 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2311
2312 if full_mode {
2313 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2314 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2315
2316 if editor.git_blame_inline_enabled {
2317 editor.start_git_blame_inline(false, window, cx);
2318 }
2319
2320 editor.go_to_active_debug_line(window, cx);
2321
2322 if let Some(buffer) = buffer.read(cx).as_singleton()
2323 && let Some(project) = editor.project()
2324 {
2325 let handle = project.update(cx, |project, cx| {
2326 project.register_buffer_with_language_servers(&buffer, cx)
2327 });
2328 editor
2329 .registered_buffers
2330 .insert(buffer.read(cx).remote_id(), handle);
2331 }
2332
2333 editor.minimap =
2334 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2335 editor.colors = Some(LspColorData::new(cx));
2336 editor.update_lsp_data(false, None, window, cx);
2337 }
2338
2339 if editor.mode.is_full() {
2340 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2341 }
2342
2343 editor
2344 }
2345
2346 pub fn deploy_mouse_context_menu(
2347 &mut self,
2348 position: gpui::Point<Pixels>,
2349 context_menu: Entity<ContextMenu>,
2350 window: &mut Window,
2351 cx: &mut Context<Self>,
2352 ) {
2353 self.mouse_context_menu = Some(MouseContextMenu::new(
2354 self,
2355 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2356 context_menu,
2357 window,
2358 cx,
2359 ));
2360 }
2361
2362 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2363 self.mouse_context_menu
2364 .as_ref()
2365 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2366 }
2367
2368 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2369 if self
2370 .selections
2371 .pending
2372 .as_ref()
2373 .is_some_and(|pending_selection| {
2374 let snapshot = self.buffer().read(cx).snapshot(cx);
2375 pending_selection
2376 .selection
2377 .range()
2378 .includes(range, &snapshot)
2379 })
2380 {
2381 return true;
2382 }
2383
2384 self.selections
2385 .disjoint_in_range::<usize>(range.clone(), cx)
2386 .into_iter()
2387 .any(|selection| {
2388 // This is needed to cover a corner case, if we just check for an existing
2389 // selection in the fold range, having a cursor at the start of the fold
2390 // marks it as selected. Non-empty selections don't cause this.
2391 let length = selection.end - selection.start;
2392 length > 0
2393 })
2394 }
2395
2396 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2397 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2398 }
2399
2400 fn key_context_internal(
2401 &self,
2402 has_active_edit_prediction: bool,
2403 window: &Window,
2404 cx: &App,
2405 ) -> KeyContext {
2406 let mut key_context = KeyContext::new_with_defaults();
2407 key_context.add("Editor");
2408 let mode = match self.mode {
2409 EditorMode::SingleLine { .. } => "single_line",
2410 EditorMode::AutoHeight { .. } => "auto_height",
2411 EditorMode::Minimap { .. } => "minimap",
2412 EditorMode::Full { .. } => "full",
2413 };
2414
2415 if EditorSettings::jupyter_enabled(cx) {
2416 key_context.add("jupyter");
2417 }
2418
2419 key_context.set("mode", mode);
2420 if self.pending_rename.is_some() {
2421 key_context.add("renaming");
2422 }
2423
2424 match self.context_menu.borrow().as_ref() {
2425 Some(CodeContextMenu::Completions(menu)) => {
2426 if menu.visible() {
2427 key_context.add("menu");
2428 key_context.add("showing_completions");
2429 }
2430 }
2431 Some(CodeContextMenu::CodeActions(menu)) => {
2432 if menu.visible() {
2433 key_context.add("menu");
2434 key_context.add("showing_code_actions")
2435 }
2436 }
2437 None => {}
2438 }
2439
2440 if self.signature_help_state.has_multiple_signatures() {
2441 key_context.add("showing_signature_help");
2442 }
2443
2444 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2445 if !self.focus_handle(cx).contains_focused(window, cx)
2446 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2447 {
2448 for addon in self.addons.values() {
2449 addon.extend_key_context(&mut key_context, cx)
2450 }
2451 }
2452
2453 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2454 if let Some(extension) = singleton_buffer
2455 .read(cx)
2456 .file()
2457 .and_then(|file| file.path().extension()?.to_str())
2458 {
2459 key_context.set("extension", extension.to_string());
2460 }
2461 } else {
2462 key_context.add("multibuffer");
2463 }
2464
2465 if has_active_edit_prediction {
2466 if self.edit_prediction_in_conflict() {
2467 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2468 } else {
2469 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2470 key_context.add("copilot_suggestion");
2471 }
2472 }
2473
2474 if self.selection_mark_mode {
2475 key_context.add("selection_mode");
2476 }
2477
2478 key_context
2479 }
2480
2481 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2482 if self.mouse_cursor_hidden {
2483 self.mouse_cursor_hidden = false;
2484 cx.notify();
2485 }
2486 }
2487
2488 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2489 let hide_mouse_cursor = match origin {
2490 HideMouseCursorOrigin::TypingAction => {
2491 matches!(
2492 self.hide_mouse_mode,
2493 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2494 )
2495 }
2496 HideMouseCursorOrigin::MovementAction => {
2497 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2498 }
2499 };
2500 if self.mouse_cursor_hidden != hide_mouse_cursor {
2501 self.mouse_cursor_hidden = hide_mouse_cursor;
2502 cx.notify();
2503 }
2504 }
2505
2506 pub fn edit_prediction_in_conflict(&self) -> bool {
2507 if !self.show_edit_predictions_in_menu() {
2508 return false;
2509 }
2510
2511 let showing_completions = self
2512 .context_menu
2513 .borrow()
2514 .as_ref()
2515 .map_or(false, |context| {
2516 matches!(context, CodeContextMenu::Completions(_))
2517 });
2518
2519 showing_completions
2520 || self.edit_prediction_requires_modifier()
2521 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2522 // bindings to insert tab characters.
2523 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2524 }
2525
2526 pub fn accept_edit_prediction_keybind(
2527 &self,
2528 accept_partial: bool,
2529 window: &Window,
2530 cx: &App,
2531 ) -> AcceptEditPredictionBinding {
2532 let key_context = self.key_context_internal(true, window, cx);
2533 let in_conflict = self.edit_prediction_in_conflict();
2534
2535 let bindings = if accept_partial {
2536 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2537 } else {
2538 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2539 };
2540
2541 // TODO: if the binding contains multiple keystrokes, display all of them, not
2542 // just the first one.
2543 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2544 !in_conflict
2545 || binding
2546 .keystrokes()
2547 .first()
2548 .map_or(false, |keystroke| keystroke.modifiers.modified())
2549 }))
2550 }
2551
2552 pub fn new_file(
2553 workspace: &mut Workspace,
2554 _: &workspace::NewFile,
2555 window: &mut Window,
2556 cx: &mut Context<Workspace>,
2557 ) {
2558 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2559 "Failed to create buffer",
2560 window,
2561 cx,
2562 |e, _, _| match e.error_code() {
2563 ErrorCode::RemoteUpgradeRequired => Some(format!(
2564 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2565 e.error_tag("required").unwrap_or("the latest version")
2566 )),
2567 _ => None,
2568 },
2569 );
2570 }
2571
2572 pub fn new_in_workspace(
2573 workspace: &mut Workspace,
2574 window: &mut Window,
2575 cx: &mut Context<Workspace>,
2576 ) -> Task<Result<Entity<Editor>>> {
2577 let project = workspace.project().clone();
2578 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2579
2580 cx.spawn_in(window, async move |workspace, cx| {
2581 let buffer = create.await?;
2582 workspace.update_in(cx, |workspace, window, cx| {
2583 let editor =
2584 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2585 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2586 editor
2587 })
2588 })
2589 }
2590
2591 fn new_file_vertical(
2592 workspace: &mut Workspace,
2593 _: &workspace::NewFileSplitVertical,
2594 window: &mut Window,
2595 cx: &mut Context<Workspace>,
2596 ) {
2597 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2598 }
2599
2600 fn new_file_horizontal(
2601 workspace: &mut Workspace,
2602 _: &workspace::NewFileSplitHorizontal,
2603 window: &mut Window,
2604 cx: &mut Context<Workspace>,
2605 ) {
2606 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2607 }
2608
2609 fn new_file_in_direction(
2610 workspace: &mut Workspace,
2611 direction: SplitDirection,
2612 window: &mut Window,
2613 cx: &mut Context<Workspace>,
2614 ) {
2615 let project = workspace.project().clone();
2616 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2617
2618 cx.spawn_in(window, async move |workspace, cx| {
2619 let buffer = create.await?;
2620 workspace.update_in(cx, move |workspace, window, cx| {
2621 workspace.split_item(
2622 direction,
2623 Box::new(
2624 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2625 ),
2626 window,
2627 cx,
2628 )
2629 })?;
2630 anyhow::Ok(())
2631 })
2632 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2633 match e.error_code() {
2634 ErrorCode::RemoteUpgradeRequired => Some(format!(
2635 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2636 e.error_tag("required").unwrap_or("the latest version")
2637 )),
2638 _ => None,
2639 }
2640 });
2641 }
2642
2643 pub fn leader_id(&self) -> Option<CollaboratorId> {
2644 self.leader_id
2645 }
2646
2647 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2648 &self.buffer
2649 }
2650
2651 pub fn project(&self) -> Option<&Entity<Project>> {
2652 self.project.as_ref()
2653 }
2654
2655 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2656 self.workspace.as_ref()?.0.upgrade()
2657 }
2658
2659 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2660 self.buffer().read(cx).title(cx)
2661 }
2662
2663 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2664 let git_blame_gutter_max_author_length = self
2665 .render_git_blame_gutter(cx)
2666 .then(|| {
2667 if let Some(blame) = self.blame.as_ref() {
2668 let max_author_length =
2669 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2670 Some(max_author_length)
2671 } else {
2672 None
2673 }
2674 })
2675 .flatten();
2676
2677 EditorSnapshot {
2678 mode: self.mode.clone(),
2679 show_gutter: self.show_gutter,
2680 show_line_numbers: self.show_line_numbers,
2681 show_git_diff_gutter: self.show_git_diff_gutter,
2682 show_code_actions: self.show_code_actions,
2683 show_runnables: self.show_runnables,
2684 show_breakpoints: self.show_breakpoints,
2685 git_blame_gutter_max_author_length,
2686 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2687 scroll_anchor: self.scroll_manager.anchor(),
2688 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2689 placeholder_text: self.placeholder_text.clone(),
2690 is_focused: self.focus_handle.is_focused(window),
2691 current_line_highlight: self
2692 .current_line_highlight
2693 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2694 gutter_hovered: self.gutter_hovered,
2695 }
2696 }
2697
2698 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2699 self.buffer.read(cx).language_at(point, cx)
2700 }
2701
2702 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2703 self.buffer.read(cx).read(cx).file_at(point).cloned()
2704 }
2705
2706 pub fn active_excerpt(
2707 &self,
2708 cx: &App,
2709 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2710 self.buffer
2711 .read(cx)
2712 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2713 }
2714
2715 pub fn mode(&self) -> &EditorMode {
2716 &self.mode
2717 }
2718
2719 pub fn set_mode(&mut self, mode: EditorMode) {
2720 self.mode = mode;
2721 }
2722
2723 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2724 self.collaboration_hub.as_deref()
2725 }
2726
2727 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2728 self.collaboration_hub = Some(hub);
2729 }
2730
2731 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2732 self.in_project_search = in_project_search;
2733 }
2734
2735 pub fn set_custom_context_menu(
2736 &mut self,
2737 f: impl 'static
2738 + Fn(
2739 &mut Self,
2740 DisplayPoint,
2741 &mut Window,
2742 &mut Context<Self>,
2743 ) -> Option<Entity<ui::ContextMenu>>,
2744 ) {
2745 self.custom_context_menu = Some(Box::new(f))
2746 }
2747
2748 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2749 self.completion_provider = provider;
2750 }
2751
2752 #[cfg(any(test, feature = "test-support"))]
2753 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2754 self.completion_provider.clone()
2755 }
2756
2757 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2758 self.semantics_provider.clone()
2759 }
2760
2761 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2762 self.semantics_provider = provider;
2763 }
2764
2765 pub fn set_edit_prediction_provider<T>(
2766 &mut self,
2767 provider: Option<Entity<T>>,
2768 window: &mut Window,
2769 cx: &mut Context<Self>,
2770 ) where
2771 T: EditPredictionProvider,
2772 {
2773 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2774 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2775 if this.focus_handle.is_focused(window) {
2776 this.update_visible_edit_prediction(window, cx);
2777 }
2778 }),
2779 provider: Arc::new(provider),
2780 });
2781 self.update_edit_prediction_settings(cx);
2782 self.refresh_edit_prediction(false, false, window, cx);
2783 }
2784
2785 pub fn placeholder_text(&self) -> Option<&str> {
2786 self.placeholder_text.as_deref()
2787 }
2788
2789 pub fn set_placeholder_text(
2790 &mut self,
2791 placeholder_text: impl Into<Arc<str>>,
2792 cx: &mut Context<Self>,
2793 ) {
2794 let placeholder_text = Some(placeholder_text.into());
2795 if self.placeholder_text != placeholder_text {
2796 self.placeholder_text = placeholder_text;
2797 cx.notify();
2798 }
2799 }
2800
2801 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2802 self.cursor_shape = cursor_shape;
2803
2804 // Disrupt blink for immediate user feedback that the cursor shape has changed
2805 self.blink_manager.update(cx, BlinkManager::show_cursor);
2806
2807 cx.notify();
2808 }
2809
2810 pub fn set_current_line_highlight(
2811 &mut self,
2812 current_line_highlight: Option<CurrentLineHighlight>,
2813 ) {
2814 self.current_line_highlight = current_line_highlight;
2815 }
2816
2817 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2818 self.collapse_matches = collapse_matches;
2819 }
2820
2821 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2822 let buffers = self.buffer.read(cx).all_buffers();
2823 let Some(project) = self.project.as_ref() else {
2824 return;
2825 };
2826 project.update(cx, |project, cx| {
2827 for buffer in buffers {
2828 self.registered_buffers
2829 .entry(buffer.read(cx).remote_id())
2830 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2831 }
2832 })
2833 }
2834
2835 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2836 if self.collapse_matches {
2837 return range.start..range.start;
2838 }
2839 range.clone()
2840 }
2841
2842 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2843 if self.display_map.read(cx).clip_at_line_ends != clip {
2844 self.display_map
2845 .update(cx, |map, _| map.clip_at_line_ends = clip);
2846 }
2847 }
2848
2849 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2850 self.input_enabled = input_enabled;
2851 }
2852
2853 pub fn set_edit_predictions_hidden_for_vim_mode(
2854 &mut self,
2855 hidden: bool,
2856 window: &mut Window,
2857 cx: &mut Context<Self>,
2858 ) {
2859 if hidden != self.edit_predictions_hidden_for_vim_mode {
2860 self.edit_predictions_hidden_for_vim_mode = hidden;
2861 if hidden {
2862 self.update_visible_edit_prediction(window, cx);
2863 } else {
2864 self.refresh_edit_prediction(true, false, window, cx);
2865 }
2866 }
2867 }
2868
2869 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2870 self.menu_edit_predictions_policy = value;
2871 }
2872
2873 pub fn set_autoindent(&mut self, autoindent: bool) {
2874 if autoindent {
2875 self.autoindent_mode = Some(AutoindentMode::EachLine);
2876 } else {
2877 self.autoindent_mode = None;
2878 }
2879 }
2880
2881 pub fn read_only(&self, cx: &App) -> bool {
2882 self.read_only || self.buffer.read(cx).read_only()
2883 }
2884
2885 pub fn set_read_only(&mut self, read_only: bool) {
2886 self.read_only = read_only;
2887 }
2888
2889 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2890 self.use_autoclose = autoclose;
2891 }
2892
2893 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2894 self.use_auto_surround = auto_surround;
2895 }
2896
2897 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2898 self.auto_replace_emoji_shortcode = auto_replace;
2899 }
2900
2901 pub fn toggle_edit_predictions(
2902 &mut self,
2903 _: &ToggleEditPrediction,
2904 window: &mut Window,
2905 cx: &mut Context<Self>,
2906 ) {
2907 if self.show_edit_predictions_override.is_some() {
2908 self.set_show_edit_predictions(None, window, cx);
2909 } else {
2910 let show_edit_predictions = !self.edit_predictions_enabled();
2911 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2912 }
2913 }
2914
2915 pub fn set_show_edit_predictions(
2916 &mut self,
2917 show_edit_predictions: Option<bool>,
2918 window: &mut Window,
2919 cx: &mut Context<Self>,
2920 ) {
2921 self.show_edit_predictions_override = show_edit_predictions;
2922 self.update_edit_prediction_settings(cx);
2923
2924 if let Some(false) = show_edit_predictions {
2925 self.discard_edit_prediction(false, cx);
2926 } else {
2927 self.refresh_edit_prediction(false, true, window, cx);
2928 }
2929 }
2930
2931 fn edit_predictions_disabled_in_scope(
2932 &self,
2933 buffer: &Entity<Buffer>,
2934 buffer_position: language::Anchor,
2935 cx: &App,
2936 ) -> bool {
2937 let snapshot = buffer.read(cx).snapshot();
2938 let settings = snapshot.settings_at(buffer_position, cx);
2939
2940 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2941 return false;
2942 };
2943
2944 scope.override_name().map_or(false, |scope_name| {
2945 settings
2946 .edit_predictions_disabled_in
2947 .iter()
2948 .any(|s| s == scope_name)
2949 })
2950 }
2951
2952 pub fn set_use_modal_editing(&mut self, to: bool) {
2953 self.use_modal_editing = to;
2954 }
2955
2956 pub fn use_modal_editing(&self) -> bool {
2957 self.use_modal_editing
2958 }
2959
2960 fn selections_did_change(
2961 &mut self,
2962 local: bool,
2963 old_cursor_position: &Anchor,
2964 effects: SelectionEffects,
2965 window: &mut Window,
2966 cx: &mut Context<Self>,
2967 ) {
2968 window.invalidate_character_coordinates();
2969
2970 // Copy selections to primary selection buffer
2971 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2972 if local {
2973 let selections = self.selections.all::<usize>(cx);
2974 let buffer_handle = self.buffer.read(cx).read(cx);
2975
2976 let mut text = String::new();
2977 for (index, selection) in selections.iter().enumerate() {
2978 let text_for_selection = buffer_handle
2979 .text_for_range(selection.start..selection.end)
2980 .collect::<String>();
2981
2982 text.push_str(&text_for_selection);
2983 if index != selections.len() - 1 {
2984 text.push('\n');
2985 }
2986 }
2987
2988 if !text.is_empty() {
2989 cx.write_to_primary(ClipboardItem::new_string(text));
2990 }
2991 }
2992
2993 let selection_anchors = self.selections.disjoint_anchors();
2994
2995 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2996 self.buffer.update(cx, |buffer, cx| {
2997 buffer.set_active_selections(
2998 &selection_anchors,
2999 self.selections.line_mode,
3000 self.cursor_shape,
3001 cx,
3002 )
3003 });
3004 }
3005 let display_map = self
3006 .display_map
3007 .update(cx, |display_map, cx| display_map.snapshot(cx));
3008 let buffer = &display_map.buffer_snapshot;
3009 if self.selections.count() == 1 {
3010 self.add_selections_state = None;
3011 }
3012 self.select_next_state = None;
3013 self.select_prev_state = None;
3014 self.select_syntax_node_history.try_clear();
3015 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3016 self.snippet_stack.invalidate(&selection_anchors, buffer);
3017 self.take_rename(false, window, cx);
3018
3019 let newest_selection = self.selections.newest_anchor();
3020 let new_cursor_position = newest_selection.head();
3021 let selection_start = newest_selection.start;
3022
3023 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3024 self.push_to_nav_history(
3025 *old_cursor_position,
3026 Some(new_cursor_position.to_point(buffer)),
3027 false,
3028 effects.nav_history == Some(true),
3029 cx,
3030 );
3031 }
3032
3033 if local {
3034 if let Some(buffer_id) = new_cursor_position.buffer_id
3035 && !self.registered_buffers.contains_key(&buffer_id)
3036 && let Some(project) = self.project.as_ref()
3037 {
3038 project.update(cx, |project, cx| {
3039 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3040 return;
3041 };
3042 self.registered_buffers.insert(
3043 buffer_id,
3044 project.register_buffer_with_language_servers(&buffer, cx),
3045 );
3046 })
3047 }
3048
3049 let mut context_menu = self.context_menu.borrow_mut();
3050 let completion_menu = match context_menu.as_ref() {
3051 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3052 Some(CodeContextMenu::CodeActions(_)) => {
3053 *context_menu = None;
3054 None
3055 }
3056 None => None,
3057 };
3058 let completion_position = completion_menu.map(|menu| menu.initial_position);
3059 drop(context_menu);
3060
3061 if effects.completions
3062 && let Some(completion_position) = completion_position
3063 {
3064 let start_offset = selection_start.to_offset(buffer);
3065 let position_matches = start_offset == completion_position.to_offset(buffer);
3066 let continue_showing = if position_matches {
3067 if self.snippet_stack.is_empty() {
3068 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3069 } else {
3070 // Snippet choices can be shown even when the cursor is in whitespace.
3071 // Dismissing the menu with actions like backspace is handled by
3072 // invalidation regions.
3073 true
3074 }
3075 } else {
3076 false
3077 };
3078
3079 if continue_showing {
3080 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3081 } else {
3082 self.hide_context_menu(window, cx);
3083 }
3084 }
3085
3086 hide_hover(self, cx);
3087
3088 if old_cursor_position.to_display_point(&display_map).row()
3089 != new_cursor_position.to_display_point(&display_map).row()
3090 {
3091 self.available_code_actions.take();
3092 }
3093 self.refresh_code_actions(window, cx);
3094 self.refresh_document_highlights(cx);
3095 self.refresh_selected_text_highlights(false, window, cx);
3096 refresh_matching_bracket_highlights(self, window, cx);
3097 self.update_visible_edit_prediction(window, cx);
3098 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3099 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3100 self.inline_blame_popover.take();
3101 if self.git_blame_inline_enabled {
3102 self.start_inline_blame_timer(window, cx);
3103 }
3104 }
3105
3106 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3107 cx.emit(EditorEvent::SelectionsChanged { local });
3108
3109 let selections = &self.selections.disjoint;
3110 if selections.len() == 1 {
3111 cx.emit(SearchEvent::ActiveMatchChanged)
3112 }
3113 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3114 let inmemory_selections = selections
3115 .iter()
3116 .map(|s| {
3117 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3118 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3119 })
3120 .collect();
3121 self.update_restoration_data(cx, |data| {
3122 data.selections = inmemory_selections;
3123 });
3124
3125 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3126 && let Some(workspace_id) =
3127 self.workspace.as_ref().and_then(|workspace| workspace.1)
3128 {
3129 let snapshot = self.buffer().read(cx).snapshot(cx);
3130 let selections = selections.clone();
3131 let background_executor = cx.background_executor().clone();
3132 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3133 self.serialize_selections = cx.background_spawn(async move {
3134 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3135 let db_selections = selections
3136 .iter()
3137 .map(|selection| {
3138 (
3139 selection.start.to_offset(&snapshot),
3140 selection.end.to_offset(&snapshot),
3141 )
3142 })
3143 .collect();
3144
3145 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3146 .await
3147 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3148 .log_err();
3149 });
3150 }
3151 }
3152
3153 cx.notify();
3154 }
3155
3156 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3157 use text::ToOffset as _;
3158 use text::ToPoint as _;
3159
3160 if self.mode.is_minimap()
3161 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3162 {
3163 return;
3164 }
3165
3166 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3167 return;
3168 };
3169
3170 let snapshot = singleton.read(cx).snapshot();
3171 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3172 let display_snapshot = display_map.snapshot(cx);
3173
3174 display_snapshot
3175 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3176 .map(|fold| {
3177 fold.range.start.text_anchor.to_point(&snapshot)
3178 ..fold.range.end.text_anchor.to_point(&snapshot)
3179 })
3180 .collect()
3181 });
3182 self.update_restoration_data(cx, |data| {
3183 data.folds = inmemory_folds;
3184 });
3185
3186 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3187 return;
3188 };
3189 let background_executor = cx.background_executor().clone();
3190 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3191 let db_folds = self.display_map.update(cx, |display_map, cx| {
3192 display_map
3193 .snapshot(cx)
3194 .folds_in_range(0..snapshot.len())
3195 .map(|fold| {
3196 (
3197 fold.range.start.text_anchor.to_offset(&snapshot),
3198 fold.range.end.text_anchor.to_offset(&snapshot),
3199 )
3200 })
3201 .collect()
3202 });
3203 self.serialize_folds = cx.background_spawn(async move {
3204 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3205 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3206 .await
3207 .with_context(|| {
3208 format!(
3209 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3210 )
3211 })
3212 .log_err();
3213 });
3214 }
3215
3216 pub fn sync_selections(
3217 &mut self,
3218 other: Entity<Editor>,
3219 cx: &mut Context<Self>,
3220 ) -> gpui::Subscription {
3221 let other_selections = other.read(cx).selections.disjoint.to_vec();
3222 self.selections.change_with(cx, |selections| {
3223 selections.select_anchors(other_selections);
3224 });
3225
3226 let other_subscription =
3227 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3228 EditorEvent::SelectionsChanged { local: true } => {
3229 let other_selections = other.read(cx).selections.disjoint.to_vec();
3230 if other_selections.is_empty() {
3231 return;
3232 }
3233 this.selections.change_with(cx, |selections| {
3234 selections.select_anchors(other_selections);
3235 });
3236 }
3237 _ => {}
3238 });
3239
3240 let this_subscription =
3241 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3242 EditorEvent::SelectionsChanged { local: true } => {
3243 let these_selections = this.selections.disjoint.to_vec();
3244 if these_selections.is_empty() {
3245 return;
3246 }
3247 other.update(cx, |other_editor, cx| {
3248 other_editor.selections.change_with(cx, |selections| {
3249 selections.select_anchors(these_selections);
3250 })
3251 });
3252 }
3253 _ => {}
3254 });
3255
3256 Subscription::join(other_subscription, this_subscription)
3257 }
3258
3259 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3260 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3261 /// effects of selection change occur at the end of the transaction.
3262 pub fn change_selections<R>(
3263 &mut self,
3264 effects: SelectionEffects,
3265 window: &mut Window,
3266 cx: &mut Context<Self>,
3267 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3268 ) -> R {
3269 if let Some(state) = &mut self.deferred_selection_effects_state {
3270 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3271 state.effects.completions = effects.completions;
3272 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3273 let (changed, result) = self.selections.change_with(cx, change);
3274 state.changed |= changed;
3275 return result;
3276 }
3277 let mut state = DeferredSelectionEffectsState {
3278 changed: false,
3279 effects,
3280 old_cursor_position: self.selections.newest_anchor().head(),
3281 history_entry: SelectionHistoryEntry {
3282 selections: self.selections.disjoint_anchors(),
3283 select_next_state: self.select_next_state.clone(),
3284 select_prev_state: self.select_prev_state.clone(),
3285 add_selections_state: self.add_selections_state.clone(),
3286 },
3287 };
3288 let (changed, result) = self.selections.change_with(cx, change);
3289 state.changed = state.changed || changed;
3290 if self.defer_selection_effects {
3291 self.deferred_selection_effects_state = Some(state);
3292 } else {
3293 self.apply_selection_effects(state, window, cx);
3294 }
3295 result
3296 }
3297
3298 /// Defers the effects of selection change, so that the effects of multiple calls to
3299 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3300 /// to selection history and the state of popovers based on selection position aren't
3301 /// erroneously updated.
3302 pub fn with_selection_effects_deferred<R>(
3303 &mut self,
3304 window: &mut Window,
3305 cx: &mut Context<Self>,
3306 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3307 ) -> R {
3308 let already_deferred = self.defer_selection_effects;
3309 self.defer_selection_effects = true;
3310 let result = update(self, window, cx);
3311 if !already_deferred {
3312 self.defer_selection_effects = false;
3313 if let Some(state) = self.deferred_selection_effects_state.take() {
3314 self.apply_selection_effects(state, window, cx);
3315 }
3316 }
3317 result
3318 }
3319
3320 fn apply_selection_effects(
3321 &mut self,
3322 state: DeferredSelectionEffectsState,
3323 window: &mut Window,
3324 cx: &mut Context<Self>,
3325 ) {
3326 if state.changed {
3327 self.selection_history.push(state.history_entry);
3328
3329 if let Some(autoscroll) = state.effects.scroll {
3330 self.request_autoscroll(autoscroll, cx);
3331 }
3332
3333 let old_cursor_position = &state.old_cursor_position;
3334
3335 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3336
3337 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3338 self.show_signature_help(&ShowSignatureHelp, window, cx);
3339 }
3340 }
3341 }
3342
3343 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3344 where
3345 I: IntoIterator<Item = (Range<S>, T)>,
3346 S: ToOffset,
3347 T: Into<Arc<str>>,
3348 {
3349 if self.read_only(cx) {
3350 return;
3351 }
3352
3353 self.buffer
3354 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3355 }
3356
3357 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3358 where
3359 I: IntoIterator<Item = (Range<S>, T)>,
3360 S: ToOffset,
3361 T: Into<Arc<str>>,
3362 {
3363 if self.read_only(cx) {
3364 return;
3365 }
3366
3367 self.buffer.update(cx, |buffer, cx| {
3368 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3369 });
3370 }
3371
3372 pub fn edit_with_block_indent<I, S, T>(
3373 &mut self,
3374 edits: I,
3375 original_indent_columns: Vec<Option<u32>>,
3376 cx: &mut Context<Self>,
3377 ) where
3378 I: IntoIterator<Item = (Range<S>, T)>,
3379 S: ToOffset,
3380 T: Into<Arc<str>>,
3381 {
3382 if self.read_only(cx) {
3383 return;
3384 }
3385
3386 self.buffer.update(cx, |buffer, cx| {
3387 buffer.edit(
3388 edits,
3389 Some(AutoindentMode::Block {
3390 original_indent_columns,
3391 }),
3392 cx,
3393 )
3394 });
3395 }
3396
3397 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3398 self.hide_context_menu(window, cx);
3399
3400 match phase {
3401 SelectPhase::Begin {
3402 position,
3403 add,
3404 click_count,
3405 } => self.begin_selection(position, add, click_count, window, cx),
3406 SelectPhase::BeginColumnar {
3407 position,
3408 goal_column,
3409 reset,
3410 mode,
3411 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3412 SelectPhase::Extend {
3413 position,
3414 click_count,
3415 } => self.extend_selection(position, click_count, window, cx),
3416 SelectPhase::Update {
3417 position,
3418 goal_column,
3419 scroll_delta,
3420 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3421 SelectPhase::End => self.end_selection(window, cx),
3422 }
3423 }
3424
3425 fn extend_selection(
3426 &mut self,
3427 position: DisplayPoint,
3428 click_count: usize,
3429 window: &mut Window,
3430 cx: &mut Context<Self>,
3431 ) {
3432 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3433 let tail = self.selections.newest::<usize>(cx).tail();
3434 self.begin_selection(position, false, click_count, window, cx);
3435
3436 let position = position.to_offset(&display_map, Bias::Left);
3437 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3438
3439 let mut pending_selection = self
3440 .selections
3441 .pending_anchor()
3442 .expect("extend_selection not called with pending selection");
3443 if position >= tail {
3444 pending_selection.start = tail_anchor;
3445 } else {
3446 pending_selection.end = tail_anchor;
3447 pending_selection.reversed = true;
3448 }
3449
3450 let mut pending_mode = self.selections.pending_mode().unwrap();
3451 match &mut pending_mode {
3452 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3453 _ => {}
3454 }
3455
3456 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3457 SelectionEffects::scroll(Autoscroll::fit())
3458 } else {
3459 SelectionEffects::no_scroll()
3460 };
3461
3462 self.change_selections(effects, window, cx, |s| {
3463 s.set_pending(pending_selection, pending_mode)
3464 });
3465 }
3466
3467 fn begin_selection(
3468 &mut self,
3469 position: DisplayPoint,
3470 add: bool,
3471 click_count: usize,
3472 window: &mut Window,
3473 cx: &mut Context<Self>,
3474 ) {
3475 if !self.focus_handle.is_focused(window) {
3476 self.last_focused_descendant = None;
3477 window.focus(&self.focus_handle);
3478 }
3479
3480 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3481 let buffer = &display_map.buffer_snapshot;
3482 let position = display_map.clip_point(position, Bias::Left);
3483
3484 let start;
3485 let end;
3486 let mode;
3487 let mut auto_scroll;
3488 match click_count {
3489 1 => {
3490 start = buffer.anchor_before(position.to_point(&display_map));
3491 end = start;
3492 mode = SelectMode::Character;
3493 auto_scroll = true;
3494 }
3495 2 => {
3496 let position = display_map
3497 .clip_point(position, Bias::Left)
3498 .to_offset(&display_map, Bias::Left);
3499 let (range, _) = buffer.surrounding_word(position, false);
3500 start = buffer.anchor_before(range.start);
3501 end = buffer.anchor_before(range.end);
3502 mode = SelectMode::Word(start..end);
3503 auto_scroll = true;
3504 }
3505 3 => {
3506 let position = display_map
3507 .clip_point(position, Bias::Left)
3508 .to_point(&display_map);
3509 let line_start = display_map.prev_line_boundary(position).0;
3510 let next_line_start = buffer.clip_point(
3511 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3512 Bias::Left,
3513 );
3514 start = buffer.anchor_before(line_start);
3515 end = buffer.anchor_before(next_line_start);
3516 mode = SelectMode::Line(start..end);
3517 auto_scroll = true;
3518 }
3519 _ => {
3520 start = buffer.anchor_before(0);
3521 end = buffer.anchor_before(buffer.len());
3522 mode = SelectMode::All;
3523 auto_scroll = false;
3524 }
3525 }
3526 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3527
3528 let point_to_delete: Option<usize> = {
3529 let selected_points: Vec<Selection<Point>> =
3530 self.selections.disjoint_in_range(start..end, cx);
3531
3532 if !add || click_count > 1 {
3533 None
3534 } else if !selected_points.is_empty() {
3535 Some(selected_points[0].id)
3536 } else {
3537 let clicked_point_already_selected =
3538 self.selections.disjoint.iter().find(|selection| {
3539 selection.start.to_point(buffer) == start.to_point(buffer)
3540 || selection.end.to_point(buffer) == end.to_point(buffer)
3541 });
3542
3543 clicked_point_already_selected.map(|selection| selection.id)
3544 }
3545 };
3546
3547 let selections_count = self.selections.count();
3548 let effects = if auto_scroll {
3549 SelectionEffects::default()
3550 } else {
3551 SelectionEffects::no_scroll()
3552 };
3553
3554 self.change_selections(effects, window, cx, |s| {
3555 if let Some(point_to_delete) = point_to_delete {
3556 s.delete(point_to_delete);
3557
3558 if selections_count == 1 {
3559 s.set_pending_anchor_range(start..end, mode);
3560 }
3561 } else {
3562 if !add {
3563 s.clear_disjoint();
3564 }
3565
3566 s.set_pending_anchor_range(start..end, mode);
3567 }
3568 });
3569 }
3570
3571 fn begin_columnar_selection(
3572 &mut self,
3573 position: DisplayPoint,
3574 goal_column: u32,
3575 reset: bool,
3576 mode: ColumnarMode,
3577 window: &mut Window,
3578 cx: &mut Context<Self>,
3579 ) {
3580 if !self.focus_handle.is_focused(window) {
3581 self.last_focused_descendant = None;
3582 window.focus(&self.focus_handle);
3583 }
3584
3585 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3586
3587 if reset {
3588 let pointer_position = display_map
3589 .buffer_snapshot
3590 .anchor_before(position.to_point(&display_map));
3591
3592 self.change_selections(
3593 SelectionEffects::scroll(Autoscroll::newest()),
3594 window,
3595 cx,
3596 |s| {
3597 s.clear_disjoint();
3598 s.set_pending_anchor_range(
3599 pointer_position..pointer_position,
3600 SelectMode::Character,
3601 );
3602 },
3603 );
3604 };
3605
3606 let tail = self.selections.newest::<Point>(cx).tail();
3607 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3608 self.columnar_selection_state = match mode {
3609 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3610 selection_tail: selection_anchor,
3611 display_point: if reset {
3612 if position.column() != goal_column {
3613 Some(DisplayPoint::new(position.row(), goal_column))
3614 } else {
3615 None
3616 }
3617 } else {
3618 None
3619 },
3620 }),
3621 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3622 selection_tail: selection_anchor,
3623 }),
3624 };
3625
3626 if !reset {
3627 self.select_columns(position, goal_column, &display_map, window, cx);
3628 }
3629 }
3630
3631 fn update_selection(
3632 &mut self,
3633 position: DisplayPoint,
3634 goal_column: u32,
3635 scroll_delta: gpui::Point<f32>,
3636 window: &mut Window,
3637 cx: &mut Context<Self>,
3638 ) {
3639 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3640
3641 if self.columnar_selection_state.is_some() {
3642 self.select_columns(position, goal_column, &display_map, window, cx);
3643 } else if let Some(mut pending) = self.selections.pending_anchor() {
3644 let buffer = &display_map.buffer_snapshot;
3645 let head;
3646 let tail;
3647 let mode = self.selections.pending_mode().unwrap();
3648 match &mode {
3649 SelectMode::Character => {
3650 head = position.to_point(&display_map);
3651 tail = pending.tail().to_point(buffer);
3652 }
3653 SelectMode::Word(original_range) => {
3654 let offset = display_map
3655 .clip_point(position, Bias::Left)
3656 .to_offset(&display_map, Bias::Left);
3657 let original_range = original_range.to_offset(buffer);
3658
3659 let head_offset = if buffer.is_inside_word(offset, false)
3660 || original_range.contains(&offset)
3661 {
3662 let (word_range, _) = buffer.surrounding_word(offset, false);
3663 if word_range.start < original_range.start {
3664 word_range.start
3665 } else {
3666 word_range.end
3667 }
3668 } else {
3669 offset
3670 };
3671
3672 head = head_offset.to_point(buffer);
3673 if head_offset <= original_range.start {
3674 tail = original_range.end.to_point(buffer);
3675 } else {
3676 tail = original_range.start.to_point(buffer);
3677 }
3678 }
3679 SelectMode::Line(original_range) => {
3680 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3681
3682 let position = display_map
3683 .clip_point(position, Bias::Left)
3684 .to_point(&display_map);
3685 let line_start = display_map.prev_line_boundary(position).0;
3686 let next_line_start = buffer.clip_point(
3687 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3688 Bias::Left,
3689 );
3690
3691 if line_start < original_range.start {
3692 head = line_start
3693 } else {
3694 head = next_line_start
3695 }
3696
3697 if head <= original_range.start {
3698 tail = original_range.end;
3699 } else {
3700 tail = original_range.start;
3701 }
3702 }
3703 SelectMode::All => {
3704 return;
3705 }
3706 };
3707
3708 if head < tail {
3709 pending.start = buffer.anchor_before(head);
3710 pending.end = buffer.anchor_before(tail);
3711 pending.reversed = true;
3712 } else {
3713 pending.start = buffer.anchor_before(tail);
3714 pending.end = buffer.anchor_before(head);
3715 pending.reversed = false;
3716 }
3717
3718 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3719 s.set_pending(pending, mode);
3720 });
3721 } else {
3722 log::error!("update_selection dispatched with no pending selection");
3723 return;
3724 }
3725
3726 self.apply_scroll_delta(scroll_delta, window, cx);
3727 cx.notify();
3728 }
3729
3730 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3731 self.columnar_selection_state.take();
3732 if self.selections.pending_anchor().is_some() {
3733 let selections = self.selections.all::<usize>(cx);
3734 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3735 s.select(selections);
3736 s.clear_pending();
3737 });
3738 }
3739 }
3740
3741 fn select_columns(
3742 &mut self,
3743 head: DisplayPoint,
3744 goal_column: u32,
3745 display_map: &DisplaySnapshot,
3746 window: &mut Window,
3747 cx: &mut Context<Self>,
3748 ) {
3749 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3750 return;
3751 };
3752
3753 let tail = match columnar_state {
3754 ColumnarSelectionState::FromMouse {
3755 selection_tail,
3756 display_point,
3757 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3758 ColumnarSelectionState::FromSelection { selection_tail } => {
3759 selection_tail.to_display_point(display_map)
3760 }
3761 };
3762
3763 let start_row = cmp::min(tail.row(), head.row());
3764 let end_row = cmp::max(tail.row(), head.row());
3765 let start_column = cmp::min(tail.column(), goal_column);
3766 let end_column = cmp::max(tail.column(), goal_column);
3767 let reversed = start_column < tail.column();
3768
3769 let selection_ranges = (start_row.0..=end_row.0)
3770 .map(DisplayRow)
3771 .filter_map(|row| {
3772 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3773 || start_column <= display_map.line_len(row))
3774 && !display_map.is_block_line(row)
3775 {
3776 let start = display_map
3777 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3778 .to_point(display_map);
3779 let end = display_map
3780 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3781 .to_point(display_map);
3782 if reversed {
3783 Some(end..start)
3784 } else {
3785 Some(start..end)
3786 }
3787 } else {
3788 None
3789 }
3790 })
3791 .collect::<Vec<_>>();
3792
3793 let ranges = match columnar_state {
3794 ColumnarSelectionState::FromMouse { .. } => {
3795 let mut non_empty_ranges = selection_ranges
3796 .iter()
3797 .filter(|selection_range| selection_range.start != selection_range.end)
3798 .peekable();
3799 if non_empty_ranges.peek().is_some() {
3800 non_empty_ranges.cloned().collect()
3801 } else {
3802 selection_ranges
3803 }
3804 }
3805 _ => selection_ranges,
3806 };
3807
3808 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3809 s.select_ranges(ranges);
3810 });
3811 cx.notify();
3812 }
3813
3814 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3815 self.selections
3816 .all_adjusted(cx)
3817 .iter()
3818 .any(|selection| !selection.is_empty())
3819 }
3820
3821 pub fn has_pending_nonempty_selection(&self) -> bool {
3822 let pending_nonempty_selection = match self.selections.pending_anchor() {
3823 Some(Selection { start, end, .. }) => start != end,
3824 None => false,
3825 };
3826
3827 pending_nonempty_selection
3828 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3829 }
3830
3831 pub fn has_pending_selection(&self) -> bool {
3832 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3833 }
3834
3835 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3836 self.selection_mark_mode = false;
3837 self.selection_drag_state = SelectionDragState::None;
3838
3839 if self.clear_expanded_diff_hunks(cx) {
3840 cx.notify();
3841 return;
3842 }
3843 if self.dismiss_menus_and_popups(true, window, cx) {
3844 return;
3845 }
3846
3847 if self.mode.is_full()
3848 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3849 {
3850 return;
3851 }
3852
3853 cx.propagate();
3854 }
3855
3856 pub fn dismiss_menus_and_popups(
3857 &mut self,
3858 is_user_requested: bool,
3859 window: &mut Window,
3860 cx: &mut Context<Self>,
3861 ) -> bool {
3862 if self.take_rename(false, window, cx).is_some() {
3863 return true;
3864 }
3865
3866 if hide_hover(self, cx) {
3867 return true;
3868 }
3869
3870 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3871 return true;
3872 }
3873
3874 if self.hide_context_menu(window, cx).is_some() {
3875 return true;
3876 }
3877
3878 if self.mouse_context_menu.take().is_some() {
3879 return true;
3880 }
3881
3882 if is_user_requested && self.discard_edit_prediction(true, cx) {
3883 return true;
3884 }
3885
3886 if self.snippet_stack.pop().is_some() {
3887 return true;
3888 }
3889
3890 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3891 self.dismiss_diagnostics(cx);
3892 return true;
3893 }
3894
3895 false
3896 }
3897
3898 fn linked_editing_ranges_for(
3899 &self,
3900 selection: Range<text::Anchor>,
3901 cx: &App,
3902 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3903 if self.linked_edit_ranges.is_empty() {
3904 return None;
3905 }
3906 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3907 selection.end.buffer_id.and_then(|end_buffer_id| {
3908 if selection.start.buffer_id != Some(end_buffer_id) {
3909 return None;
3910 }
3911 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3912 let snapshot = buffer.read(cx).snapshot();
3913 self.linked_edit_ranges
3914 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3915 .map(|ranges| (ranges, snapshot, buffer))
3916 })?;
3917 use text::ToOffset as TO;
3918 // find offset from the start of current range to current cursor position
3919 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3920
3921 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3922 let start_difference = start_offset - start_byte_offset;
3923 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3924 let end_difference = end_offset - start_byte_offset;
3925 // Current range has associated linked ranges.
3926 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3927 for range in linked_ranges.iter() {
3928 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3929 let end_offset = start_offset + end_difference;
3930 let start_offset = start_offset + start_difference;
3931 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3932 continue;
3933 }
3934 if self.selections.disjoint_anchor_ranges().any(|s| {
3935 if s.start.buffer_id != selection.start.buffer_id
3936 || s.end.buffer_id != selection.end.buffer_id
3937 {
3938 return false;
3939 }
3940 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3941 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3942 }) {
3943 continue;
3944 }
3945 let start = buffer_snapshot.anchor_after(start_offset);
3946 let end = buffer_snapshot.anchor_after(end_offset);
3947 linked_edits
3948 .entry(buffer.clone())
3949 .or_default()
3950 .push(start..end);
3951 }
3952 Some(linked_edits)
3953 }
3954
3955 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3956 let text: Arc<str> = text.into();
3957
3958 if self.read_only(cx) {
3959 return;
3960 }
3961
3962 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3963
3964 let selections = self.selections.all_adjusted(cx);
3965 let mut bracket_inserted = false;
3966 let mut edits = Vec::new();
3967 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3968 let mut new_selections = Vec::with_capacity(selections.len());
3969 let mut new_autoclose_regions = Vec::new();
3970 let snapshot = self.buffer.read(cx).read(cx);
3971 let mut clear_linked_edit_ranges = false;
3972
3973 for (selection, autoclose_region) in
3974 self.selections_with_autoclose_regions(selections, &snapshot)
3975 {
3976 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3977 // Determine if the inserted text matches the opening or closing
3978 // bracket of any of this language's bracket pairs.
3979 let mut bracket_pair = None;
3980 let mut is_bracket_pair_start = false;
3981 let mut is_bracket_pair_end = false;
3982 if !text.is_empty() {
3983 let mut bracket_pair_matching_end = None;
3984 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3985 // and they are removing the character that triggered IME popup.
3986 for (pair, enabled) in scope.brackets() {
3987 if !pair.close && !pair.surround {
3988 continue;
3989 }
3990
3991 if enabled && pair.start.ends_with(text.as_ref()) {
3992 let prefix_len = pair.start.len() - text.len();
3993 let preceding_text_matches_prefix = prefix_len == 0
3994 || (selection.start.column >= (prefix_len as u32)
3995 && snapshot.contains_str_at(
3996 Point::new(
3997 selection.start.row,
3998 selection.start.column - (prefix_len as u32),
3999 ),
4000 &pair.start[..prefix_len],
4001 ));
4002 if preceding_text_matches_prefix {
4003 bracket_pair = Some(pair.clone());
4004 is_bracket_pair_start = true;
4005 break;
4006 }
4007 }
4008 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4009 {
4010 // take first bracket pair matching end, but don't break in case a later bracket
4011 // pair matches start
4012 bracket_pair_matching_end = Some(pair.clone());
4013 }
4014 }
4015 if let Some(end) = bracket_pair_matching_end
4016 && bracket_pair.is_none()
4017 {
4018 bracket_pair = Some(end);
4019 is_bracket_pair_end = true;
4020 }
4021 }
4022
4023 if let Some(bracket_pair) = bracket_pair {
4024 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4025 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4026 let auto_surround =
4027 self.use_auto_surround && snapshot_settings.use_auto_surround;
4028 if selection.is_empty() {
4029 if is_bracket_pair_start {
4030 // If the inserted text is a suffix of an opening bracket and the
4031 // selection is preceded by the rest of the opening bracket, then
4032 // insert the closing bracket.
4033 let following_text_allows_autoclose = snapshot
4034 .chars_at(selection.start)
4035 .next()
4036 .map_or(true, |c| scope.should_autoclose_before(c));
4037
4038 let preceding_text_allows_autoclose = selection.start.column == 0
4039 || snapshot.reversed_chars_at(selection.start).next().map_or(
4040 true,
4041 |c| {
4042 bracket_pair.start != bracket_pair.end
4043 || !snapshot
4044 .char_classifier_at(selection.start)
4045 .is_word(c)
4046 },
4047 );
4048
4049 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4050 && bracket_pair.start.len() == 1
4051 {
4052 let target = bracket_pair.start.chars().next().unwrap();
4053 let current_line_count = snapshot
4054 .reversed_chars_at(selection.start)
4055 .take_while(|&c| c != '\n')
4056 .filter(|&c| c == target)
4057 .count();
4058 current_line_count % 2 == 1
4059 } else {
4060 false
4061 };
4062
4063 if autoclose
4064 && bracket_pair.close
4065 && following_text_allows_autoclose
4066 && preceding_text_allows_autoclose
4067 && !is_closing_quote
4068 {
4069 let anchor = snapshot.anchor_before(selection.end);
4070 new_selections.push((selection.map(|_| anchor), text.len()));
4071 new_autoclose_regions.push((
4072 anchor,
4073 text.len(),
4074 selection.id,
4075 bracket_pair.clone(),
4076 ));
4077 edits.push((
4078 selection.range(),
4079 format!("{}{}", text, bracket_pair.end).into(),
4080 ));
4081 bracket_inserted = true;
4082 continue;
4083 }
4084 }
4085
4086 if let Some(region) = autoclose_region {
4087 // If the selection is followed by an auto-inserted closing bracket,
4088 // then don't insert that closing bracket again; just move the selection
4089 // past the closing bracket.
4090 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4091 && text.as_ref() == region.pair.end.as_str()
4092 && snapshot.contains_str_at(region.range.end, text.as_ref());
4093 if should_skip {
4094 let anchor = snapshot.anchor_after(selection.end);
4095 new_selections
4096 .push((selection.map(|_| anchor), region.pair.end.len()));
4097 continue;
4098 }
4099 }
4100
4101 let always_treat_brackets_as_autoclosed = snapshot
4102 .language_settings_at(selection.start, cx)
4103 .always_treat_brackets_as_autoclosed;
4104 if always_treat_brackets_as_autoclosed
4105 && is_bracket_pair_end
4106 && snapshot.contains_str_at(selection.end, text.as_ref())
4107 {
4108 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4109 // and the inserted text is a closing bracket and the selection is followed
4110 // by the closing bracket then move the selection past the closing bracket.
4111 let anchor = snapshot.anchor_after(selection.end);
4112 new_selections.push((selection.map(|_| anchor), text.len()));
4113 continue;
4114 }
4115 }
4116 // If an opening bracket is 1 character long and is typed while
4117 // text is selected, then surround that text with the bracket pair.
4118 else if auto_surround
4119 && bracket_pair.surround
4120 && is_bracket_pair_start
4121 && bracket_pair.start.chars().count() == 1
4122 {
4123 edits.push((selection.start..selection.start, text.clone()));
4124 edits.push((
4125 selection.end..selection.end,
4126 bracket_pair.end.as_str().into(),
4127 ));
4128 bracket_inserted = true;
4129 new_selections.push((
4130 Selection {
4131 id: selection.id,
4132 start: snapshot.anchor_after(selection.start),
4133 end: snapshot.anchor_before(selection.end),
4134 reversed: selection.reversed,
4135 goal: selection.goal,
4136 },
4137 0,
4138 ));
4139 continue;
4140 }
4141 }
4142 }
4143
4144 if self.auto_replace_emoji_shortcode
4145 && selection.is_empty()
4146 && text.as_ref().ends_with(':')
4147 && let Some(possible_emoji_short_code) =
4148 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4149 && !possible_emoji_short_code.is_empty()
4150 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4151 {
4152 let emoji_shortcode_start = Point::new(
4153 selection.start.row,
4154 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4155 );
4156
4157 // Remove shortcode from buffer
4158 edits.push((
4159 emoji_shortcode_start..selection.start,
4160 "".to_string().into(),
4161 ));
4162 new_selections.push((
4163 Selection {
4164 id: selection.id,
4165 start: snapshot.anchor_after(emoji_shortcode_start),
4166 end: snapshot.anchor_before(selection.start),
4167 reversed: selection.reversed,
4168 goal: selection.goal,
4169 },
4170 0,
4171 ));
4172
4173 // Insert emoji
4174 let selection_start_anchor = snapshot.anchor_after(selection.start);
4175 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4176 edits.push((selection.start..selection.end, emoji.to_string().into()));
4177
4178 continue;
4179 }
4180
4181 // If not handling any auto-close operation, then just replace the selected
4182 // text with the given input and move the selection to the end of the
4183 // newly inserted text.
4184 let anchor = snapshot.anchor_after(selection.end);
4185 if !self.linked_edit_ranges.is_empty() {
4186 let start_anchor = snapshot.anchor_before(selection.start);
4187
4188 let is_word_char = text.chars().next().map_or(true, |char| {
4189 let classifier = snapshot
4190 .char_classifier_at(start_anchor.to_offset(&snapshot))
4191 .ignore_punctuation(true);
4192 classifier.is_word(char)
4193 });
4194
4195 if is_word_char {
4196 if let Some(ranges) = self
4197 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4198 {
4199 for (buffer, edits) in ranges {
4200 linked_edits
4201 .entry(buffer.clone())
4202 .or_default()
4203 .extend(edits.into_iter().map(|range| (range, text.clone())));
4204 }
4205 }
4206 } else {
4207 clear_linked_edit_ranges = true;
4208 }
4209 }
4210
4211 new_selections.push((selection.map(|_| anchor), 0));
4212 edits.push((selection.start..selection.end, text.clone()));
4213 }
4214
4215 drop(snapshot);
4216
4217 self.transact(window, cx, |this, window, cx| {
4218 if clear_linked_edit_ranges {
4219 this.linked_edit_ranges.clear();
4220 }
4221 let initial_buffer_versions =
4222 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4223
4224 this.buffer.update(cx, |buffer, cx| {
4225 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4226 });
4227 for (buffer, edits) in linked_edits {
4228 buffer.update(cx, |buffer, cx| {
4229 let snapshot = buffer.snapshot();
4230 let edits = edits
4231 .into_iter()
4232 .map(|(range, text)| {
4233 use text::ToPoint as TP;
4234 let end_point = TP::to_point(&range.end, &snapshot);
4235 let start_point = TP::to_point(&range.start, &snapshot);
4236 (start_point..end_point, text)
4237 })
4238 .sorted_by_key(|(range, _)| range.start);
4239 buffer.edit(edits, None, cx);
4240 })
4241 }
4242 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4243 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4244 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4245 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4246 .zip(new_selection_deltas)
4247 .map(|(selection, delta)| Selection {
4248 id: selection.id,
4249 start: selection.start + delta,
4250 end: selection.end + delta,
4251 reversed: selection.reversed,
4252 goal: SelectionGoal::None,
4253 })
4254 .collect::<Vec<_>>();
4255
4256 let mut i = 0;
4257 for (position, delta, selection_id, pair) in new_autoclose_regions {
4258 let position = position.to_offset(&map.buffer_snapshot) + delta;
4259 let start = map.buffer_snapshot.anchor_before(position);
4260 let end = map.buffer_snapshot.anchor_after(position);
4261 while let Some(existing_state) = this.autoclose_regions.get(i) {
4262 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4263 Ordering::Less => i += 1,
4264 Ordering::Greater => break,
4265 Ordering::Equal => {
4266 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4267 Ordering::Less => i += 1,
4268 Ordering::Equal => break,
4269 Ordering::Greater => break,
4270 }
4271 }
4272 }
4273 }
4274 this.autoclose_regions.insert(
4275 i,
4276 AutocloseRegion {
4277 selection_id,
4278 range: start..end,
4279 pair,
4280 },
4281 );
4282 }
4283
4284 let had_active_edit_prediction = this.has_active_edit_prediction();
4285 this.change_selections(
4286 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4287 window,
4288 cx,
4289 |s| s.select(new_selections),
4290 );
4291
4292 if !bracket_inserted
4293 && let Some(on_type_format_task) =
4294 this.trigger_on_type_formatting(text.to_string(), window, cx)
4295 {
4296 on_type_format_task.detach_and_log_err(cx);
4297 }
4298
4299 let editor_settings = EditorSettings::get_global(cx);
4300 if bracket_inserted
4301 && (editor_settings.auto_signature_help
4302 || editor_settings.show_signature_help_after_edits)
4303 {
4304 this.show_signature_help(&ShowSignatureHelp, window, cx);
4305 }
4306
4307 let trigger_in_words =
4308 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4309 if this.hard_wrap.is_some() {
4310 let latest: Range<Point> = this.selections.newest(cx).range();
4311 if latest.is_empty()
4312 && this
4313 .buffer()
4314 .read(cx)
4315 .snapshot(cx)
4316 .line_len(MultiBufferRow(latest.start.row))
4317 == latest.start.column
4318 {
4319 this.rewrap_impl(
4320 RewrapOptions {
4321 override_language_settings: true,
4322 preserve_existing_whitespace: true,
4323 },
4324 cx,
4325 )
4326 }
4327 }
4328 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4329 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4330 this.refresh_edit_prediction(true, false, window, cx);
4331 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4332 });
4333 }
4334
4335 fn find_possible_emoji_shortcode_at_position(
4336 snapshot: &MultiBufferSnapshot,
4337 position: Point,
4338 ) -> Option<String> {
4339 let mut chars = Vec::new();
4340 let mut found_colon = false;
4341 for char in snapshot.reversed_chars_at(position).take(100) {
4342 // Found a possible emoji shortcode in the middle of the buffer
4343 if found_colon {
4344 if char.is_whitespace() {
4345 chars.reverse();
4346 return Some(chars.iter().collect());
4347 }
4348 // If the previous character is not a whitespace, we are in the middle of a word
4349 // and we only want to complete the shortcode if the word is made up of other emojis
4350 let mut containing_word = String::new();
4351 for ch in snapshot
4352 .reversed_chars_at(position)
4353 .skip(chars.len() + 1)
4354 .take(100)
4355 {
4356 if ch.is_whitespace() {
4357 break;
4358 }
4359 containing_word.push(ch);
4360 }
4361 let containing_word = containing_word.chars().rev().collect::<String>();
4362 if util::word_consists_of_emojis(containing_word.as_str()) {
4363 chars.reverse();
4364 return Some(chars.iter().collect());
4365 }
4366 }
4367
4368 if char.is_whitespace() || !char.is_ascii() {
4369 return None;
4370 }
4371 if char == ':' {
4372 found_colon = true;
4373 } else {
4374 chars.push(char);
4375 }
4376 }
4377 // Found a possible emoji shortcode at the beginning of the buffer
4378 chars.reverse();
4379 Some(chars.iter().collect())
4380 }
4381
4382 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4383 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4384 self.transact(window, cx, |this, window, cx| {
4385 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4386 let selections = this.selections.all::<usize>(cx);
4387 let multi_buffer = this.buffer.read(cx);
4388 let buffer = multi_buffer.snapshot(cx);
4389 selections
4390 .iter()
4391 .map(|selection| {
4392 let start_point = selection.start.to_point(&buffer);
4393 let mut existing_indent =
4394 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4395 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4396 let start = selection.start;
4397 let end = selection.end;
4398 let selection_is_empty = start == end;
4399 let language_scope = buffer.language_scope_at(start);
4400 let (
4401 comment_delimiter,
4402 doc_delimiter,
4403 insert_extra_newline,
4404 indent_on_newline,
4405 indent_on_extra_newline,
4406 ) = if let Some(language) = &language_scope {
4407 let mut insert_extra_newline =
4408 insert_extra_newline_brackets(&buffer, start..end, language)
4409 || insert_extra_newline_tree_sitter(&buffer, start..end);
4410
4411 // Comment extension on newline is allowed only for cursor selections
4412 let comment_delimiter = maybe!({
4413 if !selection_is_empty {
4414 return None;
4415 }
4416
4417 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4418 return None;
4419 }
4420
4421 let delimiters = language.line_comment_prefixes();
4422 let max_len_of_delimiter =
4423 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4424 let (snapshot, range) =
4425 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4426
4427 let num_of_whitespaces = snapshot
4428 .chars_for_range(range.clone())
4429 .take_while(|c| c.is_whitespace())
4430 .count();
4431 let comment_candidate = snapshot
4432 .chars_for_range(range.clone())
4433 .skip(num_of_whitespaces)
4434 .take(max_len_of_delimiter)
4435 .collect::<String>();
4436 let (delimiter, trimmed_len) = delimiters
4437 .iter()
4438 .filter_map(|delimiter| {
4439 let prefix = delimiter.trim_end();
4440 if comment_candidate.starts_with(prefix) {
4441 Some((delimiter, prefix.len()))
4442 } else {
4443 None
4444 }
4445 })
4446 .max_by_key(|(_, len)| *len)?;
4447
4448 if let Some(BlockCommentConfig {
4449 start: block_start, ..
4450 }) = language.block_comment()
4451 {
4452 let block_start_trimmed = block_start.trim_end();
4453 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4454 let line_content = snapshot
4455 .chars_for_range(range)
4456 .skip(num_of_whitespaces)
4457 .take(block_start_trimmed.len())
4458 .collect::<String>();
4459
4460 if line_content.starts_with(block_start_trimmed) {
4461 return None;
4462 }
4463 }
4464 }
4465
4466 let cursor_is_placed_after_comment_marker =
4467 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4468 if cursor_is_placed_after_comment_marker {
4469 Some(delimiter.clone())
4470 } else {
4471 None
4472 }
4473 });
4474
4475 let mut indent_on_newline = IndentSize::spaces(0);
4476 let mut indent_on_extra_newline = IndentSize::spaces(0);
4477
4478 let doc_delimiter = maybe!({
4479 if !selection_is_empty {
4480 return None;
4481 }
4482
4483 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4484 return None;
4485 }
4486
4487 let BlockCommentConfig {
4488 start: start_tag,
4489 end: end_tag,
4490 prefix: delimiter,
4491 tab_size: len,
4492 } = language.documentation_comment()?;
4493 let is_within_block_comment = buffer
4494 .language_scope_at(start_point)
4495 .is_some_and(|scope| scope.override_name() == Some("comment"));
4496 if !is_within_block_comment {
4497 return None;
4498 }
4499
4500 let (snapshot, range) =
4501 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4502
4503 let num_of_whitespaces = snapshot
4504 .chars_for_range(range.clone())
4505 .take_while(|c| c.is_whitespace())
4506 .count();
4507
4508 // 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.
4509 let column = start_point.column;
4510 let cursor_is_after_start_tag = {
4511 let start_tag_len = start_tag.len();
4512 let start_tag_line = snapshot
4513 .chars_for_range(range.clone())
4514 .skip(num_of_whitespaces)
4515 .take(start_tag_len)
4516 .collect::<String>();
4517 if start_tag_line.starts_with(start_tag.as_ref()) {
4518 num_of_whitespaces + start_tag_len <= column as usize
4519 } else {
4520 false
4521 }
4522 };
4523
4524 let cursor_is_after_delimiter = {
4525 let delimiter_trim = delimiter.trim_end();
4526 let delimiter_line = snapshot
4527 .chars_for_range(range.clone())
4528 .skip(num_of_whitespaces)
4529 .take(delimiter_trim.len())
4530 .collect::<String>();
4531 if delimiter_line.starts_with(delimiter_trim) {
4532 num_of_whitespaces + delimiter_trim.len() <= column as usize
4533 } else {
4534 false
4535 }
4536 };
4537
4538 let cursor_is_before_end_tag_if_exists = {
4539 let mut char_position = 0u32;
4540 let mut end_tag_offset = None;
4541
4542 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4543 if let Some(byte_pos) = chunk.find(&**end_tag) {
4544 let chars_before_match =
4545 chunk[..byte_pos].chars().count() as u32;
4546 end_tag_offset =
4547 Some(char_position + chars_before_match);
4548 break 'outer;
4549 }
4550 char_position += chunk.chars().count() as u32;
4551 }
4552
4553 if let Some(end_tag_offset) = end_tag_offset {
4554 let cursor_is_before_end_tag = column <= end_tag_offset;
4555 if cursor_is_after_start_tag {
4556 if cursor_is_before_end_tag {
4557 insert_extra_newline = true;
4558 }
4559 let cursor_is_at_start_of_end_tag =
4560 column == end_tag_offset;
4561 if cursor_is_at_start_of_end_tag {
4562 indent_on_extra_newline.len = *len;
4563 }
4564 }
4565 cursor_is_before_end_tag
4566 } else {
4567 true
4568 }
4569 };
4570
4571 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4572 && cursor_is_before_end_tag_if_exists
4573 {
4574 if cursor_is_after_start_tag {
4575 indent_on_newline.len = *len;
4576 }
4577 Some(delimiter.clone())
4578 } else {
4579 None
4580 }
4581 });
4582
4583 (
4584 comment_delimiter,
4585 doc_delimiter,
4586 insert_extra_newline,
4587 indent_on_newline,
4588 indent_on_extra_newline,
4589 )
4590 } else {
4591 (
4592 None,
4593 None,
4594 false,
4595 IndentSize::default(),
4596 IndentSize::default(),
4597 )
4598 };
4599
4600 let prevent_auto_indent = doc_delimiter.is_some();
4601 let delimiter = comment_delimiter.or(doc_delimiter);
4602
4603 let capacity_for_delimiter =
4604 delimiter.as_deref().map(str::len).unwrap_or_default();
4605 let mut new_text = String::with_capacity(
4606 1 + capacity_for_delimiter
4607 + existing_indent.len as usize
4608 + indent_on_newline.len as usize
4609 + indent_on_extra_newline.len as usize,
4610 );
4611 new_text.push('\n');
4612 new_text.extend(existing_indent.chars());
4613 new_text.extend(indent_on_newline.chars());
4614
4615 if let Some(delimiter) = &delimiter {
4616 new_text.push_str(delimiter);
4617 }
4618
4619 if insert_extra_newline {
4620 new_text.push('\n');
4621 new_text.extend(existing_indent.chars());
4622 new_text.extend(indent_on_extra_newline.chars());
4623 }
4624
4625 let anchor = buffer.anchor_after(end);
4626 let new_selection = selection.map(|_| anchor);
4627 (
4628 ((start..end, new_text), prevent_auto_indent),
4629 (insert_extra_newline, new_selection),
4630 )
4631 })
4632 .unzip()
4633 };
4634
4635 let mut auto_indent_edits = Vec::new();
4636 let mut edits = Vec::new();
4637 for (edit, prevent_auto_indent) in edits_with_flags {
4638 if prevent_auto_indent {
4639 edits.push(edit);
4640 } else {
4641 auto_indent_edits.push(edit);
4642 }
4643 }
4644 if !edits.is_empty() {
4645 this.edit(edits, cx);
4646 }
4647 if !auto_indent_edits.is_empty() {
4648 this.edit_with_autoindent(auto_indent_edits, cx);
4649 }
4650
4651 let buffer = this.buffer.read(cx).snapshot(cx);
4652 let new_selections = selection_info
4653 .into_iter()
4654 .map(|(extra_newline_inserted, new_selection)| {
4655 let mut cursor = new_selection.end.to_point(&buffer);
4656 if extra_newline_inserted {
4657 cursor.row -= 1;
4658 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4659 }
4660 new_selection.map(|_| cursor)
4661 })
4662 .collect();
4663
4664 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4665 this.refresh_edit_prediction(true, false, window, cx);
4666 });
4667 }
4668
4669 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4670 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4671
4672 let buffer = self.buffer.read(cx);
4673 let snapshot = buffer.snapshot(cx);
4674
4675 let mut edits = Vec::new();
4676 let mut rows = Vec::new();
4677
4678 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4679 let cursor = selection.head();
4680 let row = cursor.row;
4681
4682 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4683
4684 let newline = "\n".to_string();
4685 edits.push((start_of_line..start_of_line, newline));
4686
4687 rows.push(row + rows_inserted as u32);
4688 }
4689
4690 self.transact(window, cx, |editor, window, cx| {
4691 editor.edit(edits, cx);
4692
4693 editor.change_selections(Default::default(), window, cx, |s| {
4694 let mut index = 0;
4695 s.move_cursors_with(|map, _, _| {
4696 let row = rows[index];
4697 index += 1;
4698
4699 let point = Point::new(row, 0);
4700 let boundary = map.next_line_boundary(point).1;
4701 let clipped = map.clip_point(boundary, Bias::Left);
4702
4703 (clipped, SelectionGoal::None)
4704 });
4705 });
4706
4707 let mut indent_edits = Vec::new();
4708 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4709 for row in rows {
4710 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4711 for (row, indent) in indents {
4712 if indent.len == 0 {
4713 continue;
4714 }
4715
4716 let text = match indent.kind {
4717 IndentKind::Space => " ".repeat(indent.len as usize),
4718 IndentKind::Tab => "\t".repeat(indent.len as usize),
4719 };
4720 let point = Point::new(row.0, 0);
4721 indent_edits.push((point..point, text));
4722 }
4723 }
4724 editor.edit(indent_edits, cx);
4725 });
4726 }
4727
4728 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4729 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4730
4731 let buffer = self.buffer.read(cx);
4732 let snapshot = buffer.snapshot(cx);
4733
4734 let mut edits = Vec::new();
4735 let mut rows = Vec::new();
4736 let mut rows_inserted = 0;
4737
4738 for selection in self.selections.all_adjusted(cx) {
4739 let cursor = selection.head();
4740 let row = cursor.row;
4741
4742 let point = Point::new(row + 1, 0);
4743 let start_of_line = snapshot.clip_point(point, Bias::Left);
4744
4745 let newline = "\n".to_string();
4746 edits.push((start_of_line..start_of_line, newline));
4747
4748 rows_inserted += 1;
4749 rows.push(row + rows_inserted);
4750 }
4751
4752 self.transact(window, cx, |editor, window, cx| {
4753 editor.edit(edits, cx);
4754
4755 editor.change_selections(Default::default(), window, cx, |s| {
4756 let mut index = 0;
4757 s.move_cursors_with(|map, _, _| {
4758 let row = rows[index];
4759 index += 1;
4760
4761 let point = Point::new(row, 0);
4762 let boundary = map.next_line_boundary(point).1;
4763 let clipped = map.clip_point(boundary, Bias::Left);
4764
4765 (clipped, SelectionGoal::None)
4766 });
4767 });
4768
4769 let mut indent_edits = Vec::new();
4770 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4771 for row in rows {
4772 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4773 for (row, indent) in indents {
4774 if indent.len == 0 {
4775 continue;
4776 }
4777
4778 let text = match indent.kind {
4779 IndentKind::Space => " ".repeat(indent.len as usize),
4780 IndentKind::Tab => "\t".repeat(indent.len as usize),
4781 };
4782 let point = Point::new(row.0, 0);
4783 indent_edits.push((point..point, text));
4784 }
4785 }
4786 editor.edit(indent_edits, cx);
4787 });
4788 }
4789
4790 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4791 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4792 original_indent_columns: Vec::new(),
4793 });
4794 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4795 }
4796
4797 fn insert_with_autoindent_mode(
4798 &mut self,
4799 text: &str,
4800 autoindent_mode: Option<AutoindentMode>,
4801 window: &mut Window,
4802 cx: &mut Context<Self>,
4803 ) {
4804 if self.read_only(cx) {
4805 return;
4806 }
4807
4808 let text: Arc<str> = text.into();
4809 self.transact(window, cx, |this, window, cx| {
4810 let old_selections = this.selections.all_adjusted(cx);
4811 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4812 let anchors = {
4813 let snapshot = buffer.read(cx);
4814 old_selections
4815 .iter()
4816 .map(|s| {
4817 let anchor = snapshot.anchor_after(s.head());
4818 s.map(|_| anchor)
4819 })
4820 .collect::<Vec<_>>()
4821 };
4822 buffer.edit(
4823 old_selections
4824 .iter()
4825 .map(|s| (s.start..s.end, text.clone())),
4826 autoindent_mode,
4827 cx,
4828 );
4829 anchors
4830 });
4831
4832 this.change_selections(Default::default(), window, cx, |s| {
4833 s.select_anchors(selection_anchors);
4834 });
4835
4836 cx.notify();
4837 });
4838 }
4839
4840 fn trigger_completion_on_input(
4841 &mut self,
4842 text: &str,
4843 trigger_in_words: bool,
4844 window: &mut Window,
4845 cx: &mut Context<Self>,
4846 ) {
4847 let completions_source = self
4848 .context_menu
4849 .borrow()
4850 .as_ref()
4851 .and_then(|menu| match menu {
4852 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4853 CodeContextMenu::CodeActions(_) => None,
4854 });
4855
4856 match completions_source {
4857 Some(CompletionsMenuSource::Words) => {
4858 self.show_word_completions(&ShowWordCompletions, window, cx)
4859 }
4860 Some(CompletionsMenuSource::Normal)
4861 | Some(CompletionsMenuSource::SnippetChoices)
4862 | None
4863 if self.is_completion_trigger(
4864 text,
4865 trigger_in_words,
4866 completions_source.is_some(),
4867 cx,
4868 ) =>
4869 {
4870 self.show_completions(
4871 &ShowCompletions {
4872 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4873 },
4874 window,
4875 cx,
4876 )
4877 }
4878 _ => {
4879 self.hide_context_menu(window, cx);
4880 }
4881 }
4882 }
4883
4884 fn is_completion_trigger(
4885 &self,
4886 text: &str,
4887 trigger_in_words: bool,
4888 menu_is_open: bool,
4889 cx: &mut Context<Self>,
4890 ) -> bool {
4891 let position = self.selections.newest_anchor().head();
4892 let multibuffer = self.buffer.read(cx);
4893 let Some(buffer) = position
4894 .buffer_id
4895 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4896 else {
4897 return false;
4898 };
4899
4900 if let Some(completion_provider) = &self.completion_provider {
4901 completion_provider.is_completion_trigger(
4902 &buffer,
4903 position.text_anchor,
4904 text,
4905 trigger_in_words,
4906 menu_is_open,
4907 cx,
4908 )
4909 } else {
4910 false
4911 }
4912 }
4913
4914 /// If any empty selections is touching the start of its innermost containing autoclose
4915 /// region, expand it to select the brackets.
4916 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4917 let selections = self.selections.all::<usize>(cx);
4918 let buffer = self.buffer.read(cx).read(cx);
4919 let new_selections = self
4920 .selections_with_autoclose_regions(selections, &buffer)
4921 .map(|(mut selection, region)| {
4922 if !selection.is_empty() {
4923 return selection;
4924 }
4925
4926 if let Some(region) = region {
4927 let mut range = region.range.to_offset(&buffer);
4928 if selection.start == range.start && range.start >= region.pair.start.len() {
4929 range.start -= region.pair.start.len();
4930 if buffer.contains_str_at(range.start, ®ion.pair.start)
4931 && buffer.contains_str_at(range.end, ®ion.pair.end)
4932 {
4933 range.end += region.pair.end.len();
4934 selection.start = range.start;
4935 selection.end = range.end;
4936
4937 return selection;
4938 }
4939 }
4940 }
4941
4942 let always_treat_brackets_as_autoclosed = buffer
4943 .language_settings_at(selection.start, cx)
4944 .always_treat_brackets_as_autoclosed;
4945
4946 if !always_treat_brackets_as_autoclosed {
4947 return selection;
4948 }
4949
4950 if let Some(scope) = buffer.language_scope_at(selection.start) {
4951 for (pair, enabled) in scope.brackets() {
4952 if !enabled || !pair.close {
4953 continue;
4954 }
4955
4956 if buffer.contains_str_at(selection.start, &pair.end) {
4957 let pair_start_len = pair.start.len();
4958 if buffer.contains_str_at(
4959 selection.start.saturating_sub(pair_start_len),
4960 &pair.start,
4961 ) {
4962 selection.start -= pair_start_len;
4963 selection.end += pair.end.len();
4964
4965 return selection;
4966 }
4967 }
4968 }
4969 }
4970
4971 selection
4972 })
4973 .collect();
4974
4975 drop(buffer);
4976 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4977 selections.select(new_selections)
4978 });
4979 }
4980
4981 /// Iterate the given selections, and for each one, find the smallest surrounding
4982 /// autoclose region. This uses the ordering of the selections and the autoclose
4983 /// regions to avoid repeated comparisons.
4984 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4985 &'a self,
4986 selections: impl IntoIterator<Item = Selection<D>>,
4987 buffer: &'a MultiBufferSnapshot,
4988 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4989 let mut i = 0;
4990 let mut regions = self.autoclose_regions.as_slice();
4991 selections.into_iter().map(move |selection| {
4992 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4993
4994 let mut enclosing = None;
4995 while let Some(pair_state) = regions.get(i) {
4996 if pair_state.range.end.to_offset(buffer) < range.start {
4997 regions = ®ions[i + 1..];
4998 i = 0;
4999 } else if pair_state.range.start.to_offset(buffer) > range.end {
5000 break;
5001 } else {
5002 if pair_state.selection_id == selection.id {
5003 enclosing = Some(pair_state);
5004 }
5005 i += 1;
5006 }
5007 }
5008
5009 (selection, enclosing)
5010 })
5011 }
5012
5013 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5014 fn invalidate_autoclose_regions(
5015 &mut self,
5016 mut selections: &[Selection<Anchor>],
5017 buffer: &MultiBufferSnapshot,
5018 ) {
5019 self.autoclose_regions.retain(|state| {
5020 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5021 return false;
5022 }
5023
5024 let mut i = 0;
5025 while let Some(selection) = selections.get(i) {
5026 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5027 selections = &selections[1..];
5028 continue;
5029 }
5030 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5031 break;
5032 }
5033 if selection.id == state.selection_id {
5034 return true;
5035 } else {
5036 i += 1;
5037 }
5038 }
5039 false
5040 });
5041 }
5042
5043 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5044 let offset = position.to_offset(buffer);
5045 let (word_range, kind) = buffer.surrounding_word(offset, true);
5046 if offset > word_range.start && kind == Some(CharKind::Word) {
5047 Some(
5048 buffer
5049 .text_for_range(word_range.start..offset)
5050 .collect::<String>(),
5051 )
5052 } else {
5053 None
5054 }
5055 }
5056
5057 pub fn toggle_inline_values(
5058 &mut self,
5059 _: &ToggleInlineValues,
5060 _: &mut Window,
5061 cx: &mut Context<Self>,
5062 ) {
5063 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5064
5065 self.refresh_inline_values(cx);
5066 }
5067
5068 pub fn toggle_inlay_hints(
5069 &mut self,
5070 _: &ToggleInlayHints,
5071 _: &mut Window,
5072 cx: &mut Context<Self>,
5073 ) {
5074 self.refresh_inlay_hints(
5075 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5076 cx,
5077 );
5078 }
5079
5080 pub fn inlay_hints_enabled(&self) -> bool {
5081 self.inlay_hint_cache.enabled
5082 }
5083
5084 pub fn inline_values_enabled(&self) -> bool {
5085 self.inline_value_cache.enabled
5086 }
5087
5088 #[cfg(any(test, feature = "test-support"))]
5089 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5090 self.display_map
5091 .read(cx)
5092 .current_inlays()
5093 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5094 .cloned()
5095 .collect()
5096 }
5097
5098 #[cfg(any(test, feature = "test-support"))]
5099 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5100 self.display_map
5101 .read(cx)
5102 .current_inlays()
5103 .cloned()
5104 .collect()
5105 }
5106
5107 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5108 if self.semantics_provider.is_none() || !self.mode.is_full() {
5109 return;
5110 }
5111
5112 let reason_description = reason.description();
5113 let ignore_debounce = matches!(
5114 reason,
5115 InlayHintRefreshReason::SettingsChange(_)
5116 | InlayHintRefreshReason::Toggle(_)
5117 | InlayHintRefreshReason::ExcerptsRemoved(_)
5118 | InlayHintRefreshReason::ModifiersChanged(_)
5119 );
5120 let (invalidate_cache, required_languages) = match reason {
5121 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5122 match self.inlay_hint_cache.modifiers_override(enabled) {
5123 Some(enabled) => {
5124 if enabled {
5125 (InvalidationStrategy::RefreshRequested, None)
5126 } else {
5127 self.splice_inlays(
5128 &self
5129 .visible_inlay_hints(cx)
5130 .iter()
5131 .map(|inlay| inlay.id)
5132 .collect::<Vec<InlayId>>(),
5133 Vec::new(),
5134 cx,
5135 );
5136 return;
5137 }
5138 }
5139 None => return,
5140 }
5141 }
5142 InlayHintRefreshReason::Toggle(enabled) => {
5143 if self.inlay_hint_cache.toggle(enabled) {
5144 if enabled {
5145 (InvalidationStrategy::RefreshRequested, None)
5146 } else {
5147 self.splice_inlays(
5148 &self
5149 .visible_inlay_hints(cx)
5150 .iter()
5151 .map(|inlay| inlay.id)
5152 .collect::<Vec<InlayId>>(),
5153 Vec::new(),
5154 cx,
5155 );
5156 return;
5157 }
5158 } else {
5159 return;
5160 }
5161 }
5162 InlayHintRefreshReason::SettingsChange(new_settings) => {
5163 match self.inlay_hint_cache.update_settings(
5164 &self.buffer,
5165 new_settings,
5166 self.visible_inlay_hints(cx),
5167 cx,
5168 ) {
5169 ControlFlow::Break(Some(InlaySplice {
5170 to_remove,
5171 to_insert,
5172 })) => {
5173 self.splice_inlays(&to_remove, to_insert, cx);
5174 return;
5175 }
5176 ControlFlow::Break(None) => return,
5177 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5178 }
5179 }
5180 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5181 if let Some(InlaySplice {
5182 to_remove,
5183 to_insert,
5184 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5185 {
5186 self.splice_inlays(&to_remove, to_insert, cx);
5187 }
5188 self.display_map.update(cx, |display_map, _| {
5189 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5190 });
5191 return;
5192 }
5193 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5194 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5195 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5196 }
5197 InlayHintRefreshReason::RefreshRequested => {
5198 (InvalidationStrategy::RefreshRequested, None)
5199 }
5200 };
5201
5202 if let Some(InlaySplice {
5203 to_remove,
5204 to_insert,
5205 }) = self.inlay_hint_cache.spawn_hint_refresh(
5206 reason_description,
5207 self.visible_excerpts(required_languages.as_ref(), cx),
5208 invalidate_cache,
5209 ignore_debounce,
5210 cx,
5211 ) {
5212 self.splice_inlays(&to_remove, to_insert, cx);
5213 }
5214 }
5215
5216 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5217 self.display_map
5218 .read(cx)
5219 .current_inlays()
5220 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5221 .cloned()
5222 .collect()
5223 }
5224
5225 pub fn visible_excerpts(
5226 &self,
5227 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5228 cx: &mut Context<Editor>,
5229 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5230 let Some(project) = self.project() else {
5231 return HashMap::default();
5232 };
5233 let project = project.read(cx);
5234 let multi_buffer = self.buffer().read(cx);
5235 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5236 let multi_buffer_visible_start = self
5237 .scroll_manager
5238 .anchor()
5239 .anchor
5240 .to_point(&multi_buffer_snapshot);
5241 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5242 multi_buffer_visible_start
5243 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5244 Bias::Left,
5245 );
5246 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5247 multi_buffer_snapshot
5248 .range_to_buffer_ranges(multi_buffer_visible_range)
5249 .into_iter()
5250 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5251 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5252 let buffer_file = project::File::from_dyn(buffer.file())?;
5253 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5254 let worktree_entry = buffer_worktree
5255 .read(cx)
5256 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5257 if worktree_entry.is_ignored {
5258 return None;
5259 }
5260
5261 let language = buffer.language()?;
5262 if let Some(restrict_to_languages) = restrict_to_languages
5263 && !restrict_to_languages.contains(language)
5264 {
5265 return None;
5266 }
5267 Some((
5268 excerpt_id,
5269 (
5270 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5271 buffer.version().clone(),
5272 excerpt_visible_range,
5273 ),
5274 ))
5275 })
5276 .collect()
5277 }
5278
5279 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5280 TextLayoutDetails {
5281 text_system: window.text_system().clone(),
5282 editor_style: self.style.clone().unwrap(),
5283 rem_size: window.rem_size(),
5284 scroll_anchor: self.scroll_manager.anchor(),
5285 visible_rows: self.visible_line_count(),
5286 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5287 }
5288 }
5289
5290 pub fn splice_inlays(
5291 &self,
5292 to_remove: &[InlayId],
5293 to_insert: Vec<Inlay>,
5294 cx: &mut Context<Self>,
5295 ) {
5296 self.display_map.update(cx, |display_map, cx| {
5297 display_map.splice_inlays(to_remove, to_insert, cx)
5298 });
5299 cx.notify();
5300 }
5301
5302 fn trigger_on_type_formatting(
5303 &self,
5304 input: String,
5305 window: &mut Window,
5306 cx: &mut Context<Self>,
5307 ) -> Option<Task<Result<()>>> {
5308 if input.len() != 1 {
5309 return None;
5310 }
5311
5312 let project = self.project()?;
5313 let position = self.selections.newest_anchor().head();
5314 let (buffer, buffer_position) = self
5315 .buffer
5316 .read(cx)
5317 .text_anchor_for_position(position, cx)?;
5318
5319 let settings = language_settings::language_settings(
5320 buffer
5321 .read(cx)
5322 .language_at(buffer_position)
5323 .map(|l| l.name()),
5324 buffer.read(cx).file(),
5325 cx,
5326 );
5327 if !settings.use_on_type_format {
5328 return None;
5329 }
5330
5331 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5332 // hence we do LSP request & edit on host side only — add formats to host's history.
5333 let push_to_lsp_host_history = true;
5334 // If this is not the host, append its history with new edits.
5335 let push_to_client_history = project.read(cx).is_via_collab();
5336
5337 let on_type_formatting = project.update(cx, |project, cx| {
5338 project.on_type_format(
5339 buffer.clone(),
5340 buffer_position,
5341 input,
5342 push_to_lsp_host_history,
5343 cx,
5344 )
5345 });
5346 Some(cx.spawn_in(window, async move |editor, cx| {
5347 if let Some(transaction) = on_type_formatting.await? {
5348 if push_to_client_history {
5349 buffer
5350 .update(cx, |buffer, _| {
5351 buffer.push_transaction(transaction, Instant::now());
5352 buffer.finalize_last_transaction();
5353 })
5354 .ok();
5355 }
5356 editor.update(cx, |editor, cx| {
5357 editor.refresh_document_highlights(cx);
5358 })?;
5359 }
5360 Ok(())
5361 }))
5362 }
5363
5364 pub fn show_word_completions(
5365 &mut self,
5366 _: &ShowWordCompletions,
5367 window: &mut Window,
5368 cx: &mut Context<Self>,
5369 ) {
5370 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5371 }
5372
5373 pub fn show_completions(
5374 &mut self,
5375 options: &ShowCompletions,
5376 window: &mut Window,
5377 cx: &mut Context<Self>,
5378 ) {
5379 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5380 }
5381
5382 fn open_or_update_completions_menu(
5383 &mut self,
5384 requested_source: Option<CompletionsMenuSource>,
5385 trigger: Option<&str>,
5386 window: &mut Window,
5387 cx: &mut Context<Self>,
5388 ) {
5389 if self.pending_rename.is_some() {
5390 return;
5391 }
5392
5393 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5394
5395 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5396 // inserted and selected. To handle that case, the start of the selection is used so that
5397 // the menu starts with all choices.
5398 let position = self
5399 .selections
5400 .newest_anchor()
5401 .start
5402 .bias_right(&multibuffer_snapshot);
5403 if position.diff_base_anchor.is_some() {
5404 return;
5405 }
5406 let (buffer, buffer_position) =
5407 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5408 output
5409 } else {
5410 return;
5411 };
5412 let buffer_snapshot = buffer.read(cx).snapshot();
5413
5414 let query: Option<Arc<String>> =
5415 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5416
5417 drop(multibuffer_snapshot);
5418
5419 let provider = match requested_source {
5420 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5421 Some(CompletionsMenuSource::Words) => None,
5422 Some(CompletionsMenuSource::SnippetChoices) => {
5423 log::error!("bug: SnippetChoices requested_source is not handled");
5424 None
5425 }
5426 };
5427
5428 let sort_completions = provider
5429 .as_ref()
5430 .map_or(false, |provider| provider.sort_completions());
5431
5432 let filter_completions = provider
5433 .as_ref()
5434 .map_or(true, |provider| provider.filter_completions());
5435
5436 let trigger_kind = match trigger {
5437 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5438 CompletionTriggerKind::TRIGGER_CHARACTER
5439 }
5440 _ => CompletionTriggerKind::INVOKED,
5441 };
5442 let completion_context = CompletionContext {
5443 trigger_character: trigger.and_then(|trigger| {
5444 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5445 Some(String::from(trigger))
5446 } else {
5447 None
5448 }
5449 }),
5450 trigger_kind,
5451 };
5452
5453 // Hide the current completions menu when a trigger char is typed. Without this, cached
5454 // completions from before the trigger char may be reused (#32774). Snippet choices could
5455 // involve trigger chars, so this is skipped in that case.
5456 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5457 {
5458 let menu_is_open = matches!(
5459 self.context_menu.borrow().as_ref(),
5460 Some(CodeContextMenu::Completions(_))
5461 );
5462 if menu_is_open {
5463 self.hide_context_menu(window, cx);
5464 }
5465 }
5466
5467 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5468 if filter_completions {
5469 menu.filter(query.clone(), provider.clone(), window, cx);
5470 }
5471 // When `is_incomplete` is false, no need to re-query completions when the current query
5472 // is a suffix of the initial query.
5473 if !menu.is_incomplete {
5474 // If the new query is a suffix of the old query (typing more characters) and
5475 // the previous result was complete, the existing completions can be filtered.
5476 //
5477 // Note that this is always true for snippet completions.
5478 let query_matches = match (&menu.initial_query, &query) {
5479 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5480 (None, _) => true,
5481 _ => false,
5482 };
5483 if query_matches {
5484 let position_matches = if menu.initial_position == position {
5485 true
5486 } else {
5487 let snapshot = self.buffer.read(cx).read(cx);
5488 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5489 };
5490 if position_matches {
5491 return;
5492 }
5493 }
5494 }
5495 };
5496
5497 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5498 buffer_snapshot.surrounding_word(buffer_position, false)
5499 {
5500 let word_to_exclude = buffer_snapshot
5501 .text_for_range(word_range.clone())
5502 .collect::<String>();
5503 (
5504 buffer_snapshot.anchor_before(word_range.start)
5505 ..buffer_snapshot.anchor_after(buffer_position),
5506 Some(word_to_exclude),
5507 )
5508 } else {
5509 (buffer_position..buffer_position, None)
5510 };
5511
5512 let language = buffer_snapshot
5513 .language_at(buffer_position)
5514 .map(|language| language.name());
5515
5516 let completion_settings =
5517 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5518
5519 let show_completion_documentation = buffer_snapshot
5520 .settings_at(buffer_position, cx)
5521 .show_completion_documentation;
5522
5523 // The document can be large, so stay in reasonable bounds when searching for words,
5524 // otherwise completion pop-up might be slow to appear.
5525 const WORD_LOOKUP_ROWS: u32 = 5_000;
5526 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5527 let min_word_search = buffer_snapshot.clip_point(
5528 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5529 Bias::Left,
5530 );
5531 let max_word_search = buffer_snapshot.clip_point(
5532 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5533 Bias::Right,
5534 );
5535 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5536 ..buffer_snapshot.point_to_offset(max_word_search);
5537
5538 let skip_digits = query
5539 .as_ref()
5540 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5541
5542 let (mut words, provider_responses) = match &provider {
5543 Some(provider) => {
5544 let provider_responses = provider.completions(
5545 position.excerpt_id,
5546 &buffer,
5547 buffer_position,
5548 completion_context,
5549 window,
5550 cx,
5551 );
5552
5553 let words = match completion_settings.words {
5554 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5555 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5556 .background_spawn(async move {
5557 buffer_snapshot.words_in_range(WordsQuery {
5558 fuzzy_contents: None,
5559 range: word_search_range,
5560 skip_digits,
5561 })
5562 }),
5563 };
5564
5565 (words, provider_responses)
5566 }
5567 None => (
5568 cx.background_spawn(async move {
5569 buffer_snapshot.words_in_range(WordsQuery {
5570 fuzzy_contents: None,
5571 range: word_search_range,
5572 skip_digits,
5573 })
5574 }),
5575 Task::ready(Ok(Vec::new())),
5576 ),
5577 };
5578
5579 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5580
5581 let id = post_inc(&mut self.next_completion_id);
5582 let task = cx.spawn_in(window, async move |editor, cx| {
5583 let Ok(()) = editor.update(cx, |this, _| {
5584 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5585 }) else {
5586 return;
5587 };
5588
5589 // TODO: Ideally completions from different sources would be selectively re-queried, so
5590 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5591 let mut completions = Vec::new();
5592 let mut is_incomplete = false;
5593 if let Some(provider_responses) = provider_responses.await.log_err()
5594 && !provider_responses.is_empty()
5595 {
5596 for response in provider_responses {
5597 completions.extend(response.completions);
5598 is_incomplete = is_incomplete || response.is_incomplete;
5599 }
5600 if completion_settings.words == WordsCompletionMode::Fallback {
5601 words = Task::ready(BTreeMap::default());
5602 }
5603 }
5604
5605 let mut words = words.await;
5606 if let Some(word_to_exclude) = &word_to_exclude {
5607 words.remove(word_to_exclude);
5608 }
5609 for lsp_completion in &completions {
5610 words.remove(&lsp_completion.new_text);
5611 }
5612 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5613 replace_range: word_replace_range.clone(),
5614 new_text: word.clone(),
5615 label: CodeLabel::plain(word, None),
5616 icon_path: None,
5617 documentation: None,
5618 source: CompletionSource::BufferWord {
5619 word_range,
5620 resolved: false,
5621 },
5622 insert_text_mode: Some(InsertTextMode::AS_IS),
5623 confirm: None,
5624 }));
5625
5626 let menu = if completions.is_empty() {
5627 None
5628 } else {
5629 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5630 let languages = editor
5631 .workspace
5632 .as_ref()
5633 .and_then(|(workspace, _)| workspace.upgrade())
5634 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5635 let menu = CompletionsMenu::new(
5636 id,
5637 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5638 sort_completions,
5639 show_completion_documentation,
5640 position,
5641 query.clone(),
5642 is_incomplete,
5643 buffer.clone(),
5644 completions.into(),
5645 snippet_sort_order,
5646 languages,
5647 language,
5648 cx,
5649 );
5650
5651 let query = if filter_completions { query } else { None };
5652 let matches_task = if let Some(query) = query {
5653 menu.do_async_filtering(query, cx)
5654 } else {
5655 Task::ready(menu.unfiltered_matches())
5656 };
5657 (menu, matches_task)
5658 }) else {
5659 return;
5660 };
5661
5662 let matches = matches_task.await;
5663
5664 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5665 // Newer menu already set, so exit.
5666 match editor.context_menu.borrow().as_ref() {
5667 Some(CodeContextMenu::Completions(prev_menu)) => {
5668 if prev_menu.id > id {
5669 return;
5670 }
5671 }
5672 _ => {}
5673 };
5674
5675 // Only valid to take prev_menu because it the new menu is immediately set
5676 // below, or the menu is hidden.
5677 match editor.context_menu.borrow_mut().take() {
5678 Some(CodeContextMenu::Completions(prev_menu)) => {
5679 let position_matches =
5680 if prev_menu.initial_position == menu.initial_position {
5681 true
5682 } else {
5683 let snapshot = editor.buffer.read(cx).read(cx);
5684 prev_menu.initial_position.to_offset(&snapshot)
5685 == menu.initial_position.to_offset(&snapshot)
5686 };
5687 if position_matches {
5688 // Preserve markdown cache before `set_filter_results` because it will
5689 // try to populate the documentation cache.
5690 menu.preserve_markdown_cache(prev_menu);
5691 }
5692 }
5693 _ => {}
5694 };
5695
5696 menu.set_filter_results(matches, provider, window, cx);
5697 }) else {
5698 return;
5699 };
5700
5701 menu.visible().then_some(menu)
5702 };
5703
5704 editor
5705 .update_in(cx, |editor, window, cx| {
5706 if editor.focus_handle.is_focused(window)
5707 && let Some(menu) = menu
5708 {
5709 *editor.context_menu.borrow_mut() =
5710 Some(CodeContextMenu::Completions(menu));
5711
5712 crate::hover_popover::hide_hover(editor, cx);
5713 if editor.show_edit_predictions_in_menu() {
5714 editor.update_visible_edit_prediction(window, cx);
5715 } else {
5716 editor.discard_edit_prediction(false, cx);
5717 }
5718
5719 cx.notify();
5720 return;
5721 }
5722
5723 if editor.completion_tasks.len() <= 1 {
5724 // If there are no more completion tasks and the last menu was empty, we should hide it.
5725 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5726 // If it was already hidden and we don't show edit predictions in the menu,
5727 // we should also show the edit prediction when available.
5728 if was_hidden && editor.show_edit_predictions_in_menu() {
5729 editor.update_visible_edit_prediction(window, cx);
5730 }
5731 }
5732 })
5733 .ok();
5734 });
5735
5736 self.completion_tasks.push((id, task));
5737 }
5738
5739 #[cfg(feature = "test-support")]
5740 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5741 let menu = self.context_menu.borrow();
5742 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5743 let completions = menu.completions.borrow();
5744 Some(completions.to_vec())
5745 } else {
5746 None
5747 }
5748 }
5749
5750 pub fn with_completions_menu_matching_id<R>(
5751 &self,
5752 id: CompletionId,
5753 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5754 ) -> R {
5755 let mut context_menu = self.context_menu.borrow_mut();
5756 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5757 return f(None);
5758 };
5759 if completions_menu.id != id {
5760 return f(None);
5761 }
5762 f(Some(completions_menu))
5763 }
5764
5765 pub fn confirm_completion(
5766 &mut self,
5767 action: &ConfirmCompletion,
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(action.item_ix, CompletionIntent::Complete, window, cx)
5773 }
5774
5775 pub fn confirm_completion_insert(
5776 &mut self,
5777 _: &ConfirmCompletionInsert,
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(None, CompletionIntent::CompleteWithInsert, window, cx)
5783 }
5784
5785 pub fn confirm_completion_replace(
5786 &mut self,
5787 _: &ConfirmCompletionReplace,
5788 window: &mut Window,
5789 cx: &mut Context<Self>,
5790 ) -> Option<Task<Result<()>>> {
5791 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5792 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5793 }
5794
5795 pub fn compose_completion(
5796 &mut self,
5797 action: &ComposeCompletion,
5798 window: &mut Window,
5799 cx: &mut Context<Self>,
5800 ) -> Option<Task<Result<()>>> {
5801 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5802 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5803 }
5804
5805 fn do_completion(
5806 &mut self,
5807 item_ix: Option<usize>,
5808 intent: CompletionIntent,
5809 window: &mut Window,
5810 cx: &mut Context<Editor>,
5811 ) -> Option<Task<Result<()>>> {
5812 use language::ToOffset as _;
5813
5814 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5815 else {
5816 return None;
5817 };
5818
5819 let candidate_id = {
5820 let entries = completions_menu.entries.borrow();
5821 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5822 if self.show_edit_predictions_in_menu() {
5823 self.discard_edit_prediction(true, cx);
5824 }
5825 mat.candidate_id
5826 };
5827
5828 let completion = completions_menu
5829 .completions
5830 .borrow()
5831 .get(candidate_id)?
5832 .clone();
5833 cx.stop_propagation();
5834
5835 let buffer_handle = completions_menu.buffer.clone();
5836
5837 let CompletionEdit {
5838 new_text,
5839 snippet,
5840 replace_range,
5841 } = process_completion_for_edit(
5842 &completion,
5843 intent,
5844 &buffer_handle,
5845 &completions_menu.initial_position.text_anchor,
5846 cx,
5847 );
5848
5849 let buffer = buffer_handle.read(cx);
5850 let snapshot = self.buffer.read(cx).snapshot(cx);
5851 let newest_anchor = self.selections.newest_anchor();
5852 let replace_range_multibuffer = {
5853 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5854 let multibuffer_anchor = snapshot
5855 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5856 .unwrap()
5857 ..snapshot
5858 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5859 .unwrap();
5860 multibuffer_anchor.start.to_offset(&snapshot)
5861 ..multibuffer_anchor.end.to_offset(&snapshot)
5862 };
5863 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5864 return None;
5865 }
5866
5867 let old_text = buffer
5868 .text_for_range(replace_range.clone())
5869 .collect::<String>();
5870 let lookbehind = newest_anchor
5871 .start
5872 .text_anchor
5873 .to_offset(buffer)
5874 .saturating_sub(replace_range.start);
5875 let lookahead = replace_range
5876 .end
5877 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5878 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5879 let suffix = &old_text[lookbehind.min(old_text.len())..];
5880
5881 let selections = self.selections.all::<usize>(cx);
5882 let mut ranges = Vec::new();
5883 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5884
5885 for selection in &selections {
5886 let range = if selection.id == newest_anchor.id {
5887 replace_range_multibuffer.clone()
5888 } else {
5889 let mut range = selection.range();
5890
5891 // if prefix is present, don't duplicate it
5892 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5893 range.start = range.start.saturating_sub(lookbehind);
5894
5895 // if suffix is also present, mimic the newest cursor and replace it
5896 if selection.id != newest_anchor.id
5897 && snapshot.contains_str_at(range.end, suffix)
5898 {
5899 range.end += lookahead;
5900 }
5901 }
5902 range
5903 };
5904
5905 ranges.push(range.clone());
5906
5907 if !self.linked_edit_ranges.is_empty() {
5908 let start_anchor = snapshot.anchor_before(range.start);
5909 let end_anchor = snapshot.anchor_after(range.end);
5910 if let Some(ranges) = self
5911 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5912 {
5913 for (buffer, edits) in ranges {
5914 linked_edits
5915 .entry(buffer.clone())
5916 .or_default()
5917 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5918 }
5919 }
5920 }
5921 }
5922
5923 let common_prefix_len = old_text
5924 .chars()
5925 .zip(new_text.chars())
5926 .take_while(|(a, b)| a == b)
5927 .map(|(a, _)| a.len_utf8())
5928 .sum::<usize>();
5929
5930 cx.emit(EditorEvent::InputHandled {
5931 utf16_range_to_replace: None,
5932 text: new_text[common_prefix_len..].into(),
5933 });
5934
5935 self.transact(window, cx, |editor, window, cx| {
5936 if let Some(mut snippet) = snippet {
5937 snippet.text = new_text.to_string();
5938 editor
5939 .insert_snippet(&ranges, snippet, window, cx)
5940 .log_err();
5941 } else {
5942 editor.buffer.update(cx, |multi_buffer, cx| {
5943 let auto_indent = match completion.insert_text_mode {
5944 Some(InsertTextMode::AS_IS) => None,
5945 _ => editor.autoindent_mode.clone(),
5946 };
5947 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5948 multi_buffer.edit(edits, auto_indent, cx);
5949 });
5950 }
5951 for (buffer, edits) in linked_edits {
5952 buffer.update(cx, |buffer, cx| {
5953 let snapshot = buffer.snapshot();
5954 let edits = edits
5955 .into_iter()
5956 .map(|(range, text)| {
5957 use text::ToPoint as TP;
5958 let end_point = TP::to_point(&range.end, &snapshot);
5959 let start_point = TP::to_point(&range.start, &snapshot);
5960 (start_point..end_point, text)
5961 })
5962 .sorted_by_key(|(range, _)| range.start);
5963 buffer.edit(edits, None, cx);
5964 })
5965 }
5966
5967 editor.refresh_edit_prediction(true, false, window, cx);
5968 });
5969 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5970
5971 let show_new_completions_on_confirm = completion
5972 .confirm
5973 .as_ref()
5974 .map_or(false, |confirm| confirm(intent, window, cx));
5975 if show_new_completions_on_confirm {
5976 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5977 }
5978
5979 let provider = self.completion_provider.as_ref()?;
5980 drop(completion);
5981 let apply_edits = provider.apply_additional_edits_for_completion(
5982 buffer_handle,
5983 completions_menu.completions.clone(),
5984 candidate_id,
5985 true,
5986 cx,
5987 );
5988
5989 let editor_settings = EditorSettings::get_global(cx);
5990 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5991 // After the code completion is finished, users often want to know what signatures are needed.
5992 // so we should automatically call signature_help
5993 self.show_signature_help(&ShowSignatureHelp, window, cx);
5994 }
5995
5996 Some(cx.foreground_executor().spawn(async move {
5997 apply_edits.await?;
5998 Ok(())
5999 }))
6000 }
6001
6002 pub fn toggle_code_actions(
6003 &mut self,
6004 action: &ToggleCodeActions,
6005 window: &mut Window,
6006 cx: &mut Context<Self>,
6007 ) {
6008 let quick_launch = action.quick_launch;
6009 let mut context_menu = self.context_menu.borrow_mut();
6010 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6011 if code_actions.deployed_from == action.deployed_from {
6012 // Toggle if we're selecting the same one
6013 *context_menu = None;
6014 cx.notify();
6015 return;
6016 } else {
6017 // Otherwise, clear it and start a new one
6018 *context_menu = None;
6019 cx.notify();
6020 }
6021 }
6022 drop(context_menu);
6023 let snapshot = self.snapshot(window, cx);
6024 let deployed_from = action.deployed_from.clone();
6025 let action = action.clone();
6026 self.completion_tasks.clear();
6027 self.discard_edit_prediction(false, cx);
6028
6029 let multibuffer_point = match &action.deployed_from {
6030 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6031 DisplayPoint::new(*row, 0).to_point(&snapshot)
6032 }
6033 _ => self.selections.newest::<Point>(cx).head(),
6034 };
6035 let Some((buffer, buffer_row)) = snapshot
6036 .buffer_snapshot
6037 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6038 .and_then(|(buffer_snapshot, range)| {
6039 self.buffer()
6040 .read(cx)
6041 .buffer(buffer_snapshot.remote_id())
6042 .map(|buffer| (buffer, range.start.row))
6043 })
6044 else {
6045 return;
6046 };
6047 let buffer_id = buffer.read(cx).remote_id();
6048 let tasks = self
6049 .tasks
6050 .get(&(buffer_id, buffer_row))
6051 .map(|t| Arc::new(t.to_owned()));
6052
6053 if !self.focus_handle.is_focused(window) {
6054 return;
6055 }
6056 let project = self.project.clone();
6057
6058 let code_actions_task = match deployed_from {
6059 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6060 _ => self.code_actions(buffer_row, window, cx),
6061 };
6062
6063 let runnable_task = match deployed_from {
6064 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6065 _ => {
6066 let mut task_context_task = Task::ready(None);
6067 if let Some(tasks) = &tasks
6068 && let Some(project) = project
6069 {
6070 task_context_task =
6071 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6072 }
6073
6074 cx.spawn_in(window, {
6075 let buffer = buffer.clone();
6076 async move |editor, cx| {
6077 let task_context = task_context_task.await;
6078
6079 let resolved_tasks =
6080 tasks
6081 .zip(task_context.clone())
6082 .map(|(tasks, task_context)| ResolvedTasks {
6083 templates: tasks.resolve(&task_context).collect(),
6084 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6085 multibuffer_point.row,
6086 tasks.column,
6087 )),
6088 });
6089 let debug_scenarios = editor
6090 .update(cx, |editor, cx| {
6091 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6092 })?
6093 .await;
6094 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6095 }
6096 })
6097 }
6098 };
6099
6100 cx.spawn_in(window, async move |editor, cx| {
6101 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6102 let code_actions = code_actions_task.await;
6103 let spawn_straight_away = quick_launch
6104 && resolved_tasks
6105 .as_ref()
6106 .map_or(false, |tasks| tasks.templates.len() == 1)
6107 && code_actions
6108 .as_ref()
6109 .map_or(true, |actions| actions.is_empty())
6110 && debug_scenarios.is_empty();
6111
6112 editor.update_in(cx, |editor, window, cx| {
6113 crate::hover_popover::hide_hover(editor, cx);
6114 let actions = CodeActionContents::new(
6115 resolved_tasks,
6116 code_actions,
6117 debug_scenarios,
6118 task_context.unwrap_or_default(),
6119 );
6120
6121 // Don't show the menu if there are no actions available
6122 if actions.is_empty() {
6123 cx.notify();
6124 return Task::ready(Ok(()));
6125 }
6126
6127 *editor.context_menu.borrow_mut() =
6128 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6129 buffer,
6130 actions,
6131 selected_item: Default::default(),
6132 scroll_handle: UniformListScrollHandle::default(),
6133 deployed_from,
6134 }));
6135 cx.notify();
6136 if spawn_straight_away
6137 && let Some(task) = editor.confirm_code_action(
6138 &ConfirmCodeAction { item_ix: Some(0) },
6139 window,
6140 cx,
6141 )
6142 {
6143 return task;
6144 }
6145
6146 Task::ready(Ok(()))
6147 })
6148 })
6149 .detach_and_log_err(cx);
6150 }
6151
6152 fn debug_scenarios(
6153 &mut self,
6154 resolved_tasks: &Option<ResolvedTasks>,
6155 buffer: &Entity<Buffer>,
6156 cx: &mut App,
6157 ) -> Task<Vec<task::DebugScenario>> {
6158 maybe!({
6159 let project = self.project()?;
6160 let dap_store = project.read(cx).dap_store();
6161 let mut scenarios = vec![];
6162 let resolved_tasks = resolved_tasks.as_ref()?;
6163 let buffer = buffer.read(cx);
6164 let language = buffer.language()?;
6165 let file = buffer.file();
6166 let debug_adapter = language_settings(language.name().into(), file, cx)
6167 .debuggers
6168 .first()
6169 .map(SharedString::from)
6170 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6171
6172 dap_store.update(cx, |dap_store, cx| {
6173 for (_, task) in &resolved_tasks.templates {
6174 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6175 task.original_task().clone(),
6176 debug_adapter.clone().into(),
6177 task.display_label().to_owned().into(),
6178 cx,
6179 );
6180 scenarios.push(maybe_scenario);
6181 }
6182 });
6183 Some(cx.background_spawn(async move {
6184 let scenarios = futures::future::join_all(scenarios)
6185 .await
6186 .into_iter()
6187 .flatten()
6188 .collect::<Vec<_>>();
6189 scenarios
6190 }))
6191 })
6192 .unwrap_or_else(|| Task::ready(vec![]))
6193 }
6194
6195 fn code_actions(
6196 &mut self,
6197 buffer_row: u32,
6198 window: &mut Window,
6199 cx: &mut Context<Self>,
6200 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6201 let mut task = self.code_actions_task.take();
6202 cx.spawn_in(window, async move |editor, cx| {
6203 while let Some(prev_task) = task {
6204 prev_task.await.log_err();
6205 task = editor
6206 .update(cx, |this, _| this.code_actions_task.take())
6207 .ok()?;
6208 }
6209
6210 editor
6211 .update(cx, |editor, cx| {
6212 editor
6213 .available_code_actions
6214 .clone()
6215 .and_then(|(location, code_actions)| {
6216 let snapshot = location.buffer.read(cx).snapshot();
6217 let point_range = location.range.to_point(&snapshot);
6218 let point_range = point_range.start.row..=point_range.end.row;
6219 if point_range.contains(&buffer_row) {
6220 Some(code_actions)
6221 } else {
6222 None
6223 }
6224 })
6225 })
6226 .ok()
6227 .flatten()
6228 })
6229 }
6230
6231 pub fn confirm_code_action(
6232 &mut self,
6233 action: &ConfirmCodeAction,
6234 window: &mut Window,
6235 cx: &mut Context<Self>,
6236 ) -> Option<Task<Result<()>>> {
6237 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6238
6239 let actions_menu =
6240 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6241 menu
6242 } else {
6243 return None;
6244 };
6245
6246 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6247 let action = actions_menu.actions.get(action_ix)?;
6248 let title = action.label();
6249 let buffer = actions_menu.buffer;
6250 let workspace = self.workspace()?;
6251
6252 match action {
6253 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6254 workspace.update(cx, |workspace, cx| {
6255 workspace.schedule_resolved_task(
6256 task_source_kind,
6257 resolved_task,
6258 false,
6259 window,
6260 cx,
6261 );
6262
6263 Some(Task::ready(Ok(())))
6264 })
6265 }
6266 CodeActionsItem::CodeAction {
6267 excerpt_id,
6268 action,
6269 provider,
6270 } => {
6271 let apply_code_action =
6272 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6273 let workspace = workspace.downgrade();
6274 Some(cx.spawn_in(window, async move |editor, cx| {
6275 let project_transaction = apply_code_action.await?;
6276 Self::open_project_transaction(
6277 &editor,
6278 workspace,
6279 project_transaction,
6280 title,
6281 cx,
6282 )
6283 .await
6284 }))
6285 }
6286 CodeActionsItem::DebugScenario(scenario) => {
6287 let context = actions_menu.actions.context.clone();
6288
6289 workspace.update(cx, |workspace, cx| {
6290 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6291 workspace.start_debug_session(
6292 scenario,
6293 context,
6294 Some(buffer),
6295 None,
6296 window,
6297 cx,
6298 );
6299 });
6300 Some(Task::ready(Ok(())))
6301 }
6302 }
6303 }
6304
6305 pub async fn open_project_transaction(
6306 this: &WeakEntity<Editor>,
6307 workspace: WeakEntity<Workspace>,
6308 transaction: ProjectTransaction,
6309 title: String,
6310 cx: &mut AsyncWindowContext,
6311 ) -> Result<()> {
6312 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6313 cx.update(|_, cx| {
6314 entries.sort_unstable_by_key(|(buffer, _)| {
6315 buffer.read(cx).file().map(|f| f.path().clone())
6316 });
6317 })?;
6318
6319 // If the project transaction's edits are all contained within this editor, then
6320 // avoid opening a new editor to display them.
6321
6322 if let Some((buffer, transaction)) = entries.first() {
6323 if entries.len() == 1 {
6324 let excerpt = this.update(cx, |editor, cx| {
6325 editor
6326 .buffer()
6327 .read(cx)
6328 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6329 })?;
6330 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6331 && excerpted_buffer == *buffer
6332 {
6333 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6334 let excerpt_range = excerpt_range.to_offset(buffer);
6335 buffer
6336 .edited_ranges_for_transaction::<usize>(transaction)
6337 .all(|range| {
6338 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6339 })
6340 })?;
6341
6342 if all_edits_within_excerpt {
6343 return Ok(());
6344 }
6345 }
6346 }
6347 } else {
6348 return Ok(());
6349 }
6350
6351 let mut ranges_to_highlight = Vec::new();
6352 let excerpt_buffer = cx.new(|cx| {
6353 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6354 for (buffer_handle, transaction) in &entries {
6355 let edited_ranges = buffer_handle
6356 .read(cx)
6357 .edited_ranges_for_transaction::<Point>(transaction)
6358 .collect::<Vec<_>>();
6359 let (ranges, _) = multibuffer.set_excerpts_for_path(
6360 PathKey::for_buffer(buffer_handle, cx),
6361 buffer_handle.clone(),
6362 edited_ranges,
6363 DEFAULT_MULTIBUFFER_CONTEXT,
6364 cx,
6365 );
6366
6367 ranges_to_highlight.extend(ranges);
6368 }
6369 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6370 multibuffer
6371 })?;
6372
6373 workspace.update_in(cx, |workspace, window, cx| {
6374 let project = workspace.project().clone();
6375 let editor =
6376 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6377 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6378 editor.update(cx, |editor, cx| {
6379 editor.highlight_background::<Self>(
6380 &ranges_to_highlight,
6381 |theme| theme.colors().editor_highlighted_line_background,
6382 cx,
6383 );
6384 });
6385 })?;
6386
6387 Ok(())
6388 }
6389
6390 pub fn clear_code_action_providers(&mut self) {
6391 self.code_action_providers.clear();
6392 self.available_code_actions.take();
6393 }
6394
6395 pub fn add_code_action_provider(
6396 &mut self,
6397 provider: Rc<dyn CodeActionProvider>,
6398 window: &mut Window,
6399 cx: &mut Context<Self>,
6400 ) {
6401 if self
6402 .code_action_providers
6403 .iter()
6404 .any(|existing_provider| existing_provider.id() == provider.id())
6405 {
6406 return;
6407 }
6408
6409 self.code_action_providers.push(provider);
6410 self.refresh_code_actions(window, cx);
6411 }
6412
6413 pub fn remove_code_action_provider(
6414 &mut self,
6415 id: Arc<str>,
6416 window: &mut Window,
6417 cx: &mut Context<Self>,
6418 ) {
6419 self.code_action_providers
6420 .retain(|provider| provider.id() != id);
6421 self.refresh_code_actions(window, cx);
6422 }
6423
6424 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6425 !self.code_action_providers.is_empty()
6426 && EditorSettings::get_global(cx).toolbar.code_actions
6427 }
6428
6429 pub fn has_available_code_actions(&self) -> bool {
6430 self.available_code_actions
6431 .as_ref()
6432 .is_some_and(|(_, actions)| !actions.is_empty())
6433 }
6434
6435 fn render_inline_code_actions(
6436 &self,
6437 icon_size: ui::IconSize,
6438 display_row: DisplayRow,
6439 is_active: bool,
6440 cx: &mut Context<Self>,
6441 ) -> AnyElement {
6442 let show_tooltip = !self.context_menu_visible();
6443 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6444 .icon_size(icon_size)
6445 .shape(ui::IconButtonShape::Square)
6446 .icon_color(ui::Color::Hidden)
6447 .toggle_state(is_active)
6448 .when(show_tooltip, |this| {
6449 this.tooltip({
6450 let focus_handle = self.focus_handle.clone();
6451 move |window, cx| {
6452 Tooltip::for_action_in(
6453 "Toggle Code Actions",
6454 &ToggleCodeActions {
6455 deployed_from: None,
6456 quick_launch: false,
6457 },
6458 &focus_handle,
6459 window,
6460 cx,
6461 )
6462 }
6463 })
6464 })
6465 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6466 window.focus(&editor.focus_handle(cx));
6467 editor.toggle_code_actions(
6468 &crate::actions::ToggleCodeActions {
6469 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6470 display_row,
6471 )),
6472 quick_launch: false,
6473 },
6474 window,
6475 cx,
6476 );
6477 }))
6478 .into_any_element()
6479 }
6480
6481 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6482 &self.context_menu
6483 }
6484
6485 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6486 let newest_selection = self.selections.newest_anchor().clone();
6487 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6488 let buffer = self.buffer.read(cx);
6489 if newest_selection.head().diff_base_anchor.is_some() {
6490 return None;
6491 }
6492 let (start_buffer, start) =
6493 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6494 let (end_buffer, end) =
6495 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6496 if start_buffer != end_buffer {
6497 return None;
6498 }
6499
6500 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6501 cx.background_executor()
6502 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6503 .await;
6504
6505 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6506 let providers = this.code_action_providers.clone();
6507 let tasks = this
6508 .code_action_providers
6509 .iter()
6510 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6511 .collect::<Vec<_>>();
6512 (providers, tasks)
6513 })?;
6514
6515 let mut actions = Vec::new();
6516 for (provider, provider_actions) in
6517 providers.into_iter().zip(future::join_all(tasks).await)
6518 {
6519 if let Some(provider_actions) = provider_actions.log_err() {
6520 actions.extend(provider_actions.into_iter().map(|action| {
6521 AvailableCodeAction {
6522 excerpt_id: newest_selection.start.excerpt_id,
6523 action,
6524 provider: provider.clone(),
6525 }
6526 }));
6527 }
6528 }
6529
6530 this.update(cx, |this, cx| {
6531 this.available_code_actions = if actions.is_empty() {
6532 None
6533 } else {
6534 Some((
6535 Location {
6536 buffer: start_buffer,
6537 range: start..end,
6538 },
6539 actions.into(),
6540 ))
6541 };
6542 cx.notify();
6543 })
6544 }));
6545 None
6546 }
6547
6548 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6549 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6550 self.show_git_blame_inline = false;
6551
6552 self.show_git_blame_inline_delay_task =
6553 Some(cx.spawn_in(window, async move |this, cx| {
6554 cx.background_executor().timer(delay).await;
6555
6556 this.update(cx, |this, cx| {
6557 this.show_git_blame_inline = true;
6558 cx.notify();
6559 })
6560 .log_err();
6561 }));
6562 }
6563 }
6564
6565 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6566 let snapshot = self.snapshot(window, cx);
6567 let cursor = self.selections.newest::<Point>(cx).head();
6568 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6569 else {
6570 return;
6571 };
6572
6573 let Some(blame) = self.blame.as_ref() else {
6574 return;
6575 };
6576
6577 let row_info = RowInfo {
6578 buffer_id: Some(buffer.remote_id()),
6579 buffer_row: Some(point.row),
6580 ..Default::default()
6581 };
6582 let Some(blame_entry) = blame
6583 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6584 .flatten()
6585 else {
6586 return;
6587 };
6588
6589 let anchor = self.selections.newest_anchor().head();
6590 let position = self.to_pixel_point(anchor, &snapshot, window);
6591 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6592 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6593 };
6594 }
6595
6596 fn show_blame_popover(
6597 &mut self,
6598 blame_entry: &BlameEntry,
6599 position: gpui::Point<Pixels>,
6600 ignore_timeout: bool,
6601 cx: &mut Context<Self>,
6602 ) {
6603 if let Some(state) = &mut self.inline_blame_popover {
6604 state.hide_task.take();
6605 } else {
6606 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6607 let blame_entry = blame_entry.clone();
6608 let show_task = cx.spawn(async move |editor, cx| {
6609 if !ignore_timeout {
6610 cx.background_executor()
6611 .timer(std::time::Duration::from_millis(blame_popover_delay))
6612 .await;
6613 }
6614 editor
6615 .update(cx, |editor, cx| {
6616 editor.inline_blame_popover_show_task.take();
6617 let Some(blame) = editor.blame.as_ref() else {
6618 return;
6619 };
6620 let blame = blame.read(cx);
6621 let details = blame.details_for_entry(&blame_entry);
6622 let markdown = cx.new(|cx| {
6623 Markdown::new(
6624 details
6625 .as_ref()
6626 .map(|message| message.message.clone())
6627 .unwrap_or_default(),
6628 None,
6629 None,
6630 cx,
6631 )
6632 });
6633 editor.inline_blame_popover = Some(InlineBlamePopover {
6634 position,
6635 hide_task: None,
6636 popover_bounds: None,
6637 popover_state: InlineBlamePopoverState {
6638 scroll_handle: ScrollHandle::new(),
6639 commit_message: details,
6640 markdown,
6641 },
6642 keyboard_grace: ignore_timeout,
6643 });
6644 cx.notify();
6645 })
6646 .ok();
6647 });
6648 self.inline_blame_popover_show_task = Some(show_task);
6649 }
6650 }
6651
6652 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6653 self.inline_blame_popover_show_task.take();
6654 if let Some(state) = &mut self.inline_blame_popover {
6655 let hide_task = cx.spawn(async move |editor, cx| {
6656 cx.background_executor()
6657 .timer(std::time::Duration::from_millis(100))
6658 .await;
6659 editor
6660 .update(cx, |editor, cx| {
6661 editor.inline_blame_popover.take();
6662 cx.notify();
6663 })
6664 .ok();
6665 });
6666 state.hide_task = Some(hide_task);
6667 }
6668 }
6669
6670 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6671 if self.pending_rename.is_some() {
6672 return None;
6673 }
6674
6675 let provider = self.semantics_provider.clone()?;
6676 let buffer = self.buffer.read(cx);
6677 let newest_selection = self.selections.newest_anchor().clone();
6678 let cursor_position = newest_selection.head();
6679 let (cursor_buffer, cursor_buffer_position) =
6680 buffer.text_anchor_for_position(cursor_position, cx)?;
6681 let (tail_buffer, tail_buffer_position) =
6682 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6683 if cursor_buffer != tail_buffer {
6684 return None;
6685 }
6686
6687 let snapshot = cursor_buffer.read(cx).snapshot();
6688 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6689 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6690 if start_word_range != end_word_range {
6691 self.document_highlights_task.take();
6692 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6693 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6694 return None;
6695 }
6696
6697 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6698 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6699 cx.background_executor()
6700 .timer(Duration::from_millis(debounce))
6701 .await;
6702
6703 let highlights = if let Some(highlights) = cx
6704 .update(|cx| {
6705 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6706 })
6707 .ok()
6708 .flatten()
6709 {
6710 highlights.await.log_err()
6711 } else {
6712 None
6713 };
6714
6715 if let Some(highlights) = highlights {
6716 this.update(cx, |this, cx| {
6717 if this.pending_rename.is_some() {
6718 return;
6719 }
6720
6721 let buffer_id = cursor_position.buffer_id;
6722 let buffer = this.buffer.read(cx);
6723 if !buffer
6724 .text_anchor_for_position(cursor_position, cx)
6725 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6726 {
6727 return;
6728 }
6729
6730 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6731 let mut write_ranges = Vec::new();
6732 let mut read_ranges = Vec::new();
6733 for highlight in highlights {
6734 for (excerpt_id, excerpt_range) in
6735 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6736 {
6737 let start = highlight
6738 .range
6739 .start
6740 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6741 let end = highlight
6742 .range
6743 .end
6744 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6745 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6746 continue;
6747 }
6748
6749 let range = Anchor {
6750 buffer_id,
6751 excerpt_id,
6752 text_anchor: start,
6753 diff_base_anchor: None,
6754 }..Anchor {
6755 buffer_id,
6756 excerpt_id,
6757 text_anchor: end,
6758 diff_base_anchor: None,
6759 };
6760 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6761 write_ranges.push(range);
6762 } else {
6763 read_ranges.push(range);
6764 }
6765 }
6766 }
6767
6768 this.highlight_background::<DocumentHighlightRead>(
6769 &read_ranges,
6770 |theme| theme.colors().editor_document_highlight_read_background,
6771 cx,
6772 );
6773 this.highlight_background::<DocumentHighlightWrite>(
6774 &write_ranges,
6775 |theme| theme.colors().editor_document_highlight_write_background,
6776 cx,
6777 );
6778 cx.notify();
6779 })
6780 .log_err();
6781 }
6782 }));
6783 None
6784 }
6785
6786 fn prepare_highlight_query_from_selection(
6787 &mut self,
6788 cx: &mut Context<Editor>,
6789 ) -> Option<(String, Range<Anchor>)> {
6790 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6791 return None;
6792 }
6793 if !EditorSettings::get_global(cx).selection_highlight {
6794 return None;
6795 }
6796 if self.selections.count() != 1 || self.selections.line_mode {
6797 return None;
6798 }
6799 let selection = self.selections.newest::<Point>(cx);
6800 if selection.is_empty() || selection.start.row != selection.end.row {
6801 return None;
6802 }
6803 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6804 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6805 let query = multi_buffer_snapshot
6806 .text_for_range(selection_anchor_range.clone())
6807 .collect::<String>();
6808 if query.trim().is_empty() {
6809 return None;
6810 }
6811 Some((query, selection_anchor_range))
6812 }
6813
6814 fn update_selection_occurrence_highlights(
6815 &mut self,
6816 query_text: String,
6817 query_range: Range<Anchor>,
6818 multi_buffer_range_to_query: Range<Point>,
6819 use_debounce: bool,
6820 window: &mut Window,
6821 cx: &mut Context<Editor>,
6822 ) -> Task<()> {
6823 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6824 cx.spawn_in(window, async move |editor, cx| {
6825 if use_debounce {
6826 cx.background_executor()
6827 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6828 .await;
6829 }
6830 let match_task = cx.background_spawn(async move {
6831 let buffer_ranges = multi_buffer_snapshot
6832 .range_to_buffer_ranges(multi_buffer_range_to_query)
6833 .into_iter()
6834 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6835 let mut match_ranges = Vec::new();
6836 let Ok(regex) = project::search::SearchQuery::text(
6837 query_text.clone(),
6838 false,
6839 false,
6840 false,
6841 Default::default(),
6842 Default::default(),
6843 false,
6844 None,
6845 ) else {
6846 return Vec::default();
6847 };
6848 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6849 match_ranges.extend(
6850 regex
6851 .search(buffer_snapshot, Some(search_range.clone()))
6852 .await
6853 .into_iter()
6854 .filter_map(|match_range| {
6855 let match_start = buffer_snapshot
6856 .anchor_after(search_range.start + match_range.start);
6857 let match_end = buffer_snapshot
6858 .anchor_before(search_range.start + match_range.end);
6859 let match_anchor_range = Anchor::range_in_buffer(
6860 excerpt_id,
6861 buffer_snapshot.remote_id(),
6862 match_start..match_end,
6863 );
6864 (match_anchor_range != query_range).then_some(match_anchor_range)
6865 }),
6866 );
6867 }
6868 match_ranges
6869 });
6870 let match_ranges = match_task.await;
6871 editor
6872 .update_in(cx, |editor, _, cx| {
6873 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6874 if !match_ranges.is_empty() {
6875 editor.highlight_background::<SelectedTextHighlight>(
6876 &match_ranges,
6877 |theme| theme.colors().editor_document_highlight_bracket_background,
6878 cx,
6879 )
6880 }
6881 })
6882 .log_err();
6883 })
6884 }
6885
6886 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6887 struct NewlineFold;
6888 let type_id = std::any::TypeId::of::<NewlineFold>();
6889 if !self.mode.is_single_line() {
6890 return;
6891 }
6892 let snapshot = self.snapshot(window, cx);
6893 if snapshot.buffer_snapshot.max_point().row == 0 {
6894 return;
6895 }
6896 let task = cx.background_spawn(async move {
6897 let new_newlines = snapshot
6898 .buffer_chars_at(0)
6899 .filter_map(|(c, i)| {
6900 if c == '\n' {
6901 Some(
6902 snapshot.buffer_snapshot.anchor_after(i)
6903 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6904 )
6905 } else {
6906 None
6907 }
6908 })
6909 .collect::<Vec<_>>();
6910 let existing_newlines = snapshot
6911 .folds_in_range(0..snapshot.buffer_snapshot.len())
6912 .filter_map(|fold| {
6913 if fold.placeholder.type_tag == Some(type_id) {
6914 Some(fold.range.start..fold.range.end)
6915 } else {
6916 None
6917 }
6918 })
6919 .collect::<Vec<_>>();
6920
6921 (new_newlines, existing_newlines)
6922 });
6923 self.folding_newlines = cx.spawn(async move |this, cx| {
6924 let (new_newlines, existing_newlines) = task.await;
6925 if new_newlines == existing_newlines {
6926 return;
6927 }
6928 let placeholder = FoldPlaceholder {
6929 render: Arc::new(move |_, _, cx| {
6930 div()
6931 .bg(cx.theme().status().hint_background)
6932 .border_b_1()
6933 .size_full()
6934 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6935 .border_color(cx.theme().status().hint)
6936 .child("\\n")
6937 .into_any()
6938 }),
6939 constrain_width: false,
6940 merge_adjacent: false,
6941 type_tag: Some(type_id),
6942 };
6943 let creases = new_newlines
6944 .into_iter()
6945 .map(|range| Crease::simple(range, placeholder.clone()))
6946 .collect();
6947 this.update(cx, |this, cx| {
6948 this.display_map.update(cx, |display_map, cx| {
6949 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6950 display_map.fold(creases, cx);
6951 });
6952 })
6953 .ok();
6954 });
6955 }
6956
6957 fn refresh_selected_text_highlights(
6958 &mut self,
6959 on_buffer_edit: bool,
6960 window: &mut Window,
6961 cx: &mut Context<Editor>,
6962 ) {
6963 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6964 else {
6965 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6966 self.quick_selection_highlight_task.take();
6967 self.debounced_selection_highlight_task.take();
6968 return;
6969 };
6970 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6971 if on_buffer_edit
6972 || self
6973 .quick_selection_highlight_task
6974 .as_ref()
6975 .map_or(true, |(prev_anchor_range, _)| {
6976 prev_anchor_range != &query_range
6977 })
6978 {
6979 let multi_buffer_visible_start = self
6980 .scroll_manager
6981 .anchor()
6982 .anchor
6983 .to_point(&multi_buffer_snapshot);
6984 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6985 multi_buffer_visible_start
6986 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6987 Bias::Left,
6988 );
6989 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6990 self.quick_selection_highlight_task = Some((
6991 query_range.clone(),
6992 self.update_selection_occurrence_highlights(
6993 query_text.clone(),
6994 query_range.clone(),
6995 multi_buffer_visible_range,
6996 false,
6997 window,
6998 cx,
6999 ),
7000 ));
7001 }
7002 if on_buffer_edit
7003 || self
7004 .debounced_selection_highlight_task
7005 .as_ref()
7006 .map_or(true, |(prev_anchor_range, _)| {
7007 prev_anchor_range != &query_range
7008 })
7009 {
7010 let multi_buffer_start = multi_buffer_snapshot
7011 .anchor_before(0)
7012 .to_point(&multi_buffer_snapshot);
7013 let multi_buffer_end = multi_buffer_snapshot
7014 .anchor_after(multi_buffer_snapshot.len())
7015 .to_point(&multi_buffer_snapshot);
7016 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7017 self.debounced_selection_highlight_task = Some((
7018 query_range.clone(),
7019 self.update_selection_occurrence_highlights(
7020 query_text,
7021 query_range,
7022 multi_buffer_full_range,
7023 true,
7024 window,
7025 cx,
7026 ),
7027 ));
7028 }
7029 }
7030
7031 pub fn refresh_edit_prediction(
7032 &mut self,
7033 debounce: bool,
7034 user_requested: bool,
7035 window: &mut Window,
7036 cx: &mut Context<Self>,
7037 ) -> Option<()> {
7038 if DisableAiSettings::get_global(cx).disable_ai {
7039 return None;
7040 }
7041
7042 let provider = self.edit_prediction_provider()?;
7043 let cursor = self.selections.newest_anchor().head();
7044 let (buffer, cursor_buffer_position) =
7045 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7046
7047 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7048 self.discard_edit_prediction(false, cx);
7049 return None;
7050 }
7051
7052 if !user_requested
7053 && (!self.should_show_edit_predictions()
7054 || !self.is_focused(window)
7055 || buffer.read(cx).is_empty())
7056 {
7057 self.discard_edit_prediction(false, cx);
7058 return None;
7059 }
7060
7061 self.update_visible_edit_prediction(window, cx);
7062 provider.refresh(
7063 self.project.clone(),
7064 buffer,
7065 cursor_buffer_position,
7066 debounce,
7067 cx,
7068 );
7069 Some(())
7070 }
7071
7072 fn show_edit_predictions_in_menu(&self) -> bool {
7073 match self.edit_prediction_settings {
7074 EditPredictionSettings::Disabled => false,
7075 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7076 }
7077 }
7078
7079 pub fn edit_predictions_enabled(&self) -> bool {
7080 match self.edit_prediction_settings {
7081 EditPredictionSettings::Disabled => false,
7082 EditPredictionSettings::Enabled { .. } => true,
7083 }
7084 }
7085
7086 fn edit_prediction_requires_modifier(&self) -> bool {
7087 match self.edit_prediction_settings {
7088 EditPredictionSettings::Disabled => false,
7089 EditPredictionSettings::Enabled {
7090 preview_requires_modifier,
7091 ..
7092 } => preview_requires_modifier,
7093 }
7094 }
7095
7096 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7097 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7098 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7099 self.discard_edit_prediction(false, cx);
7100 } else {
7101 let selection = self.selections.newest_anchor();
7102 let cursor = selection.head();
7103
7104 if let Some((buffer, cursor_buffer_position)) =
7105 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7106 {
7107 self.edit_prediction_settings =
7108 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7109 }
7110 }
7111 }
7112
7113 fn edit_prediction_settings_at_position(
7114 &self,
7115 buffer: &Entity<Buffer>,
7116 buffer_position: language::Anchor,
7117 cx: &App,
7118 ) -> EditPredictionSettings {
7119 if !self.mode.is_full()
7120 || !self.show_edit_predictions_override.unwrap_or(true)
7121 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7122 {
7123 return EditPredictionSettings::Disabled;
7124 }
7125
7126 let buffer = buffer.read(cx);
7127
7128 let file = buffer.file();
7129
7130 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7131 return EditPredictionSettings::Disabled;
7132 };
7133
7134 let by_provider = matches!(
7135 self.menu_edit_predictions_policy,
7136 MenuEditPredictionsPolicy::ByProvider
7137 );
7138
7139 let show_in_menu = by_provider
7140 && self
7141 .edit_prediction_provider
7142 .as_ref()
7143 .map_or(false, |provider| {
7144 provider.provider.show_completions_in_menu()
7145 });
7146
7147 let preview_requires_modifier =
7148 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7149
7150 EditPredictionSettings::Enabled {
7151 show_in_menu,
7152 preview_requires_modifier,
7153 }
7154 }
7155
7156 fn should_show_edit_predictions(&self) -> bool {
7157 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7158 }
7159
7160 pub fn edit_prediction_preview_is_active(&self) -> bool {
7161 matches!(
7162 self.edit_prediction_preview,
7163 EditPredictionPreview::Active { .. }
7164 )
7165 }
7166
7167 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7168 let cursor = self.selections.newest_anchor().head();
7169 if let Some((buffer, cursor_position)) =
7170 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7171 {
7172 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7173 } else {
7174 false
7175 }
7176 }
7177
7178 pub fn supports_minimap(&self, cx: &App) -> bool {
7179 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7180 }
7181
7182 fn edit_predictions_enabled_in_buffer(
7183 &self,
7184 buffer: &Entity<Buffer>,
7185 buffer_position: language::Anchor,
7186 cx: &App,
7187 ) -> bool {
7188 maybe!({
7189 if self.read_only(cx) {
7190 return Some(false);
7191 }
7192 let provider = self.edit_prediction_provider()?;
7193 if !provider.is_enabled(buffer, buffer_position, cx) {
7194 return Some(false);
7195 }
7196 let buffer = buffer.read(cx);
7197 let Some(file) = buffer.file() else {
7198 return Some(true);
7199 };
7200 let settings = all_language_settings(Some(file), cx);
7201 Some(settings.edit_predictions_enabled_for_file(file, cx))
7202 })
7203 .unwrap_or(false)
7204 }
7205
7206 fn cycle_edit_prediction(
7207 &mut self,
7208 direction: Direction,
7209 window: &mut Window,
7210 cx: &mut Context<Self>,
7211 ) -> Option<()> {
7212 let provider = self.edit_prediction_provider()?;
7213 let cursor = self.selections.newest_anchor().head();
7214 let (buffer, cursor_buffer_position) =
7215 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7216 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7217 return None;
7218 }
7219
7220 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7221 self.update_visible_edit_prediction(window, cx);
7222
7223 Some(())
7224 }
7225
7226 pub fn show_edit_prediction(
7227 &mut self,
7228 _: &ShowEditPrediction,
7229 window: &mut Window,
7230 cx: &mut Context<Self>,
7231 ) {
7232 if !self.has_active_edit_prediction() {
7233 self.refresh_edit_prediction(false, true, window, cx);
7234 return;
7235 }
7236
7237 self.update_visible_edit_prediction(window, cx);
7238 }
7239
7240 pub fn display_cursor_names(
7241 &mut self,
7242 _: &DisplayCursorNames,
7243 window: &mut Window,
7244 cx: &mut Context<Self>,
7245 ) {
7246 self.show_cursor_names(window, cx);
7247 }
7248
7249 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7250 self.show_cursor_names = true;
7251 cx.notify();
7252 cx.spawn_in(window, async move |this, cx| {
7253 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7254 this.update(cx, |this, cx| {
7255 this.show_cursor_names = false;
7256 cx.notify()
7257 })
7258 .ok()
7259 })
7260 .detach();
7261 }
7262
7263 pub fn next_edit_prediction(
7264 &mut self,
7265 _: &NextEditPrediction,
7266 window: &mut Window,
7267 cx: &mut Context<Self>,
7268 ) {
7269 if self.has_active_edit_prediction() {
7270 self.cycle_edit_prediction(Direction::Next, window, cx);
7271 } else {
7272 let is_copilot_disabled = self
7273 .refresh_edit_prediction(false, true, window, cx)
7274 .is_none();
7275 if is_copilot_disabled {
7276 cx.propagate();
7277 }
7278 }
7279 }
7280
7281 pub fn previous_edit_prediction(
7282 &mut self,
7283 _: &PreviousEditPrediction,
7284 window: &mut Window,
7285 cx: &mut Context<Self>,
7286 ) {
7287 if self.has_active_edit_prediction() {
7288 self.cycle_edit_prediction(Direction::Prev, window, cx);
7289 } else {
7290 let is_copilot_disabled = self
7291 .refresh_edit_prediction(false, true, window, cx)
7292 .is_none();
7293 if is_copilot_disabled {
7294 cx.propagate();
7295 }
7296 }
7297 }
7298
7299 pub fn accept_edit_prediction(
7300 &mut self,
7301 _: &AcceptEditPrediction,
7302 window: &mut Window,
7303 cx: &mut Context<Self>,
7304 ) {
7305 if self.show_edit_predictions_in_menu() {
7306 self.hide_context_menu(window, cx);
7307 }
7308
7309 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7310 return;
7311 };
7312
7313 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7314
7315 match &active_edit_prediction.completion {
7316 EditPrediction::Move { target, .. } => {
7317 let target = *target;
7318
7319 if let Some(position_map) = &self.last_position_map {
7320 if position_map
7321 .visible_row_range
7322 .contains(&target.to_display_point(&position_map.snapshot).row())
7323 || !self.edit_prediction_requires_modifier()
7324 {
7325 self.unfold_ranges(&[target..target], true, false, cx);
7326 // Note that this is also done in vim's handler of the Tab action.
7327 self.change_selections(
7328 SelectionEffects::scroll(Autoscroll::newest()),
7329 window,
7330 cx,
7331 |selections| {
7332 selections.select_anchor_ranges([target..target]);
7333 },
7334 );
7335 self.clear_row_highlights::<EditPredictionPreview>();
7336
7337 self.edit_prediction_preview
7338 .set_previous_scroll_position(None);
7339 } else {
7340 self.edit_prediction_preview
7341 .set_previous_scroll_position(Some(
7342 position_map.snapshot.scroll_anchor,
7343 ));
7344
7345 self.highlight_rows::<EditPredictionPreview>(
7346 target..target,
7347 cx.theme().colors().editor_highlighted_line_background,
7348 RowHighlightOptions {
7349 autoscroll: true,
7350 ..Default::default()
7351 },
7352 cx,
7353 );
7354 self.request_autoscroll(Autoscroll::fit(), cx);
7355 }
7356 }
7357 }
7358 EditPrediction::Edit { edits, .. } => {
7359 if let Some(provider) = self.edit_prediction_provider() {
7360 provider.accept(cx);
7361 }
7362
7363 // Store the transaction ID and selections before applying the edit
7364 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7365
7366 let snapshot = self.buffer.read(cx).snapshot(cx);
7367 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7368
7369 self.buffer.update(cx, |buffer, cx| {
7370 buffer.edit(edits.iter().cloned(), None, cx)
7371 });
7372
7373 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7374 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7375 });
7376
7377 let selections = self.selections.disjoint_anchors();
7378 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7379 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7380 if has_new_transaction {
7381 self.selection_history
7382 .insert_transaction(transaction_id_now, selections);
7383 }
7384 }
7385
7386 self.update_visible_edit_prediction(window, cx);
7387 if self.active_edit_prediction.is_none() {
7388 self.refresh_edit_prediction(true, true, window, cx);
7389 }
7390
7391 cx.notify();
7392 }
7393 }
7394
7395 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7396 }
7397
7398 pub fn accept_partial_edit_prediction(
7399 &mut self,
7400 _: &AcceptPartialEditPrediction,
7401 window: &mut Window,
7402 cx: &mut Context<Self>,
7403 ) {
7404 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7405 return;
7406 };
7407 if self.selections.count() != 1 {
7408 return;
7409 }
7410
7411 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7412
7413 match &active_edit_prediction.completion {
7414 EditPrediction::Move { target, .. } => {
7415 let target = *target;
7416 self.change_selections(
7417 SelectionEffects::scroll(Autoscroll::newest()),
7418 window,
7419 cx,
7420 |selections| {
7421 selections.select_anchor_ranges([target..target]);
7422 },
7423 );
7424 }
7425 EditPrediction::Edit { edits, .. } => {
7426 // Find an insertion that starts at the cursor position.
7427 let snapshot = self.buffer.read(cx).snapshot(cx);
7428 let cursor_offset = self.selections.newest::<usize>(cx).head();
7429 let insertion = edits.iter().find_map(|(range, text)| {
7430 let range = range.to_offset(&snapshot);
7431 if range.is_empty() && range.start == cursor_offset {
7432 Some(text)
7433 } else {
7434 None
7435 }
7436 });
7437
7438 if let Some(text) = insertion {
7439 let mut partial_completion = text
7440 .chars()
7441 .by_ref()
7442 .take_while(|c| c.is_alphabetic())
7443 .collect::<String>();
7444 if partial_completion.is_empty() {
7445 partial_completion = text
7446 .chars()
7447 .by_ref()
7448 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7449 .collect::<String>();
7450 }
7451
7452 cx.emit(EditorEvent::InputHandled {
7453 utf16_range_to_replace: None,
7454 text: partial_completion.clone().into(),
7455 });
7456
7457 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7458
7459 self.refresh_edit_prediction(true, true, window, cx);
7460 cx.notify();
7461 } else {
7462 self.accept_edit_prediction(&Default::default(), window, cx);
7463 }
7464 }
7465 }
7466 }
7467
7468 fn discard_edit_prediction(
7469 &mut self,
7470 should_report_edit_prediction_event: bool,
7471 cx: &mut Context<Self>,
7472 ) -> bool {
7473 if should_report_edit_prediction_event {
7474 let completion_id = self
7475 .active_edit_prediction
7476 .as_ref()
7477 .and_then(|active_completion| active_completion.completion_id.clone());
7478
7479 self.report_edit_prediction_event(completion_id, false, cx);
7480 }
7481
7482 if let Some(provider) = self.edit_prediction_provider() {
7483 provider.discard(cx);
7484 }
7485
7486 self.take_active_edit_prediction(cx)
7487 }
7488
7489 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7490 let Some(provider) = self.edit_prediction_provider() else {
7491 return;
7492 };
7493
7494 let Some((_, buffer, _)) = self
7495 .buffer
7496 .read(cx)
7497 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7498 else {
7499 return;
7500 };
7501
7502 let extension = buffer
7503 .read(cx)
7504 .file()
7505 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7506
7507 let event_type = match accepted {
7508 true => "Edit Prediction Accepted",
7509 false => "Edit Prediction Discarded",
7510 };
7511 telemetry::event!(
7512 event_type,
7513 provider = provider.name(),
7514 prediction_id = id,
7515 suggestion_accepted = accepted,
7516 file_extension = extension,
7517 );
7518 }
7519
7520 pub fn has_active_edit_prediction(&self) -> bool {
7521 self.active_edit_prediction.is_some()
7522 }
7523
7524 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7525 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7526 return false;
7527 };
7528
7529 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7530 self.clear_highlights::<EditPredictionHighlight>(cx);
7531 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7532 true
7533 }
7534
7535 /// Returns true when we're displaying the edit prediction popover below the cursor
7536 /// like we are not previewing and the LSP autocomplete menu is visible
7537 /// or we are in `when_holding_modifier` mode.
7538 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7539 if self.edit_prediction_preview_is_active()
7540 || !self.show_edit_predictions_in_menu()
7541 || !self.edit_predictions_enabled()
7542 {
7543 return false;
7544 }
7545
7546 if self.has_visible_completions_menu() {
7547 return true;
7548 }
7549
7550 has_completion && self.edit_prediction_requires_modifier()
7551 }
7552
7553 fn handle_modifiers_changed(
7554 &mut self,
7555 modifiers: Modifiers,
7556 position_map: &PositionMap,
7557 window: &mut Window,
7558 cx: &mut Context<Self>,
7559 ) {
7560 if self.show_edit_predictions_in_menu() {
7561 self.update_edit_prediction_preview(&modifiers, window, cx);
7562 }
7563
7564 self.update_selection_mode(&modifiers, position_map, window, cx);
7565
7566 let mouse_position = window.mouse_position();
7567 if !position_map.text_hitbox.is_hovered(window) {
7568 return;
7569 }
7570
7571 self.update_hovered_link(
7572 position_map.point_for_position(mouse_position),
7573 &position_map.snapshot,
7574 modifiers,
7575 window,
7576 cx,
7577 )
7578 }
7579
7580 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7581 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7582 if invert {
7583 match multi_cursor_setting {
7584 MultiCursorModifier::Alt => modifiers.alt,
7585 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7586 }
7587 } else {
7588 match multi_cursor_setting {
7589 MultiCursorModifier::Alt => modifiers.secondary(),
7590 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7591 }
7592 }
7593 }
7594
7595 fn columnar_selection_mode(
7596 modifiers: &Modifiers,
7597 cx: &mut Context<Self>,
7598 ) -> Option<ColumnarMode> {
7599 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7600 if Self::multi_cursor_modifier(false, modifiers, cx) {
7601 Some(ColumnarMode::FromMouse)
7602 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7603 Some(ColumnarMode::FromSelection)
7604 } else {
7605 None
7606 }
7607 } else {
7608 None
7609 }
7610 }
7611
7612 fn update_selection_mode(
7613 &mut self,
7614 modifiers: &Modifiers,
7615 position_map: &PositionMap,
7616 window: &mut Window,
7617 cx: &mut Context<Self>,
7618 ) {
7619 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7620 return;
7621 };
7622 if self.selections.pending.is_none() {
7623 return;
7624 }
7625
7626 let mouse_position = window.mouse_position();
7627 let point_for_position = position_map.point_for_position(mouse_position);
7628 let position = point_for_position.previous_valid;
7629
7630 self.select(
7631 SelectPhase::BeginColumnar {
7632 position,
7633 reset: false,
7634 mode,
7635 goal_column: point_for_position.exact_unclipped.column(),
7636 },
7637 window,
7638 cx,
7639 );
7640 }
7641
7642 fn update_edit_prediction_preview(
7643 &mut self,
7644 modifiers: &Modifiers,
7645 window: &mut Window,
7646 cx: &mut Context<Self>,
7647 ) {
7648 let mut modifiers_held = false;
7649 if let Some(accept_keystroke) = self
7650 .accept_edit_prediction_keybind(false, window, cx)
7651 .keystroke()
7652 {
7653 modifiers_held = modifiers_held
7654 || (&accept_keystroke.modifiers == modifiers
7655 && accept_keystroke.modifiers.modified());
7656 };
7657 if let Some(accept_partial_keystroke) = self
7658 .accept_edit_prediction_keybind(true, window, cx)
7659 .keystroke()
7660 {
7661 modifiers_held = modifiers_held
7662 || (&accept_partial_keystroke.modifiers == modifiers
7663 && accept_partial_keystroke.modifiers.modified());
7664 }
7665
7666 if modifiers_held {
7667 if matches!(
7668 self.edit_prediction_preview,
7669 EditPredictionPreview::Inactive { .. }
7670 ) {
7671 self.edit_prediction_preview = EditPredictionPreview::Active {
7672 previous_scroll_position: None,
7673 since: Instant::now(),
7674 };
7675
7676 self.update_visible_edit_prediction(window, cx);
7677 cx.notify();
7678 }
7679 } else if let EditPredictionPreview::Active {
7680 previous_scroll_position,
7681 since,
7682 } = self.edit_prediction_preview
7683 {
7684 if let (Some(previous_scroll_position), Some(position_map)) =
7685 (previous_scroll_position, self.last_position_map.as_ref())
7686 {
7687 self.set_scroll_position(
7688 previous_scroll_position
7689 .scroll_position(&position_map.snapshot.display_snapshot),
7690 window,
7691 cx,
7692 );
7693 }
7694
7695 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7696 released_too_fast: since.elapsed() < Duration::from_millis(200),
7697 };
7698 self.clear_row_highlights::<EditPredictionPreview>();
7699 self.update_visible_edit_prediction(window, cx);
7700 cx.notify();
7701 }
7702 }
7703
7704 fn update_visible_edit_prediction(
7705 &mut self,
7706 _window: &mut Window,
7707 cx: &mut Context<Self>,
7708 ) -> Option<()> {
7709 if DisableAiSettings::get_global(cx).disable_ai {
7710 return None;
7711 }
7712
7713 let selection = self.selections.newest_anchor();
7714 let cursor = selection.head();
7715 let multibuffer = self.buffer.read(cx).snapshot(cx);
7716 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7717 let excerpt_id = cursor.excerpt_id;
7718
7719 let show_in_menu = self.show_edit_predictions_in_menu();
7720 let completions_menu_has_precedence = !show_in_menu
7721 && (self.context_menu.borrow().is_some()
7722 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7723
7724 if completions_menu_has_precedence
7725 || !offset_selection.is_empty()
7726 || self
7727 .active_edit_prediction
7728 .as_ref()
7729 .map_or(false, |completion| {
7730 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7731 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7732 !invalidation_range.contains(&offset_selection.head())
7733 })
7734 {
7735 self.discard_edit_prediction(false, cx);
7736 return None;
7737 }
7738
7739 self.take_active_edit_prediction(cx);
7740 let Some(provider) = self.edit_prediction_provider() else {
7741 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7742 return None;
7743 };
7744
7745 let (buffer, cursor_buffer_position) =
7746 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7747
7748 self.edit_prediction_settings =
7749 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7750
7751 match self.edit_prediction_settings {
7752 EditPredictionSettings::Disabled => {
7753 self.discard_edit_prediction(false, cx);
7754 return None;
7755 }
7756 _ => {}
7757 };
7758
7759 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7760
7761 if self.edit_prediction_indent_conflict {
7762 let cursor_point = cursor.to_point(&multibuffer);
7763
7764 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7765
7766 if let Some((_, indent)) = indents.iter().next()
7767 && indent.len == cursor_point.column
7768 {
7769 self.edit_prediction_indent_conflict = false;
7770 }
7771 }
7772
7773 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7774 let edits = edit_prediction
7775 .edits
7776 .into_iter()
7777 .flat_map(|(range, new_text)| {
7778 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7779 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7780 Some((start..end, new_text))
7781 })
7782 .collect::<Vec<_>>();
7783 if edits.is_empty() {
7784 return None;
7785 }
7786
7787 let first_edit_start = edits.first().unwrap().0.start;
7788 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7789 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7790
7791 let last_edit_end = edits.last().unwrap().0.end;
7792 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7793 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7794
7795 let cursor_row = cursor.to_point(&multibuffer).row;
7796
7797 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7798
7799 let mut inlay_ids = Vec::new();
7800 let invalidation_row_range;
7801 let move_invalidation_row_range = if cursor_row < edit_start_row {
7802 Some(cursor_row..edit_end_row)
7803 } else if cursor_row > edit_end_row {
7804 Some(edit_start_row..cursor_row)
7805 } else {
7806 None
7807 };
7808 let supports_jump = self
7809 .edit_prediction_provider
7810 .as_ref()
7811 .map(|provider| provider.provider.supports_jump_to_edit())
7812 .unwrap_or(true);
7813
7814 let is_move = supports_jump
7815 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7816 let completion = if is_move {
7817 invalidation_row_range =
7818 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7819 let target = first_edit_start;
7820 EditPrediction::Move { target, snapshot }
7821 } else {
7822 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7823 && !self.edit_predictions_hidden_for_vim_mode;
7824
7825 if show_completions_in_buffer {
7826 if edits
7827 .iter()
7828 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7829 {
7830 let mut inlays = Vec::new();
7831 for (range, new_text) in &edits {
7832 let inlay = Inlay::edit_prediction(
7833 post_inc(&mut self.next_inlay_id),
7834 range.start,
7835 new_text.as_str(),
7836 );
7837 inlay_ids.push(inlay.id);
7838 inlays.push(inlay);
7839 }
7840
7841 self.splice_inlays(&[], inlays, cx);
7842 } else {
7843 let background_color = cx.theme().status().deleted_background;
7844 self.highlight_text::<EditPredictionHighlight>(
7845 edits.iter().map(|(range, _)| range.clone()).collect(),
7846 HighlightStyle {
7847 background_color: Some(background_color),
7848 ..Default::default()
7849 },
7850 cx,
7851 );
7852 }
7853 }
7854
7855 invalidation_row_range = edit_start_row..edit_end_row;
7856
7857 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7858 if provider.show_tab_accept_marker() {
7859 EditDisplayMode::TabAccept
7860 } else {
7861 EditDisplayMode::Inline
7862 }
7863 } else {
7864 EditDisplayMode::DiffPopover
7865 };
7866
7867 EditPrediction::Edit {
7868 edits,
7869 edit_preview: edit_prediction.edit_preview,
7870 display_mode,
7871 snapshot,
7872 }
7873 };
7874
7875 let invalidation_range = multibuffer
7876 .anchor_before(Point::new(invalidation_row_range.start, 0))
7877 ..multibuffer.anchor_after(Point::new(
7878 invalidation_row_range.end,
7879 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7880 ));
7881
7882 self.stale_edit_prediction_in_menu = None;
7883 self.active_edit_prediction = Some(EditPredictionState {
7884 inlay_ids,
7885 completion,
7886 completion_id: edit_prediction.id,
7887 invalidation_range,
7888 });
7889
7890 cx.notify();
7891
7892 Some(())
7893 }
7894
7895 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7896 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7897 }
7898
7899 fn clear_tasks(&mut self) {
7900 self.tasks.clear()
7901 }
7902
7903 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7904 if self.tasks.insert(key, value).is_some() {
7905 // This case should hopefully be rare, but just in case...
7906 log::error!(
7907 "multiple different run targets found on a single line, only the last target will be rendered"
7908 )
7909 }
7910 }
7911
7912 /// Get all display points of breakpoints that will be rendered within editor
7913 ///
7914 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7915 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7916 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7917 fn active_breakpoints(
7918 &self,
7919 range: Range<DisplayRow>,
7920 window: &mut Window,
7921 cx: &mut Context<Self>,
7922 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7923 let mut breakpoint_display_points = HashMap::default();
7924
7925 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7926 return breakpoint_display_points;
7927 };
7928
7929 let snapshot = self.snapshot(window, cx);
7930
7931 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7932 let Some(project) = self.project() else {
7933 return breakpoint_display_points;
7934 };
7935
7936 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7937 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7938
7939 for (buffer_snapshot, range, excerpt_id) in
7940 multi_buffer_snapshot.range_to_buffer_ranges(range)
7941 {
7942 let Some(buffer) = project
7943 .read(cx)
7944 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7945 else {
7946 continue;
7947 };
7948 let breakpoints = breakpoint_store.read(cx).breakpoints(
7949 &buffer,
7950 Some(
7951 buffer_snapshot.anchor_before(range.start)
7952 ..buffer_snapshot.anchor_after(range.end),
7953 ),
7954 buffer_snapshot,
7955 cx,
7956 );
7957 for (breakpoint, state) in breakpoints {
7958 let multi_buffer_anchor =
7959 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7960 let position = multi_buffer_anchor
7961 .to_point(multi_buffer_snapshot)
7962 .to_display_point(&snapshot);
7963
7964 breakpoint_display_points.insert(
7965 position.row(),
7966 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7967 );
7968 }
7969 }
7970
7971 breakpoint_display_points
7972 }
7973
7974 fn breakpoint_context_menu(
7975 &self,
7976 anchor: Anchor,
7977 window: &mut Window,
7978 cx: &mut Context<Self>,
7979 ) -> Entity<ui::ContextMenu> {
7980 let weak_editor = cx.weak_entity();
7981 let focus_handle = self.focus_handle(cx);
7982
7983 let row = self
7984 .buffer
7985 .read(cx)
7986 .snapshot(cx)
7987 .summary_for_anchor::<Point>(&anchor)
7988 .row;
7989
7990 let breakpoint = self
7991 .breakpoint_at_row(row, window, cx)
7992 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7993
7994 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7995 "Edit Log Breakpoint"
7996 } else {
7997 "Set Log Breakpoint"
7998 };
7999
8000 let condition_breakpoint_msg = if breakpoint
8001 .as_ref()
8002 .is_some_and(|bp| bp.1.condition.is_some())
8003 {
8004 "Edit Condition Breakpoint"
8005 } else {
8006 "Set Condition Breakpoint"
8007 };
8008
8009 let hit_condition_breakpoint_msg = if breakpoint
8010 .as_ref()
8011 .is_some_and(|bp| bp.1.hit_condition.is_some())
8012 {
8013 "Edit Hit Condition Breakpoint"
8014 } else {
8015 "Set Hit Condition Breakpoint"
8016 };
8017
8018 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8019 "Unset Breakpoint"
8020 } else {
8021 "Set Breakpoint"
8022 };
8023
8024 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8025
8026 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8027 BreakpointState::Enabled => Some("Disable"),
8028 BreakpointState::Disabled => Some("Enable"),
8029 });
8030
8031 let (anchor, breakpoint) =
8032 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8033
8034 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8035 menu.on_blur_subscription(Subscription::new(|| {}))
8036 .context(focus_handle)
8037 .when(run_to_cursor, |this| {
8038 let weak_editor = weak_editor.clone();
8039 this.entry("Run to cursor", None, move |window, cx| {
8040 weak_editor
8041 .update(cx, |editor, cx| {
8042 editor.change_selections(
8043 SelectionEffects::no_scroll(),
8044 window,
8045 cx,
8046 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8047 );
8048 })
8049 .ok();
8050
8051 window.dispatch_action(Box::new(RunToCursor), cx);
8052 })
8053 .separator()
8054 })
8055 .when_some(toggle_state_msg, |this, msg| {
8056 this.entry(msg, None, {
8057 let weak_editor = weak_editor.clone();
8058 let breakpoint = breakpoint.clone();
8059 move |_window, cx| {
8060 weak_editor
8061 .update(cx, |this, cx| {
8062 this.edit_breakpoint_at_anchor(
8063 anchor,
8064 breakpoint.as_ref().clone(),
8065 BreakpointEditAction::InvertState,
8066 cx,
8067 );
8068 })
8069 .log_err();
8070 }
8071 })
8072 })
8073 .entry(set_breakpoint_msg, None, {
8074 let weak_editor = weak_editor.clone();
8075 let breakpoint = breakpoint.clone();
8076 move |_window, cx| {
8077 weak_editor
8078 .update(cx, |this, cx| {
8079 this.edit_breakpoint_at_anchor(
8080 anchor,
8081 breakpoint.as_ref().clone(),
8082 BreakpointEditAction::Toggle,
8083 cx,
8084 );
8085 })
8086 .log_err();
8087 }
8088 })
8089 .entry(log_breakpoint_msg, None, {
8090 let breakpoint = breakpoint.clone();
8091 let weak_editor = weak_editor.clone();
8092 move |window, cx| {
8093 weak_editor
8094 .update(cx, |this, cx| {
8095 this.add_edit_breakpoint_block(
8096 anchor,
8097 breakpoint.as_ref(),
8098 BreakpointPromptEditAction::Log,
8099 window,
8100 cx,
8101 );
8102 })
8103 .log_err();
8104 }
8105 })
8106 .entry(condition_breakpoint_msg, None, {
8107 let breakpoint = breakpoint.clone();
8108 let weak_editor = weak_editor.clone();
8109 move |window, cx| {
8110 weak_editor
8111 .update(cx, |this, cx| {
8112 this.add_edit_breakpoint_block(
8113 anchor,
8114 breakpoint.as_ref(),
8115 BreakpointPromptEditAction::Condition,
8116 window,
8117 cx,
8118 );
8119 })
8120 .log_err();
8121 }
8122 })
8123 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8124 weak_editor
8125 .update(cx, |this, cx| {
8126 this.add_edit_breakpoint_block(
8127 anchor,
8128 breakpoint.as_ref(),
8129 BreakpointPromptEditAction::HitCondition,
8130 window,
8131 cx,
8132 );
8133 })
8134 .log_err();
8135 })
8136 })
8137 }
8138
8139 fn render_breakpoint(
8140 &self,
8141 position: Anchor,
8142 row: DisplayRow,
8143 breakpoint: &Breakpoint,
8144 state: Option<BreakpointSessionState>,
8145 cx: &mut Context<Self>,
8146 ) -> IconButton {
8147 let is_rejected = state.is_some_and(|s| !s.verified);
8148 // Is it a breakpoint that shows up when hovering over gutter?
8149 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8150 (false, false),
8151 |PhantomBreakpointIndicator {
8152 is_active,
8153 display_row,
8154 collides_with_existing_breakpoint,
8155 }| {
8156 (
8157 is_active && display_row == row,
8158 collides_with_existing_breakpoint,
8159 )
8160 },
8161 );
8162
8163 let (color, icon) = {
8164 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8165 (false, false) => ui::IconName::DebugBreakpoint,
8166 (true, false) => ui::IconName::DebugLogBreakpoint,
8167 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8168 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8169 };
8170
8171 let color = if is_phantom {
8172 Color::Hint
8173 } else if is_rejected {
8174 Color::Disabled
8175 } else {
8176 Color::Debugger
8177 };
8178
8179 (color, icon)
8180 };
8181
8182 let breakpoint = Arc::from(breakpoint.clone());
8183
8184 let alt_as_text = gpui::Keystroke {
8185 modifiers: Modifiers::secondary_key(),
8186 ..Default::default()
8187 };
8188 let primary_action_text = if breakpoint.is_disabled() {
8189 "Enable breakpoint"
8190 } else if is_phantom && !collides_with_existing {
8191 "Set breakpoint"
8192 } else {
8193 "Unset breakpoint"
8194 };
8195 let focus_handle = self.focus_handle.clone();
8196
8197 let meta = if is_rejected {
8198 SharedString::from("No executable code is associated with this line.")
8199 } else if collides_with_existing && !breakpoint.is_disabled() {
8200 SharedString::from(format!(
8201 "{alt_as_text}-click to disable,\nright-click for more options."
8202 ))
8203 } else {
8204 SharedString::from("Right-click for more options.")
8205 };
8206 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8207 .icon_size(IconSize::XSmall)
8208 .size(ui::ButtonSize::None)
8209 .when(is_rejected, |this| {
8210 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8211 })
8212 .icon_color(color)
8213 .style(ButtonStyle::Transparent)
8214 .on_click(cx.listener({
8215 let breakpoint = breakpoint.clone();
8216
8217 move |editor, event: &ClickEvent, window, cx| {
8218 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8219 BreakpointEditAction::InvertState
8220 } else {
8221 BreakpointEditAction::Toggle
8222 };
8223
8224 window.focus(&editor.focus_handle(cx));
8225 editor.edit_breakpoint_at_anchor(
8226 position,
8227 breakpoint.as_ref().clone(),
8228 edit_action,
8229 cx,
8230 );
8231 }
8232 }))
8233 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8234 editor.set_breakpoint_context_menu(
8235 row,
8236 Some(position),
8237 event.position(),
8238 window,
8239 cx,
8240 );
8241 }))
8242 .tooltip(move |window, cx| {
8243 Tooltip::with_meta_in(
8244 primary_action_text,
8245 Some(&ToggleBreakpoint),
8246 meta.clone(),
8247 &focus_handle,
8248 window,
8249 cx,
8250 )
8251 })
8252 }
8253
8254 fn build_tasks_context(
8255 project: &Entity<Project>,
8256 buffer: &Entity<Buffer>,
8257 buffer_row: u32,
8258 tasks: &Arc<RunnableTasks>,
8259 cx: &mut Context<Self>,
8260 ) -> Task<Option<task::TaskContext>> {
8261 let position = Point::new(buffer_row, tasks.column);
8262 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8263 let location = Location {
8264 buffer: buffer.clone(),
8265 range: range_start..range_start,
8266 };
8267 // Fill in the environmental variables from the tree-sitter captures
8268 let mut captured_task_variables = TaskVariables::default();
8269 for (capture_name, value) in tasks.extra_variables.clone() {
8270 captured_task_variables.insert(
8271 task::VariableName::Custom(capture_name.into()),
8272 value.clone(),
8273 );
8274 }
8275 project.update(cx, |project, cx| {
8276 project.task_store().update(cx, |task_store, cx| {
8277 task_store.task_context_for_location(captured_task_variables, location, cx)
8278 })
8279 })
8280 }
8281
8282 pub fn spawn_nearest_task(
8283 &mut self,
8284 action: &SpawnNearestTask,
8285 window: &mut Window,
8286 cx: &mut Context<Self>,
8287 ) {
8288 let Some((workspace, _)) = self.workspace.clone() else {
8289 return;
8290 };
8291 let Some(project) = self.project.clone() else {
8292 return;
8293 };
8294
8295 // Try to find a closest, enclosing node using tree-sitter that has a task
8296 let Some((buffer, buffer_row, tasks)) = self
8297 .find_enclosing_node_task(cx)
8298 // Or find the task that's closest in row-distance.
8299 .or_else(|| self.find_closest_task(cx))
8300 else {
8301 return;
8302 };
8303
8304 let reveal_strategy = action.reveal;
8305 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8306 cx.spawn_in(window, async move |_, cx| {
8307 let context = task_context.await?;
8308 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8309
8310 let resolved = &mut resolved_task.resolved;
8311 resolved.reveal = reveal_strategy;
8312
8313 workspace
8314 .update_in(cx, |workspace, window, cx| {
8315 workspace.schedule_resolved_task(
8316 task_source_kind,
8317 resolved_task,
8318 false,
8319 window,
8320 cx,
8321 );
8322 })
8323 .ok()
8324 })
8325 .detach();
8326 }
8327
8328 fn find_closest_task(
8329 &mut self,
8330 cx: &mut Context<Self>,
8331 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8332 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8333
8334 let ((buffer_id, row), tasks) = self
8335 .tasks
8336 .iter()
8337 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8338
8339 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8340 let tasks = Arc::new(tasks.to_owned());
8341 Some((buffer, *row, tasks))
8342 }
8343
8344 fn find_enclosing_node_task(
8345 &mut self,
8346 cx: &mut Context<Self>,
8347 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8348 let snapshot = self.buffer.read(cx).snapshot(cx);
8349 let offset = self.selections.newest::<usize>(cx).head();
8350 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8351 let buffer_id = excerpt.buffer().remote_id();
8352
8353 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8354 let mut cursor = layer.node().walk();
8355
8356 while cursor.goto_first_child_for_byte(offset).is_some() {
8357 if cursor.node().end_byte() == offset {
8358 cursor.goto_next_sibling();
8359 }
8360 }
8361
8362 // Ascend to the smallest ancestor that contains the range and has a task.
8363 loop {
8364 let node = cursor.node();
8365 let node_range = node.byte_range();
8366 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8367
8368 // Check if this node contains our offset
8369 if node_range.start <= offset && node_range.end >= offset {
8370 // If it contains offset, check for task
8371 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8372 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8373 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8374 }
8375 }
8376
8377 if !cursor.goto_parent() {
8378 break;
8379 }
8380 }
8381 None
8382 }
8383
8384 fn render_run_indicator(
8385 &self,
8386 _style: &EditorStyle,
8387 is_active: bool,
8388 row: DisplayRow,
8389 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8390 cx: &mut Context<Self>,
8391 ) -> IconButton {
8392 let color = Color::Muted;
8393 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8394
8395 IconButton::new(
8396 ("run_indicator", row.0 as usize),
8397 ui::IconName::PlayOutlined,
8398 )
8399 .shape(ui::IconButtonShape::Square)
8400 .icon_size(IconSize::XSmall)
8401 .icon_color(color)
8402 .toggle_state(is_active)
8403 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8404 let quick_launch = match e {
8405 ClickEvent::Keyboard(_) => true,
8406 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8407 };
8408
8409 window.focus(&editor.focus_handle(cx));
8410 editor.toggle_code_actions(
8411 &ToggleCodeActions {
8412 deployed_from: Some(CodeActionSource::RunMenu(row)),
8413 quick_launch,
8414 },
8415 window,
8416 cx,
8417 );
8418 }))
8419 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8420 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8421 }))
8422 }
8423
8424 pub fn context_menu_visible(&self) -> bool {
8425 !self.edit_prediction_preview_is_active()
8426 && self
8427 .context_menu
8428 .borrow()
8429 .as_ref()
8430 .map_or(false, |menu| menu.visible())
8431 }
8432
8433 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8434 self.context_menu
8435 .borrow()
8436 .as_ref()
8437 .map(|menu| menu.origin())
8438 }
8439
8440 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8441 self.context_menu_options = Some(options);
8442 }
8443
8444 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8445 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8446
8447 fn render_edit_prediction_popover(
8448 &mut self,
8449 text_bounds: &Bounds<Pixels>,
8450 content_origin: gpui::Point<Pixels>,
8451 right_margin: Pixels,
8452 editor_snapshot: &EditorSnapshot,
8453 visible_row_range: Range<DisplayRow>,
8454 scroll_top: f32,
8455 scroll_bottom: f32,
8456 line_layouts: &[LineWithInvisibles],
8457 line_height: Pixels,
8458 scroll_pixel_position: gpui::Point<Pixels>,
8459 newest_selection_head: Option<DisplayPoint>,
8460 editor_width: Pixels,
8461 style: &EditorStyle,
8462 window: &mut Window,
8463 cx: &mut App,
8464 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8465 if self.mode().is_minimap() {
8466 return None;
8467 }
8468 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8469
8470 if self.edit_prediction_visible_in_cursor_popover(true) {
8471 return None;
8472 }
8473
8474 match &active_edit_prediction.completion {
8475 EditPrediction::Move { target, .. } => {
8476 let target_display_point = target.to_display_point(editor_snapshot);
8477
8478 if self.edit_prediction_requires_modifier() {
8479 if !self.edit_prediction_preview_is_active() {
8480 return None;
8481 }
8482
8483 self.render_edit_prediction_modifier_jump_popover(
8484 text_bounds,
8485 content_origin,
8486 visible_row_range,
8487 line_layouts,
8488 line_height,
8489 scroll_pixel_position,
8490 newest_selection_head,
8491 target_display_point,
8492 window,
8493 cx,
8494 )
8495 } else {
8496 self.render_edit_prediction_eager_jump_popover(
8497 text_bounds,
8498 content_origin,
8499 editor_snapshot,
8500 visible_row_range,
8501 scroll_top,
8502 scroll_bottom,
8503 line_height,
8504 scroll_pixel_position,
8505 target_display_point,
8506 editor_width,
8507 window,
8508 cx,
8509 )
8510 }
8511 }
8512 EditPrediction::Edit {
8513 display_mode: EditDisplayMode::Inline,
8514 ..
8515 } => None,
8516 EditPrediction::Edit {
8517 display_mode: EditDisplayMode::TabAccept,
8518 edits,
8519 ..
8520 } => {
8521 let range = &edits.first()?.0;
8522 let target_display_point = range.end.to_display_point(editor_snapshot);
8523
8524 self.render_edit_prediction_end_of_line_popover(
8525 "Accept",
8526 editor_snapshot,
8527 visible_row_range,
8528 target_display_point,
8529 line_height,
8530 scroll_pixel_position,
8531 content_origin,
8532 editor_width,
8533 window,
8534 cx,
8535 )
8536 }
8537 EditPrediction::Edit {
8538 edits,
8539 edit_preview,
8540 display_mode: EditDisplayMode::DiffPopover,
8541 snapshot,
8542 } => self.render_edit_prediction_diff_popover(
8543 text_bounds,
8544 content_origin,
8545 right_margin,
8546 editor_snapshot,
8547 visible_row_range,
8548 line_layouts,
8549 line_height,
8550 scroll_pixel_position,
8551 newest_selection_head,
8552 editor_width,
8553 style,
8554 edits,
8555 edit_preview,
8556 snapshot,
8557 window,
8558 cx,
8559 ),
8560 }
8561 }
8562
8563 fn render_edit_prediction_modifier_jump_popover(
8564 &mut self,
8565 text_bounds: &Bounds<Pixels>,
8566 content_origin: gpui::Point<Pixels>,
8567 visible_row_range: Range<DisplayRow>,
8568 line_layouts: &[LineWithInvisibles],
8569 line_height: Pixels,
8570 scroll_pixel_position: gpui::Point<Pixels>,
8571 newest_selection_head: Option<DisplayPoint>,
8572 target_display_point: DisplayPoint,
8573 window: &mut Window,
8574 cx: &mut App,
8575 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8576 let scrolled_content_origin =
8577 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8578
8579 const SCROLL_PADDING_Y: Pixels = px(12.);
8580
8581 if target_display_point.row() < visible_row_range.start {
8582 return self.render_edit_prediction_scroll_popover(
8583 |_| SCROLL_PADDING_Y,
8584 IconName::ArrowUp,
8585 visible_row_range,
8586 line_layouts,
8587 newest_selection_head,
8588 scrolled_content_origin,
8589 window,
8590 cx,
8591 );
8592 } else if target_display_point.row() >= visible_row_range.end {
8593 return self.render_edit_prediction_scroll_popover(
8594 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8595 IconName::ArrowDown,
8596 visible_row_range,
8597 line_layouts,
8598 newest_selection_head,
8599 scrolled_content_origin,
8600 window,
8601 cx,
8602 );
8603 }
8604
8605 const POLE_WIDTH: Pixels = px(2.);
8606
8607 let line_layout =
8608 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8609 let target_column = target_display_point.column() as usize;
8610
8611 let target_x = line_layout.x_for_index(target_column);
8612 let target_y =
8613 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8614
8615 let flag_on_right = target_x < text_bounds.size.width / 2.;
8616
8617 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8618 border_color.l += 0.001;
8619
8620 let mut element = v_flex()
8621 .items_end()
8622 .when(flag_on_right, |el| el.items_start())
8623 .child(if flag_on_right {
8624 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8625 .rounded_bl(px(0.))
8626 .rounded_tl(px(0.))
8627 .border_l_2()
8628 .border_color(border_color)
8629 } else {
8630 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8631 .rounded_br(px(0.))
8632 .rounded_tr(px(0.))
8633 .border_r_2()
8634 .border_color(border_color)
8635 })
8636 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8637 .into_any();
8638
8639 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8640
8641 let mut origin = scrolled_content_origin + point(target_x, target_y)
8642 - point(
8643 if flag_on_right {
8644 POLE_WIDTH
8645 } else {
8646 size.width - POLE_WIDTH
8647 },
8648 size.height - line_height,
8649 );
8650
8651 origin.x = origin.x.max(content_origin.x);
8652
8653 element.prepaint_at(origin, window, cx);
8654
8655 Some((element, origin))
8656 }
8657
8658 fn render_edit_prediction_scroll_popover(
8659 &mut self,
8660 to_y: impl Fn(Size<Pixels>) -> Pixels,
8661 scroll_icon: IconName,
8662 visible_row_range: Range<DisplayRow>,
8663 line_layouts: &[LineWithInvisibles],
8664 newest_selection_head: Option<DisplayPoint>,
8665 scrolled_content_origin: gpui::Point<Pixels>,
8666 window: &mut Window,
8667 cx: &mut App,
8668 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8669 let mut element = self
8670 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8671 .into_any();
8672
8673 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8674
8675 let cursor = newest_selection_head?;
8676 let cursor_row_layout =
8677 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8678 let cursor_column = cursor.column() as usize;
8679
8680 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8681
8682 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8683
8684 element.prepaint_at(origin, window, cx);
8685 Some((element, origin))
8686 }
8687
8688 fn render_edit_prediction_eager_jump_popover(
8689 &mut self,
8690 text_bounds: &Bounds<Pixels>,
8691 content_origin: gpui::Point<Pixels>,
8692 editor_snapshot: &EditorSnapshot,
8693 visible_row_range: Range<DisplayRow>,
8694 scroll_top: f32,
8695 scroll_bottom: f32,
8696 line_height: Pixels,
8697 scroll_pixel_position: gpui::Point<Pixels>,
8698 target_display_point: DisplayPoint,
8699 editor_width: Pixels,
8700 window: &mut Window,
8701 cx: &mut App,
8702 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8703 if target_display_point.row().as_f32() < scroll_top {
8704 let mut element = self
8705 .render_edit_prediction_line_popover(
8706 "Jump to Edit",
8707 Some(IconName::ArrowUp),
8708 window,
8709 cx,
8710 )?
8711 .into_any();
8712
8713 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8714 let offset = point(
8715 (text_bounds.size.width - size.width) / 2.,
8716 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8717 );
8718
8719 let origin = text_bounds.origin + offset;
8720 element.prepaint_at(origin, window, cx);
8721 Some((element, origin))
8722 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8723 let mut element = self
8724 .render_edit_prediction_line_popover(
8725 "Jump to Edit",
8726 Some(IconName::ArrowDown),
8727 window,
8728 cx,
8729 )?
8730 .into_any();
8731
8732 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8733 let offset = point(
8734 (text_bounds.size.width - size.width) / 2.,
8735 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8736 );
8737
8738 let origin = text_bounds.origin + offset;
8739 element.prepaint_at(origin, window, cx);
8740 Some((element, origin))
8741 } else {
8742 self.render_edit_prediction_end_of_line_popover(
8743 "Jump to Edit",
8744 editor_snapshot,
8745 visible_row_range,
8746 target_display_point,
8747 line_height,
8748 scroll_pixel_position,
8749 content_origin,
8750 editor_width,
8751 window,
8752 cx,
8753 )
8754 }
8755 }
8756
8757 fn render_edit_prediction_end_of_line_popover(
8758 self: &mut Editor,
8759 label: &'static str,
8760 editor_snapshot: &EditorSnapshot,
8761 visible_row_range: Range<DisplayRow>,
8762 target_display_point: DisplayPoint,
8763 line_height: Pixels,
8764 scroll_pixel_position: gpui::Point<Pixels>,
8765 content_origin: gpui::Point<Pixels>,
8766 editor_width: Pixels,
8767 window: &mut Window,
8768 cx: &mut App,
8769 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8770 let target_line_end = DisplayPoint::new(
8771 target_display_point.row(),
8772 editor_snapshot.line_len(target_display_point.row()),
8773 );
8774
8775 let mut element = self
8776 .render_edit_prediction_line_popover(label, None, window, cx)?
8777 .into_any();
8778
8779 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8780
8781 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8782
8783 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8784 let mut origin = start_point
8785 + line_origin
8786 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8787 origin.x = origin.x.max(content_origin.x);
8788
8789 let max_x = content_origin.x + editor_width - size.width;
8790
8791 if origin.x > max_x {
8792 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8793
8794 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8795 origin.y += offset;
8796 IconName::ArrowUp
8797 } else {
8798 origin.y -= offset;
8799 IconName::ArrowDown
8800 };
8801
8802 element = self
8803 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8804 .into_any();
8805
8806 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8807
8808 origin.x = content_origin.x + editor_width - size.width - px(2.);
8809 }
8810
8811 element.prepaint_at(origin, window, cx);
8812 Some((element, origin))
8813 }
8814
8815 fn render_edit_prediction_diff_popover(
8816 self: &Editor,
8817 text_bounds: &Bounds<Pixels>,
8818 content_origin: gpui::Point<Pixels>,
8819 right_margin: Pixels,
8820 editor_snapshot: &EditorSnapshot,
8821 visible_row_range: Range<DisplayRow>,
8822 line_layouts: &[LineWithInvisibles],
8823 line_height: Pixels,
8824 scroll_pixel_position: gpui::Point<Pixels>,
8825 newest_selection_head: Option<DisplayPoint>,
8826 editor_width: Pixels,
8827 style: &EditorStyle,
8828 edits: &Vec<(Range<Anchor>, String)>,
8829 edit_preview: &Option<language::EditPreview>,
8830 snapshot: &language::BufferSnapshot,
8831 window: &mut Window,
8832 cx: &mut App,
8833 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8834 let edit_start = edits
8835 .first()
8836 .unwrap()
8837 .0
8838 .start
8839 .to_display_point(editor_snapshot);
8840 let edit_end = edits
8841 .last()
8842 .unwrap()
8843 .0
8844 .end
8845 .to_display_point(editor_snapshot);
8846
8847 let is_visible = visible_row_range.contains(&edit_start.row())
8848 || visible_row_range.contains(&edit_end.row());
8849 if !is_visible {
8850 return None;
8851 }
8852
8853 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8854 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8855 } else {
8856 // Fallback for providers without edit_preview
8857 crate::edit_prediction_fallback_text(edits, cx)
8858 };
8859
8860 let styled_text = highlighted_edits.to_styled_text(&style.text);
8861 let line_count = highlighted_edits.text.lines().count();
8862
8863 const BORDER_WIDTH: Pixels = px(1.);
8864
8865 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8866 let has_keybind = keybind.is_some();
8867
8868 let mut element = h_flex()
8869 .items_start()
8870 .child(
8871 h_flex()
8872 .bg(cx.theme().colors().editor_background)
8873 .border(BORDER_WIDTH)
8874 .shadow_xs()
8875 .border_color(cx.theme().colors().border)
8876 .rounded_l_lg()
8877 .when(line_count > 1, |el| el.rounded_br_lg())
8878 .pr_1()
8879 .child(styled_text),
8880 )
8881 .child(
8882 h_flex()
8883 .h(line_height + BORDER_WIDTH * 2.)
8884 .px_1p5()
8885 .gap_1()
8886 // Workaround: For some reason, there's a gap if we don't do this
8887 .ml(-BORDER_WIDTH)
8888 .shadow(vec![gpui::BoxShadow {
8889 color: gpui::black().opacity(0.05),
8890 offset: point(px(1.), px(1.)),
8891 blur_radius: px(2.),
8892 spread_radius: px(0.),
8893 }])
8894 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8895 .border(BORDER_WIDTH)
8896 .border_color(cx.theme().colors().border)
8897 .rounded_r_lg()
8898 .id("edit_prediction_diff_popover_keybind")
8899 .when(!has_keybind, |el| {
8900 let status_colors = cx.theme().status();
8901
8902 el.bg(status_colors.error_background)
8903 .border_color(status_colors.error.opacity(0.6))
8904 .child(Icon::new(IconName::Info).color(Color::Error))
8905 .cursor_default()
8906 .hoverable_tooltip(move |_window, cx| {
8907 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8908 })
8909 })
8910 .children(keybind),
8911 )
8912 .into_any();
8913
8914 let longest_row =
8915 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8916 let longest_line_width = if visible_row_range.contains(&longest_row) {
8917 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8918 } else {
8919 layout_line(
8920 longest_row,
8921 editor_snapshot,
8922 style,
8923 editor_width,
8924 |_| false,
8925 window,
8926 cx,
8927 )
8928 .width
8929 };
8930
8931 let viewport_bounds =
8932 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8933 right: -right_margin,
8934 ..Default::default()
8935 });
8936
8937 let x_after_longest =
8938 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8939 - scroll_pixel_position.x;
8940
8941 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8942
8943 // Fully visible if it can be displayed within the window (allow overlapping other
8944 // panes). However, this is only allowed if the popover starts within text_bounds.
8945 let can_position_to_the_right = x_after_longest < text_bounds.right()
8946 && x_after_longest + element_bounds.width < viewport_bounds.right();
8947
8948 let mut origin = if can_position_to_the_right {
8949 point(
8950 x_after_longest,
8951 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8952 - scroll_pixel_position.y,
8953 )
8954 } else {
8955 let cursor_row = newest_selection_head.map(|head| head.row());
8956 let above_edit = edit_start
8957 .row()
8958 .0
8959 .checked_sub(line_count as u32)
8960 .map(DisplayRow);
8961 let below_edit = Some(edit_end.row() + 1);
8962 let above_cursor =
8963 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8964 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8965
8966 // Place the edit popover adjacent to the edit if there is a location
8967 // available that is onscreen and does not obscure the cursor. Otherwise,
8968 // place it adjacent to the cursor.
8969 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8970 .into_iter()
8971 .flatten()
8972 .find(|&start_row| {
8973 let end_row = start_row + line_count as u32;
8974 visible_row_range.contains(&start_row)
8975 && visible_row_range.contains(&end_row)
8976 && cursor_row.map_or(true, |cursor_row| {
8977 !((start_row..end_row).contains(&cursor_row))
8978 })
8979 })?;
8980
8981 content_origin
8982 + point(
8983 -scroll_pixel_position.x,
8984 row_target.as_f32() * line_height - scroll_pixel_position.y,
8985 )
8986 };
8987
8988 origin.x -= BORDER_WIDTH;
8989
8990 window.defer_draw(element, origin, 1);
8991
8992 // Do not return an element, since it will already be drawn due to defer_draw.
8993 None
8994 }
8995
8996 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8997 px(30.)
8998 }
8999
9000 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9001 if self.read_only(cx) {
9002 cx.theme().players().read_only()
9003 } else {
9004 self.style.as_ref().unwrap().local_player
9005 }
9006 }
9007
9008 fn render_edit_prediction_accept_keybind(
9009 &self,
9010 window: &mut Window,
9011 cx: &App,
9012 ) -> Option<AnyElement> {
9013 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9014 let accept_keystroke = accept_binding.keystroke()?;
9015
9016 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9017
9018 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
9019 Color::Accent
9020 } else {
9021 Color::Muted
9022 };
9023
9024 h_flex()
9025 .px_0p5()
9026 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9027 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9028 .text_size(TextSize::XSmall.rems(cx))
9029 .child(h_flex().children(ui::render_modifiers(
9030 &accept_keystroke.modifiers,
9031 PlatformStyle::platform(),
9032 Some(modifiers_color),
9033 Some(IconSize::XSmall.rems().into()),
9034 true,
9035 )))
9036 .when(is_platform_style_mac, |parent| {
9037 parent.child(accept_keystroke.key.clone())
9038 })
9039 .when(!is_platform_style_mac, |parent| {
9040 parent.child(
9041 Key::new(
9042 util::capitalize(&accept_keystroke.key),
9043 Some(Color::Default),
9044 )
9045 .size(Some(IconSize::XSmall.rems().into())),
9046 )
9047 })
9048 .into_any()
9049 .into()
9050 }
9051
9052 fn render_edit_prediction_line_popover(
9053 &self,
9054 label: impl Into<SharedString>,
9055 icon: Option<IconName>,
9056 window: &mut Window,
9057 cx: &App,
9058 ) -> Option<Stateful<Div>> {
9059 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9060
9061 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9062 let has_keybind = keybind.is_some();
9063
9064 let result = h_flex()
9065 .id("ep-line-popover")
9066 .py_0p5()
9067 .pl_1()
9068 .pr(padding_right)
9069 .gap_1()
9070 .rounded_md()
9071 .border_1()
9072 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9073 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9074 .shadow_xs()
9075 .when(!has_keybind, |el| {
9076 let status_colors = cx.theme().status();
9077
9078 el.bg(status_colors.error_background)
9079 .border_color(status_colors.error.opacity(0.6))
9080 .pl_2()
9081 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9082 .cursor_default()
9083 .hoverable_tooltip(move |_window, cx| {
9084 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9085 })
9086 })
9087 .children(keybind)
9088 .child(
9089 Label::new(label)
9090 .size(LabelSize::Small)
9091 .when(!has_keybind, |el| {
9092 el.color(cx.theme().status().error.into()).strikethrough()
9093 }),
9094 )
9095 .when(!has_keybind, |el| {
9096 el.child(
9097 h_flex().ml_1().child(
9098 Icon::new(IconName::Info)
9099 .size(IconSize::Small)
9100 .color(cx.theme().status().error.into()),
9101 ),
9102 )
9103 })
9104 .when_some(icon, |element, icon| {
9105 element.child(
9106 div()
9107 .mt(px(1.5))
9108 .child(Icon::new(icon).size(IconSize::Small)),
9109 )
9110 });
9111
9112 Some(result)
9113 }
9114
9115 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9116 let accent_color = cx.theme().colors().text_accent;
9117 let editor_bg_color = cx.theme().colors().editor_background;
9118 editor_bg_color.blend(accent_color.opacity(0.1))
9119 }
9120
9121 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9122 let accent_color = cx.theme().colors().text_accent;
9123 let editor_bg_color = cx.theme().colors().editor_background;
9124 editor_bg_color.blend(accent_color.opacity(0.6))
9125 }
9126 fn get_prediction_provider_icon_name(
9127 provider: &Option<RegisteredEditPredictionProvider>,
9128 ) -> IconName {
9129 match provider {
9130 Some(provider) => match provider.provider.name() {
9131 "copilot" => IconName::Copilot,
9132 "supermaven" => IconName::Supermaven,
9133 _ => IconName::ZedPredict,
9134 },
9135 None => IconName::ZedPredict,
9136 }
9137 }
9138
9139 fn render_edit_prediction_cursor_popover(
9140 &self,
9141 min_width: Pixels,
9142 max_width: Pixels,
9143 cursor_point: Point,
9144 style: &EditorStyle,
9145 accept_keystroke: Option<&gpui::Keystroke>,
9146 _window: &Window,
9147 cx: &mut Context<Editor>,
9148 ) -> Option<AnyElement> {
9149 let provider = self.edit_prediction_provider.as_ref()?;
9150 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9151
9152 if provider.provider.needs_terms_acceptance(cx) {
9153 return Some(
9154 h_flex()
9155 .min_w(min_width)
9156 .flex_1()
9157 .px_2()
9158 .py_1()
9159 .gap_3()
9160 .elevation_2(cx)
9161 .hover(|style| style.bg(cx.theme().colors().element_hover))
9162 .id("accept-terms")
9163 .cursor_pointer()
9164 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9165 .on_click(cx.listener(|this, _event, window, cx| {
9166 cx.stop_propagation();
9167 this.report_editor_event(ReportEditorEvent::ZetaTosClicked, None, cx);
9168 window.dispatch_action(
9169 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9170 cx,
9171 );
9172 }))
9173 .child(
9174 h_flex()
9175 .flex_1()
9176 .gap_2()
9177 .child(Icon::new(provider_icon))
9178 .child(Label::new("Accept Terms of Service"))
9179 .child(div().w_full())
9180 .child(
9181 Icon::new(IconName::ArrowUpRight)
9182 .color(Color::Muted)
9183 .size(IconSize::Small),
9184 )
9185 .into_any_element(),
9186 )
9187 .into_any(),
9188 );
9189 }
9190
9191 let is_refreshing = provider.provider.is_refreshing(cx);
9192
9193 fn pending_completion_container(icon: IconName) -> Div {
9194 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9195 }
9196
9197 let completion = match &self.active_edit_prediction {
9198 Some(prediction) => {
9199 if !self.has_visible_completions_menu() {
9200 const RADIUS: Pixels = px(6.);
9201 const BORDER_WIDTH: Pixels = px(1.);
9202
9203 return Some(
9204 h_flex()
9205 .elevation_2(cx)
9206 .border(BORDER_WIDTH)
9207 .border_color(cx.theme().colors().border)
9208 .when(accept_keystroke.is_none(), |el| {
9209 el.border_color(cx.theme().status().error)
9210 })
9211 .rounded(RADIUS)
9212 .rounded_tl(px(0.))
9213 .overflow_hidden()
9214 .child(div().px_1p5().child(match &prediction.completion {
9215 EditPrediction::Move { target, snapshot } => {
9216 use text::ToPoint as _;
9217 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9218 {
9219 Icon::new(IconName::ZedPredictDown)
9220 } else {
9221 Icon::new(IconName::ZedPredictUp)
9222 }
9223 }
9224 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9225 }))
9226 .child(
9227 h_flex()
9228 .gap_1()
9229 .py_1()
9230 .px_2()
9231 .rounded_r(RADIUS - BORDER_WIDTH)
9232 .border_l_1()
9233 .border_color(cx.theme().colors().border)
9234 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9235 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9236 el.child(
9237 Label::new("Hold")
9238 .size(LabelSize::Small)
9239 .when(accept_keystroke.is_none(), |el| {
9240 el.strikethrough()
9241 })
9242 .line_height_style(LineHeightStyle::UiLabel),
9243 )
9244 })
9245 .id("edit_prediction_cursor_popover_keybind")
9246 .when(accept_keystroke.is_none(), |el| {
9247 let status_colors = cx.theme().status();
9248
9249 el.bg(status_colors.error_background)
9250 .border_color(status_colors.error.opacity(0.6))
9251 .child(Icon::new(IconName::Info).color(Color::Error))
9252 .cursor_default()
9253 .hoverable_tooltip(move |_window, cx| {
9254 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9255 .into()
9256 })
9257 })
9258 .when_some(
9259 accept_keystroke.as_ref(),
9260 |el, accept_keystroke| {
9261 el.child(h_flex().children(ui::render_modifiers(
9262 &accept_keystroke.modifiers,
9263 PlatformStyle::platform(),
9264 Some(Color::Default),
9265 Some(IconSize::XSmall.rems().into()),
9266 false,
9267 )))
9268 },
9269 ),
9270 )
9271 .into_any(),
9272 );
9273 }
9274
9275 self.render_edit_prediction_cursor_popover_preview(
9276 prediction,
9277 cursor_point,
9278 style,
9279 cx,
9280 )?
9281 }
9282
9283 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9284 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9285 stale_completion,
9286 cursor_point,
9287 style,
9288 cx,
9289 )?,
9290
9291 None => pending_completion_container(provider_icon)
9292 .child(Label::new("...").size(LabelSize::Small)),
9293 },
9294
9295 None => pending_completion_container(provider_icon)
9296 .child(Label::new("...").size(LabelSize::Small)),
9297 };
9298
9299 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9300 completion
9301 .with_animation(
9302 "loading-completion",
9303 Animation::new(Duration::from_secs(2))
9304 .repeat()
9305 .with_easing(pulsating_between(0.4, 0.8)),
9306 |label, delta| label.opacity(delta),
9307 )
9308 .into_any_element()
9309 } else {
9310 completion.into_any_element()
9311 };
9312
9313 let has_completion = self.active_edit_prediction.is_some();
9314
9315 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9316 Some(
9317 h_flex()
9318 .min_w(min_width)
9319 .max_w(max_width)
9320 .flex_1()
9321 .elevation_2(cx)
9322 .border_color(cx.theme().colors().border)
9323 .child(
9324 div()
9325 .flex_1()
9326 .py_1()
9327 .px_2()
9328 .overflow_hidden()
9329 .child(completion),
9330 )
9331 .when_some(accept_keystroke, |el, accept_keystroke| {
9332 if !accept_keystroke.modifiers.modified() {
9333 return el;
9334 }
9335
9336 el.child(
9337 h_flex()
9338 .h_full()
9339 .border_l_1()
9340 .rounded_r_lg()
9341 .border_color(cx.theme().colors().border)
9342 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9343 .gap_1()
9344 .py_1()
9345 .px_2()
9346 .child(
9347 h_flex()
9348 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9349 .when(is_platform_style_mac, |parent| parent.gap_1())
9350 .child(h_flex().children(ui::render_modifiers(
9351 &accept_keystroke.modifiers,
9352 PlatformStyle::platform(),
9353 Some(if !has_completion {
9354 Color::Muted
9355 } else {
9356 Color::Default
9357 }),
9358 None,
9359 false,
9360 ))),
9361 )
9362 .child(Label::new("Preview").into_any_element())
9363 .opacity(if has_completion { 1.0 } else { 0.4 }),
9364 )
9365 })
9366 .into_any(),
9367 )
9368 }
9369
9370 fn render_edit_prediction_cursor_popover_preview(
9371 &self,
9372 completion: &EditPredictionState,
9373 cursor_point: Point,
9374 style: &EditorStyle,
9375 cx: &mut Context<Editor>,
9376 ) -> Option<Div> {
9377 use text::ToPoint as _;
9378
9379 fn render_relative_row_jump(
9380 prefix: impl Into<String>,
9381 current_row: u32,
9382 target_row: u32,
9383 ) -> Div {
9384 let (row_diff, arrow) = if target_row < current_row {
9385 (current_row - target_row, IconName::ArrowUp)
9386 } else {
9387 (target_row - current_row, IconName::ArrowDown)
9388 };
9389
9390 h_flex()
9391 .child(
9392 Label::new(format!("{}{}", prefix.into(), row_diff))
9393 .color(Color::Muted)
9394 .size(LabelSize::Small),
9395 )
9396 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9397 }
9398
9399 let supports_jump = self
9400 .edit_prediction_provider
9401 .as_ref()
9402 .map(|provider| provider.provider.supports_jump_to_edit())
9403 .unwrap_or(true);
9404
9405 match &completion.completion {
9406 EditPrediction::Move {
9407 target, snapshot, ..
9408 } => {
9409 if !supports_jump {
9410 return None;
9411 }
9412
9413 Some(
9414 h_flex()
9415 .px_2()
9416 .gap_2()
9417 .flex_1()
9418 .child(
9419 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9420 Icon::new(IconName::ZedPredictDown)
9421 } else {
9422 Icon::new(IconName::ZedPredictUp)
9423 },
9424 )
9425 .child(Label::new("Jump to Edit")),
9426 )
9427 }
9428
9429 EditPrediction::Edit {
9430 edits,
9431 edit_preview,
9432 snapshot,
9433 display_mode: _,
9434 } => {
9435 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9436
9437 let (highlighted_edits, has_more_lines) =
9438 if let Some(edit_preview) = edit_preview.as_ref() {
9439 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9440 .first_line_preview()
9441 } else {
9442 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9443 };
9444
9445 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9446 .with_default_highlights(&style.text, highlighted_edits.highlights);
9447
9448 let preview = h_flex()
9449 .gap_1()
9450 .min_w_16()
9451 .child(styled_text)
9452 .when(has_more_lines, |parent| parent.child("…"));
9453
9454 let left = if supports_jump && first_edit_row != cursor_point.row {
9455 render_relative_row_jump("", cursor_point.row, first_edit_row)
9456 .into_any_element()
9457 } else {
9458 let icon_name =
9459 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9460 Icon::new(icon_name).into_any_element()
9461 };
9462
9463 Some(
9464 h_flex()
9465 .h_full()
9466 .flex_1()
9467 .gap_2()
9468 .pr_1()
9469 .overflow_x_hidden()
9470 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9471 .child(left)
9472 .child(preview),
9473 )
9474 }
9475 }
9476 }
9477
9478 pub fn render_context_menu(
9479 &self,
9480 style: &EditorStyle,
9481 max_height_in_lines: u32,
9482 window: &mut Window,
9483 cx: &mut Context<Editor>,
9484 ) -> Option<AnyElement> {
9485 let menu = self.context_menu.borrow();
9486 let menu = menu.as_ref()?;
9487 if !menu.visible() {
9488 return None;
9489 };
9490 Some(menu.render(style, max_height_in_lines, window, cx))
9491 }
9492
9493 fn render_context_menu_aside(
9494 &mut self,
9495 max_size: Size<Pixels>,
9496 window: &mut Window,
9497 cx: &mut Context<Editor>,
9498 ) -> Option<AnyElement> {
9499 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9500 if menu.visible() {
9501 menu.render_aside(max_size, window, cx)
9502 } else {
9503 None
9504 }
9505 })
9506 }
9507
9508 fn hide_context_menu(
9509 &mut self,
9510 window: &mut Window,
9511 cx: &mut Context<Self>,
9512 ) -> Option<CodeContextMenu> {
9513 cx.notify();
9514 self.completion_tasks.clear();
9515 let context_menu = self.context_menu.borrow_mut().take();
9516 self.stale_edit_prediction_in_menu.take();
9517 self.update_visible_edit_prediction(window, cx);
9518 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9519 && let Some(completion_provider) = &self.completion_provider
9520 {
9521 completion_provider.selection_changed(None, window, cx);
9522 }
9523 context_menu
9524 }
9525
9526 fn show_snippet_choices(
9527 &mut self,
9528 choices: &Vec<String>,
9529 selection: Range<Anchor>,
9530 cx: &mut Context<Self>,
9531 ) {
9532 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9533 (Some(a), Some(b)) if a == b => a,
9534 _ => {
9535 log::error!("expected anchor range to have matching buffer IDs");
9536 return;
9537 }
9538 };
9539 let multi_buffer = self.buffer().read(cx);
9540 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9541 return;
9542 };
9543
9544 let id = post_inc(&mut self.next_completion_id);
9545 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9546 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9547 CompletionsMenu::new_snippet_choices(
9548 id,
9549 true,
9550 choices,
9551 selection,
9552 buffer,
9553 snippet_sort_order,
9554 ),
9555 ));
9556 }
9557
9558 pub fn insert_snippet(
9559 &mut self,
9560 insertion_ranges: &[Range<usize>],
9561 snippet: Snippet,
9562 window: &mut Window,
9563 cx: &mut Context<Self>,
9564 ) -> Result<()> {
9565 struct Tabstop<T> {
9566 is_end_tabstop: bool,
9567 ranges: Vec<Range<T>>,
9568 choices: Option<Vec<String>>,
9569 }
9570
9571 let tabstops = self.buffer.update(cx, |buffer, cx| {
9572 let snippet_text: Arc<str> = snippet.text.clone().into();
9573 let edits = insertion_ranges
9574 .iter()
9575 .cloned()
9576 .map(|range| (range, snippet_text.clone()));
9577 let autoindent_mode = AutoindentMode::Block {
9578 original_indent_columns: Vec::new(),
9579 };
9580 buffer.edit(edits, Some(autoindent_mode), cx);
9581
9582 let snapshot = &*buffer.read(cx);
9583 let snippet = &snippet;
9584 snippet
9585 .tabstops
9586 .iter()
9587 .map(|tabstop| {
9588 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9589 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9590 });
9591 let mut tabstop_ranges = tabstop
9592 .ranges
9593 .iter()
9594 .flat_map(|tabstop_range| {
9595 let mut delta = 0_isize;
9596 insertion_ranges.iter().map(move |insertion_range| {
9597 let insertion_start = insertion_range.start as isize + delta;
9598 delta +=
9599 snippet.text.len() as isize - insertion_range.len() as isize;
9600
9601 let start = ((insertion_start + tabstop_range.start) as usize)
9602 .min(snapshot.len());
9603 let end = ((insertion_start + tabstop_range.end) as usize)
9604 .min(snapshot.len());
9605 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9606 })
9607 })
9608 .collect::<Vec<_>>();
9609 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9610
9611 Tabstop {
9612 is_end_tabstop,
9613 ranges: tabstop_ranges,
9614 choices: tabstop.choices.clone(),
9615 }
9616 })
9617 .collect::<Vec<_>>()
9618 });
9619 if let Some(tabstop) = tabstops.first() {
9620 self.change_selections(Default::default(), window, cx, |s| {
9621 // Reverse order so that the first range is the newest created selection.
9622 // Completions will use it and autoscroll will prioritize it.
9623 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9624 });
9625
9626 if let Some(choices) = &tabstop.choices
9627 && let Some(selection) = tabstop.ranges.first()
9628 {
9629 self.show_snippet_choices(choices, selection.clone(), cx)
9630 }
9631
9632 // If we're already at the last tabstop and it's at the end of the snippet,
9633 // we're done, we don't need to keep the state around.
9634 if !tabstop.is_end_tabstop {
9635 let choices = tabstops
9636 .iter()
9637 .map(|tabstop| tabstop.choices.clone())
9638 .collect();
9639
9640 let ranges = tabstops
9641 .into_iter()
9642 .map(|tabstop| tabstop.ranges)
9643 .collect::<Vec<_>>();
9644
9645 self.snippet_stack.push(SnippetState {
9646 active_index: 0,
9647 ranges,
9648 choices,
9649 });
9650 }
9651
9652 // Check whether the just-entered snippet ends with an auto-closable bracket.
9653 if self.autoclose_regions.is_empty() {
9654 let snapshot = self.buffer.read(cx).snapshot(cx);
9655 let mut all_selections = self.selections.all::<Point>(cx);
9656 for selection in &mut all_selections {
9657 let selection_head = selection.head();
9658 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9659 continue;
9660 };
9661
9662 let mut bracket_pair = None;
9663 let max_lookup_length = scope
9664 .brackets()
9665 .map(|(pair, _)| {
9666 pair.start
9667 .as_str()
9668 .chars()
9669 .count()
9670 .max(pair.end.as_str().chars().count())
9671 })
9672 .max();
9673 if let Some(max_lookup_length) = max_lookup_length {
9674 let next_text = snapshot
9675 .chars_at(selection_head)
9676 .take(max_lookup_length)
9677 .collect::<String>();
9678 let prev_text = snapshot
9679 .reversed_chars_at(selection_head)
9680 .take(max_lookup_length)
9681 .collect::<String>();
9682
9683 for (pair, enabled) in scope.brackets() {
9684 if enabled
9685 && pair.close
9686 && prev_text.starts_with(pair.start.as_str())
9687 && next_text.starts_with(pair.end.as_str())
9688 {
9689 bracket_pair = Some(pair.clone());
9690 break;
9691 }
9692 }
9693 }
9694
9695 if let Some(pair) = bracket_pair {
9696 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9697 let autoclose_enabled =
9698 self.use_autoclose && snapshot_settings.use_autoclose;
9699 if autoclose_enabled {
9700 let start = snapshot.anchor_after(selection_head);
9701 let end = snapshot.anchor_after(selection_head);
9702 self.autoclose_regions.push(AutocloseRegion {
9703 selection_id: selection.id,
9704 range: start..end,
9705 pair,
9706 });
9707 }
9708 }
9709 }
9710 }
9711 }
9712 Ok(())
9713 }
9714
9715 pub fn move_to_next_snippet_tabstop(
9716 &mut self,
9717 window: &mut Window,
9718 cx: &mut Context<Self>,
9719 ) -> bool {
9720 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9721 }
9722
9723 pub fn move_to_prev_snippet_tabstop(
9724 &mut self,
9725 window: &mut Window,
9726 cx: &mut Context<Self>,
9727 ) -> bool {
9728 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9729 }
9730
9731 pub fn move_to_snippet_tabstop(
9732 &mut self,
9733 bias: Bias,
9734 window: &mut Window,
9735 cx: &mut Context<Self>,
9736 ) -> bool {
9737 if let Some(mut snippet) = self.snippet_stack.pop() {
9738 match bias {
9739 Bias::Left => {
9740 if snippet.active_index > 0 {
9741 snippet.active_index -= 1;
9742 } else {
9743 self.snippet_stack.push(snippet);
9744 return false;
9745 }
9746 }
9747 Bias::Right => {
9748 if snippet.active_index + 1 < snippet.ranges.len() {
9749 snippet.active_index += 1;
9750 } else {
9751 self.snippet_stack.push(snippet);
9752 return false;
9753 }
9754 }
9755 }
9756 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9757 self.change_selections(Default::default(), window, cx, |s| {
9758 // Reverse order so that the first range is the newest created selection.
9759 // Completions will use it and autoscroll will prioritize it.
9760 s.select_ranges(current_ranges.iter().rev().cloned())
9761 });
9762
9763 if let Some(choices) = &snippet.choices[snippet.active_index]
9764 && let Some(selection) = current_ranges.first()
9765 {
9766 self.show_snippet_choices(choices, selection.clone(), cx);
9767 }
9768
9769 // If snippet state is not at the last tabstop, push it back on the stack
9770 if snippet.active_index + 1 < snippet.ranges.len() {
9771 self.snippet_stack.push(snippet);
9772 }
9773 return true;
9774 }
9775 }
9776
9777 false
9778 }
9779
9780 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9781 self.transact(window, cx, |this, window, cx| {
9782 this.select_all(&SelectAll, window, cx);
9783 this.insert("", window, cx);
9784 });
9785 }
9786
9787 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9788 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9789 self.transact(window, cx, |this, window, cx| {
9790 this.select_autoclose_pair(window, cx);
9791 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9792 if !this.linked_edit_ranges.is_empty() {
9793 let selections = this.selections.all::<MultiBufferPoint>(cx);
9794 let snapshot = this.buffer.read(cx).snapshot(cx);
9795
9796 for selection in selections.iter() {
9797 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9798 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9799 if selection_start.buffer_id != selection_end.buffer_id {
9800 continue;
9801 }
9802 if let Some(ranges) =
9803 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9804 {
9805 for (buffer, entries) in ranges {
9806 linked_ranges.entry(buffer).or_default().extend(entries);
9807 }
9808 }
9809 }
9810 }
9811
9812 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9813 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9814 for selection in &mut selections {
9815 if selection.is_empty() {
9816 let old_head = selection.head();
9817 let mut new_head =
9818 movement::left(&display_map, old_head.to_display_point(&display_map))
9819 .to_point(&display_map);
9820 if let Some((buffer, line_buffer_range)) = display_map
9821 .buffer_snapshot
9822 .buffer_line_for_row(MultiBufferRow(old_head.row))
9823 {
9824 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9825 let indent_len = match indent_size.kind {
9826 IndentKind::Space => {
9827 buffer.settings_at(line_buffer_range.start, cx).tab_size
9828 }
9829 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9830 };
9831 if old_head.column <= indent_size.len && old_head.column > 0 {
9832 let indent_len = indent_len.get();
9833 new_head = cmp::min(
9834 new_head,
9835 MultiBufferPoint::new(
9836 old_head.row,
9837 ((old_head.column - 1) / indent_len) * indent_len,
9838 ),
9839 );
9840 }
9841 }
9842
9843 selection.set_head(new_head, SelectionGoal::None);
9844 }
9845 }
9846
9847 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9848 this.insert("", window, cx);
9849 let empty_str: Arc<str> = Arc::from("");
9850 for (buffer, edits) in linked_ranges {
9851 let snapshot = buffer.read(cx).snapshot();
9852 use text::ToPoint as TP;
9853
9854 let edits = edits
9855 .into_iter()
9856 .map(|range| {
9857 let end_point = TP::to_point(&range.end, &snapshot);
9858 let mut start_point = TP::to_point(&range.start, &snapshot);
9859
9860 if end_point == start_point {
9861 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9862 .saturating_sub(1);
9863 start_point =
9864 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9865 };
9866
9867 (start_point..end_point, empty_str.clone())
9868 })
9869 .sorted_by_key(|(range, _)| range.start)
9870 .collect::<Vec<_>>();
9871 buffer.update(cx, |this, cx| {
9872 this.edit(edits, None, cx);
9873 })
9874 }
9875 this.refresh_edit_prediction(true, false, window, cx);
9876 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9877 });
9878 }
9879
9880 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9881 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9882 self.transact(window, cx, |this, window, cx| {
9883 this.change_selections(Default::default(), window, cx, |s| {
9884 s.move_with(|map, selection| {
9885 if selection.is_empty() {
9886 let cursor = movement::right(map, selection.head());
9887 selection.end = cursor;
9888 selection.reversed = true;
9889 selection.goal = SelectionGoal::None;
9890 }
9891 })
9892 });
9893 this.insert("", window, cx);
9894 this.refresh_edit_prediction(true, false, window, cx);
9895 });
9896 }
9897
9898 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9899 if self.mode.is_single_line() {
9900 cx.propagate();
9901 return;
9902 }
9903
9904 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9905 if self.move_to_prev_snippet_tabstop(window, cx) {
9906 return;
9907 }
9908 self.outdent(&Outdent, window, cx);
9909 }
9910
9911 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9912 if self.mode.is_single_line() {
9913 cx.propagate();
9914 return;
9915 }
9916
9917 if self.move_to_next_snippet_tabstop(window, cx) {
9918 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9919 return;
9920 }
9921 if self.read_only(cx) {
9922 return;
9923 }
9924 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9925 let mut selections = self.selections.all_adjusted(cx);
9926 let buffer = self.buffer.read(cx);
9927 let snapshot = buffer.snapshot(cx);
9928 let rows_iter = selections.iter().map(|s| s.head().row);
9929 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9930
9931 let has_some_cursor_in_whitespace = selections
9932 .iter()
9933 .filter(|selection| selection.is_empty())
9934 .any(|selection| {
9935 let cursor = selection.head();
9936 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9937 cursor.column < current_indent.len
9938 });
9939
9940 let mut edits = Vec::new();
9941 let mut prev_edited_row = 0;
9942 let mut row_delta = 0;
9943 for selection in &mut selections {
9944 if selection.start.row != prev_edited_row {
9945 row_delta = 0;
9946 }
9947 prev_edited_row = selection.end.row;
9948
9949 // If the selection is non-empty, then increase the indentation of the selected lines.
9950 if !selection.is_empty() {
9951 row_delta =
9952 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9953 continue;
9954 }
9955
9956 let cursor = selection.head();
9957 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9958 if let Some(suggested_indent) =
9959 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9960 {
9961 // Don't do anything if already at suggested indent
9962 // and there is any other cursor which is not
9963 if has_some_cursor_in_whitespace
9964 && cursor.column == current_indent.len
9965 && current_indent.len == suggested_indent.len
9966 {
9967 continue;
9968 }
9969
9970 // Adjust line and move cursor to suggested indent
9971 // if cursor is not at suggested indent
9972 if cursor.column < suggested_indent.len
9973 && cursor.column <= current_indent.len
9974 && current_indent.len <= suggested_indent.len
9975 {
9976 selection.start = Point::new(cursor.row, suggested_indent.len);
9977 selection.end = selection.start;
9978 if row_delta == 0 {
9979 edits.extend(Buffer::edit_for_indent_size_adjustment(
9980 cursor.row,
9981 current_indent,
9982 suggested_indent,
9983 ));
9984 row_delta = suggested_indent.len - current_indent.len;
9985 }
9986 continue;
9987 }
9988
9989 // If current indent is more than suggested indent
9990 // only move cursor to current indent and skip indent
9991 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9992 selection.start = Point::new(cursor.row, current_indent.len);
9993 selection.end = selection.start;
9994 continue;
9995 }
9996 }
9997
9998 // Otherwise, insert a hard or soft tab.
9999 let settings = buffer.language_settings_at(cursor, cx);
10000 let tab_size = if settings.hard_tabs {
10001 IndentSize::tab()
10002 } else {
10003 let tab_size = settings.tab_size.get();
10004 let indent_remainder = snapshot
10005 .text_for_range(Point::new(cursor.row, 0)..cursor)
10006 .flat_map(str::chars)
10007 .fold(row_delta % tab_size, |counter: u32, c| {
10008 if c == '\t' {
10009 0
10010 } else {
10011 (counter + 1) % tab_size
10012 }
10013 });
10014
10015 let chars_to_next_tab_stop = tab_size - indent_remainder;
10016 IndentSize::spaces(chars_to_next_tab_stop)
10017 };
10018 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10019 selection.end = selection.start;
10020 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10021 row_delta += tab_size.len;
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 this.refresh_edit_prediction(true, false, window, cx);
10028 });
10029 }
10030
10031 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10032 if self.read_only(cx) {
10033 return;
10034 }
10035 if self.mode.is_single_line() {
10036 cx.propagate();
10037 return;
10038 }
10039
10040 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10041 let mut selections = self.selections.all::<Point>(cx);
10042 let mut prev_edited_row = 0;
10043 let mut row_delta = 0;
10044 let mut edits = Vec::new();
10045 let buffer = self.buffer.read(cx);
10046 let snapshot = buffer.snapshot(cx);
10047 for selection in &mut selections {
10048 if selection.start.row != prev_edited_row {
10049 row_delta = 0;
10050 }
10051 prev_edited_row = selection.end.row;
10052
10053 row_delta =
10054 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10055 }
10056
10057 self.transact(window, cx, |this, window, cx| {
10058 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10059 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10060 });
10061 }
10062
10063 fn indent_selection(
10064 buffer: &MultiBuffer,
10065 snapshot: &MultiBufferSnapshot,
10066 selection: &mut Selection<Point>,
10067 edits: &mut Vec<(Range<Point>, String)>,
10068 delta_for_start_row: u32,
10069 cx: &App,
10070 ) -> u32 {
10071 let settings = buffer.language_settings_at(selection.start, cx);
10072 let tab_size = settings.tab_size.get();
10073 let indent_kind = if settings.hard_tabs {
10074 IndentKind::Tab
10075 } else {
10076 IndentKind::Space
10077 };
10078 let mut start_row = selection.start.row;
10079 let mut end_row = selection.end.row + 1;
10080
10081 // If a selection ends at the beginning of a line, don't indent
10082 // that last line.
10083 if selection.end.column == 0 && selection.end.row > selection.start.row {
10084 end_row -= 1;
10085 }
10086
10087 // Avoid re-indenting a row that has already been indented by a
10088 // previous selection, but still update this selection's column
10089 // to reflect that indentation.
10090 if delta_for_start_row > 0 {
10091 start_row += 1;
10092 selection.start.column += delta_for_start_row;
10093 if selection.end.row == selection.start.row {
10094 selection.end.column += delta_for_start_row;
10095 }
10096 }
10097
10098 let mut delta_for_end_row = 0;
10099 let has_multiple_rows = start_row + 1 != end_row;
10100 for row in start_row..end_row {
10101 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10102 let indent_delta = match (current_indent.kind, indent_kind) {
10103 (IndentKind::Space, IndentKind::Space) => {
10104 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10105 IndentSize::spaces(columns_to_next_tab_stop)
10106 }
10107 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10108 (_, IndentKind::Tab) => IndentSize::tab(),
10109 };
10110
10111 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10112 0
10113 } else {
10114 selection.start.column
10115 };
10116 let row_start = Point::new(row, start);
10117 edits.push((
10118 row_start..row_start,
10119 indent_delta.chars().collect::<String>(),
10120 ));
10121
10122 // Update this selection's endpoints to reflect the indentation.
10123 if row == selection.start.row {
10124 selection.start.column += indent_delta.len;
10125 }
10126 if row == selection.end.row {
10127 selection.end.column += indent_delta.len;
10128 delta_for_end_row = indent_delta.len;
10129 }
10130 }
10131
10132 if selection.start.row == selection.end.row {
10133 delta_for_start_row + delta_for_end_row
10134 } else {
10135 delta_for_end_row
10136 }
10137 }
10138
10139 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10140 if self.read_only(cx) {
10141 return;
10142 }
10143 if self.mode.is_single_line() {
10144 cx.propagate();
10145 return;
10146 }
10147
10148 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10149 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10150 let selections = self.selections.all::<Point>(cx);
10151 let mut deletion_ranges = Vec::new();
10152 let mut last_outdent = None;
10153 {
10154 let buffer = self.buffer.read(cx);
10155 let snapshot = buffer.snapshot(cx);
10156 for selection in &selections {
10157 let settings = buffer.language_settings_at(selection.start, cx);
10158 let tab_size = settings.tab_size.get();
10159 let mut rows = selection.spanned_rows(false, &display_map);
10160
10161 // Avoid re-outdenting a row that has already been outdented by a
10162 // previous selection.
10163 if let Some(last_row) = last_outdent
10164 && last_row == rows.start
10165 {
10166 rows.start = rows.start.next_row();
10167 }
10168 let has_multiple_rows = rows.len() > 1;
10169 for row in rows.iter_rows() {
10170 let indent_size = snapshot.indent_size_for_line(row);
10171 if indent_size.len > 0 {
10172 let deletion_len = match indent_size.kind {
10173 IndentKind::Space => {
10174 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10175 if columns_to_prev_tab_stop == 0 {
10176 tab_size
10177 } else {
10178 columns_to_prev_tab_stop
10179 }
10180 }
10181 IndentKind::Tab => 1,
10182 };
10183 let start = if has_multiple_rows
10184 || deletion_len > selection.start.column
10185 || indent_size.len < selection.start.column
10186 {
10187 0
10188 } else {
10189 selection.start.column - deletion_len
10190 };
10191 deletion_ranges.push(
10192 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10193 );
10194 last_outdent = Some(row);
10195 }
10196 }
10197 }
10198 }
10199
10200 self.transact(window, cx, |this, window, cx| {
10201 this.buffer.update(cx, |buffer, cx| {
10202 let empty_str: Arc<str> = Arc::default();
10203 buffer.edit(
10204 deletion_ranges
10205 .into_iter()
10206 .map(|range| (range, empty_str.clone())),
10207 None,
10208 cx,
10209 );
10210 });
10211 let selections = this.selections.all::<usize>(cx);
10212 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10213 });
10214 }
10215
10216 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10217 if self.read_only(cx) {
10218 return;
10219 }
10220 if self.mode.is_single_line() {
10221 cx.propagate();
10222 return;
10223 }
10224
10225 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10226 let selections = self
10227 .selections
10228 .all::<usize>(cx)
10229 .into_iter()
10230 .map(|s| s.range());
10231
10232 self.transact(window, cx, |this, window, cx| {
10233 this.buffer.update(cx, |buffer, cx| {
10234 buffer.autoindent_ranges(selections, cx);
10235 });
10236 let selections = this.selections.all::<usize>(cx);
10237 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10238 });
10239 }
10240
10241 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10242 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10243 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10244 let selections = self.selections.all::<Point>(cx);
10245
10246 let mut new_cursors = Vec::new();
10247 let mut edit_ranges = Vec::new();
10248 let mut selections = selections.iter().peekable();
10249 while let Some(selection) = selections.next() {
10250 let mut rows = selection.spanned_rows(false, &display_map);
10251 let goal_display_column = selection.head().to_display_point(&display_map).column();
10252
10253 // Accumulate contiguous regions of rows that we want to delete.
10254 while let Some(next_selection) = selections.peek() {
10255 let next_rows = next_selection.spanned_rows(false, &display_map);
10256 if next_rows.start <= rows.end {
10257 rows.end = next_rows.end;
10258 selections.next().unwrap();
10259 } else {
10260 break;
10261 }
10262 }
10263
10264 let buffer = &display_map.buffer_snapshot;
10265 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10266 let edit_end;
10267 let cursor_buffer_row;
10268 if buffer.max_point().row >= rows.end.0 {
10269 // If there's a line after the range, delete the \n from the end of the row range
10270 // and position the cursor on the next line.
10271 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10272 cursor_buffer_row = rows.end;
10273 } else {
10274 // If there isn't a line after the range, delete the \n from the line before the
10275 // start of the row range and position the cursor there.
10276 edit_start = edit_start.saturating_sub(1);
10277 edit_end = buffer.len();
10278 cursor_buffer_row = rows.start.previous_row();
10279 }
10280
10281 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10282 *cursor.column_mut() =
10283 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10284
10285 new_cursors.push((
10286 selection.id,
10287 buffer.anchor_after(cursor.to_point(&display_map)),
10288 ));
10289 edit_ranges.push(edit_start..edit_end);
10290 }
10291
10292 self.transact(window, cx, |this, window, cx| {
10293 let buffer = this.buffer.update(cx, |buffer, cx| {
10294 let empty_str: Arc<str> = Arc::default();
10295 buffer.edit(
10296 edit_ranges
10297 .into_iter()
10298 .map(|range| (range, empty_str.clone())),
10299 None,
10300 cx,
10301 );
10302 buffer.snapshot(cx)
10303 });
10304 let new_selections = new_cursors
10305 .into_iter()
10306 .map(|(id, cursor)| {
10307 let cursor = cursor.to_point(&buffer);
10308 Selection {
10309 id,
10310 start: cursor,
10311 end: cursor,
10312 reversed: false,
10313 goal: SelectionGoal::None,
10314 }
10315 })
10316 .collect();
10317
10318 this.change_selections(Default::default(), window, cx, |s| {
10319 s.select(new_selections);
10320 });
10321 });
10322 }
10323
10324 pub fn join_lines_impl(
10325 &mut self,
10326 insert_whitespace: bool,
10327 window: &mut Window,
10328 cx: &mut Context<Self>,
10329 ) {
10330 if self.read_only(cx) {
10331 return;
10332 }
10333 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10334 for selection in self.selections.all::<Point>(cx) {
10335 let start = MultiBufferRow(selection.start.row);
10336 // Treat single line selections as if they include the next line. Otherwise this action
10337 // would do nothing for single line selections individual cursors.
10338 let end = if selection.start.row == selection.end.row {
10339 MultiBufferRow(selection.start.row + 1)
10340 } else {
10341 MultiBufferRow(selection.end.row)
10342 };
10343
10344 if let Some(last_row_range) = row_ranges.last_mut()
10345 && start <= last_row_range.end
10346 {
10347 last_row_range.end = end;
10348 continue;
10349 }
10350 row_ranges.push(start..end);
10351 }
10352
10353 let snapshot = self.buffer.read(cx).snapshot(cx);
10354 let mut cursor_positions = Vec::new();
10355 for row_range in &row_ranges {
10356 let anchor = snapshot.anchor_before(Point::new(
10357 row_range.end.previous_row().0,
10358 snapshot.line_len(row_range.end.previous_row()),
10359 ));
10360 cursor_positions.push(anchor..anchor);
10361 }
10362
10363 self.transact(window, cx, |this, window, cx| {
10364 for row_range in row_ranges.into_iter().rev() {
10365 for row in row_range.iter_rows().rev() {
10366 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10367 let next_line_row = row.next_row();
10368 let indent = snapshot.indent_size_for_line(next_line_row);
10369 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10370
10371 let replace =
10372 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10373 " "
10374 } else {
10375 ""
10376 };
10377
10378 this.buffer.update(cx, |buffer, cx| {
10379 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10380 });
10381 }
10382 }
10383
10384 this.change_selections(Default::default(), window, cx, |s| {
10385 s.select_anchor_ranges(cursor_positions)
10386 });
10387 });
10388 }
10389
10390 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10391 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10392 self.join_lines_impl(true, window, cx);
10393 }
10394
10395 pub fn sort_lines_case_sensitive(
10396 &mut self,
10397 _: &SortLinesCaseSensitive,
10398 window: &mut Window,
10399 cx: &mut Context<Self>,
10400 ) {
10401 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10402 }
10403
10404 pub fn sort_lines_by_length(
10405 &mut self,
10406 _: &SortLinesByLength,
10407 window: &mut Window,
10408 cx: &mut Context<Self>,
10409 ) {
10410 self.manipulate_immutable_lines(window, cx, |lines| {
10411 lines.sort_by_key(|&line| line.chars().count())
10412 })
10413 }
10414
10415 pub fn sort_lines_case_insensitive(
10416 &mut self,
10417 _: &SortLinesCaseInsensitive,
10418 window: &mut Window,
10419 cx: &mut Context<Self>,
10420 ) {
10421 self.manipulate_immutable_lines(window, cx, |lines| {
10422 lines.sort_by_key(|line| line.to_lowercase())
10423 })
10424 }
10425
10426 pub fn unique_lines_case_insensitive(
10427 &mut self,
10428 _: &UniqueLinesCaseInsensitive,
10429 window: &mut Window,
10430 cx: &mut Context<Self>,
10431 ) {
10432 self.manipulate_immutable_lines(window, cx, |lines| {
10433 let mut seen = HashSet::default();
10434 lines.retain(|line| seen.insert(line.to_lowercase()));
10435 })
10436 }
10437
10438 pub fn unique_lines_case_sensitive(
10439 &mut self,
10440 _: &UniqueLinesCaseSensitive,
10441 window: &mut Window,
10442 cx: &mut Context<Self>,
10443 ) {
10444 self.manipulate_immutable_lines(window, cx, |lines| {
10445 let mut seen = HashSet::default();
10446 lines.retain(|line| seen.insert(*line));
10447 })
10448 }
10449
10450 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10451 let Some(project) = self.project.clone() else {
10452 return;
10453 };
10454 self.reload(project, window, cx)
10455 .detach_and_notify_err(window, cx);
10456 }
10457
10458 pub fn restore_file(
10459 &mut self,
10460 _: &::git::RestoreFile,
10461 window: &mut Window,
10462 cx: &mut Context<Self>,
10463 ) {
10464 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10465 let mut buffer_ids = HashSet::default();
10466 let snapshot = self.buffer().read(cx).snapshot(cx);
10467 for selection in self.selections.all::<usize>(cx) {
10468 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10469 }
10470
10471 let buffer = self.buffer().read(cx);
10472 let ranges = buffer_ids
10473 .into_iter()
10474 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10475 .collect::<Vec<_>>();
10476
10477 self.restore_hunks_in_ranges(ranges, window, cx);
10478 }
10479
10480 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10481 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10482 let selections = self
10483 .selections
10484 .all(cx)
10485 .into_iter()
10486 .map(|s| s.range())
10487 .collect();
10488 self.restore_hunks_in_ranges(selections, window, cx);
10489 }
10490
10491 pub fn restore_hunks_in_ranges(
10492 &mut self,
10493 ranges: Vec<Range<Point>>,
10494 window: &mut Window,
10495 cx: &mut Context<Editor>,
10496 ) {
10497 let mut revert_changes = HashMap::default();
10498 let chunk_by = self
10499 .snapshot(window, cx)
10500 .hunks_for_ranges(ranges)
10501 .into_iter()
10502 .chunk_by(|hunk| hunk.buffer_id);
10503 for (buffer_id, hunks) in &chunk_by {
10504 let hunks = hunks.collect::<Vec<_>>();
10505 for hunk in &hunks {
10506 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10507 }
10508 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10509 }
10510 drop(chunk_by);
10511 if !revert_changes.is_empty() {
10512 self.transact(window, cx, |editor, window, cx| {
10513 editor.restore(revert_changes, window, cx);
10514 });
10515 }
10516 }
10517
10518 pub fn open_active_item_in_terminal(
10519 &mut self,
10520 _: &OpenInTerminal,
10521 window: &mut Window,
10522 cx: &mut Context<Self>,
10523 ) {
10524 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10525 let project_path = buffer.read(cx).project_path(cx)?;
10526 let project = self.project()?.read(cx);
10527 let entry = project.entry_for_path(&project_path, cx)?;
10528 let parent = match &entry.canonical_path {
10529 Some(canonical_path) => canonical_path.to_path_buf(),
10530 None => project.absolute_path(&project_path, cx)?,
10531 }
10532 .parent()?
10533 .to_path_buf();
10534 Some(parent)
10535 }) {
10536 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10537 }
10538 }
10539
10540 fn set_breakpoint_context_menu(
10541 &mut self,
10542 display_row: DisplayRow,
10543 position: Option<Anchor>,
10544 clicked_point: gpui::Point<Pixels>,
10545 window: &mut Window,
10546 cx: &mut Context<Self>,
10547 ) {
10548 let source = self
10549 .buffer
10550 .read(cx)
10551 .snapshot(cx)
10552 .anchor_before(Point::new(display_row.0, 0u32));
10553
10554 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10555
10556 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10557 self,
10558 source,
10559 clicked_point,
10560 context_menu,
10561 window,
10562 cx,
10563 );
10564 }
10565
10566 fn add_edit_breakpoint_block(
10567 &mut self,
10568 anchor: Anchor,
10569 breakpoint: &Breakpoint,
10570 edit_action: BreakpointPromptEditAction,
10571 window: &mut Window,
10572 cx: &mut Context<Self>,
10573 ) {
10574 let weak_editor = cx.weak_entity();
10575 let bp_prompt = cx.new(|cx| {
10576 BreakpointPromptEditor::new(
10577 weak_editor,
10578 anchor,
10579 breakpoint.clone(),
10580 edit_action,
10581 window,
10582 cx,
10583 )
10584 });
10585
10586 let height = bp_prompt.update(cx, |this, cx| {
10587 this.prompt
10588 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10589 });
10590 let cloned_prompt = bp_prompt.clone();
10591 let blocks = vec![BlockProperties {
10592 style: BlockStyle::Sticky,
10593 placement: BlockPlacement::Above(anchor),
10594 height: Some(height),
10595 render: Arc::new(move |cx| {
10596 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10597 cloned_prompt.clone().into_any_element()
10598 }),
10599 priority: 0,
10600 }];
10601
10602 let focus_handle = bp_prompt.focus_handle(cx);
10603 window.focus(&focus_handle);
10604
10605 let block_ids = self.insert_blocks(blocks, None, cx);
10606 bp_prompt.update(cx, |prompt, _| {
10607 prompt.add_block_ids(block_ids);
10608 });
10609 }
10610
10611 pub(crate) fn breakpoint_at_row(
10612 &self,
10613 row: u32,
10614 window: &mut Window,
10615 cx: &mut Context<Self>,
10616 ) -> Option<(Anchor, Breakpoint)> {
10617 let snapshot = self.snapshot(window, cx);
10618 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10619
10620 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10621 }
10622
10623 pub(crate) fn breakpoint_at_anchor(
10624 &self,
10625 breakpoint_position: Anchor,
10626 snapshot: &EditorSnapshot,
10627 cx: &mut Context<Self>,
10628 ) -> Option<(Anchor, Breakpoint)> {
10629 let project = self.project.clone()?;
10630
10631 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10632 snapshot
10633 .buffer_snapshot
10634 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10635 })?;
10636
10637 let enclosing_excerpt = breakpoint_position.excerpt_id;
10638 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10639 let buffer_snapshot = buffer.read(cx).snapshot();
10640
10641 let row = buffer_snapshot
10642 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10643 .row;
10644
10645 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10646 let anchor_end = snapshot
10647 .buffer_snapshot
10648 .anchor_after(Point::new(row, line_len));
10649
10650 let bp = self
10651 .breakpoint_store
10652 .as_ref()?
10653 .read_with(cx, |breakpoint_store, cx| {
10654 breakpoint_store
10655 .breakpoints(
10656 &buffer,
10657 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10658 &buffer_snapshot,
10659 cx,
10660 )
10661 .next()
10662 .and_then(|(bp, _)| {
10663 let breakpoint_row = buffer_snapshot
10664 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10665 .row;
10666
10667 if breakpoint_row == row {
10668 snapshot
10669 .buffer_snapshot
10670 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10671 .map(|position| (position, bp.bp.clone()))
10672 } else {
10673 None
10674 }
10675 })
10676 });
10677 bp
10678 }
10679
10680 pub fn edit_log_breakpoint(
10681 &mut self,
10682 _: &EditLogBreakpoint,
10683 window: &mut Window,
10684 cx: &mut Context<Self>,
10685 ) {
10686 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10687 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10688 message: None,
10689 state: BreakpointState::Enabled,
10690 condition: None,
10691 hit_condition: None,
10692 });
10693
10694 self.add_edit_breakpoint_block(
10695 anchor,
10696 &breakpoint,
10697 BreakpointPromptEditAction::Log,
10698 window,
10699 cx,
10700 );
10701 }
10702 }
10703
10704 fn breakpoints_at_cursors(
10705 &self,
10706 window: &mut Window,
10707 cx: &mut Context<Self>,
10708 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10709 let snapshot = self.snapshot(window, cx);
10710 let cursors = self
10711 .selections
10712 .disjoint_anchors()
10713 .into_iter()
10714 .map(|selection| {
10715 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10716
10717 let breakpoint_position = self
10718 .breakpoint_at_row(cursor_position.row, window, cx)
10719 .map(|bp| bp.0)
10720 .unwrap_or_else(|| {
10721 snapshot
10722 .display_snapshot
10723 .buffer_snapshot
10724 .anchor_after(Point::new(cursor_position.row, 0))
10725 });
10726
10727 let breakpoint = self
10728 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10729 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10730
10731 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10732 })
10733 // 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.
10734 .collect::<HashMap<Anchor, _>>();
10735
10736 cursors.into_iter().collect()
10737 }
10738
10739 pub fn enable_breakpoint(
10740 &mut self,
10741 _: &crate::actions::EnableBreakpoint,
10742 window: &mut Window,
10743 cx: &mut Context<Self>,
10744 ) {
10745 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10746 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10747 continue;
10748 };
10749 self.edit_breakpoint_at_anchor(
10750 anchor,
10751 breakpoint,
10752 BreakpointEditAction::InvertState,
10753 cx,
10754 );
10755 }
10756 }
10757
10758 pub fn disable_breakpoint(
10759 &mut self,
10760 _: &crate::actions::DisableBreakpoint,
10761 window: &mut Window,
10762 cx: &mut Context<Self>,
10763 ) {
10764 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10765 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10766 continue;
10767 };
10768 self.edit_breakpoint_at_anchor(
10769 anchor,
10770 breakpoint,
10771 BreakpointEditAction::InvertState,
10772 cx,
10773 );
10774 }
10775 }
10776
10777 pub fn toggle_breakpoint(
10778 &mut self,
10779 _: &crate::actions::ToggleBreakpoint,
10780 window: &mut Window,
10781 cx: &mut Context<Self>,
10782 ) {
10783 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10784 if let Some(breakpoint) = breakpoint {
10785 self.edit_breakpoint_at_anchor(
10786 anchor,
10787 breakpoint,
10788 BreakpointEditAction::Toggle,
10789 cx,
10790 );
10791 } else {
10792 self.edit_breakpoint_at_anchor(
10793 anchor,
10794 Breakpoint::new_standard(),
10795 BreakpointEditAction::Toggle,
10796 cx,
10797 );
10798 }
10799 }
10800 }
10801
10802 pub fn edit_breakpoint_at_anchor(
10803 &mut self,
10804 breakpoint_position: Anchor,
10805 breakpoint: Breakpoint,
10806 edit_action: BreakpointEditAction,
10807 cx: &mut Context<Self>,
10808 ) {
10809 let Some(breakpoint_store) = &self.breakpoint_store else {
10810 return;
10811 };
10812
10813 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10814 if breakpoint_position == Anchor::min() {
10815 self.buffer()
10816 .read(cx)
10817 .excerpt_buffer_ids()
10818 .into_iter()
10819 .next()
10820 } else {
10821 None
10822 }
10823 }) else {
10824 return;
10825 };
10826
10827 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10828 return;
10829 };
10830
10831 breakpoint_store.update(cx, |breakpoint_store, cx| {
10832 breakpoint_store.toggle_breakpoint(
10833 buffer,
10834 BreakpointWithPosition {
10835 position: breakpoint_position.text_anchor,
10836 bp: breakpoint,
10837 },
10838 edit_action,
10839 cx,
10840 );
10841 });
10842
10843 cx.notify();
10844 }
10845
10846 #[cfg(any(test, feature = "test-support"))]
10847 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10848 self.breakpoint_store.clone()
10849 }
10850
10851 pub fn prepare_restore_change(
10852 &self,
10853 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10854 hunk: &MultiBufferDiffHunk,
10855 cx: &mut App,
10856 ) -> Option<()> {
10857 if hunk.is_created_file() {
10858 return None;
10859 }
10860 let buffer = self.buffer.read(cx);
10861 let diff = buffer.diff_for(hunk.buffer_id)?;
10862 let buffer = buffer.buffer(hunk.buffer_id)?;
10863 let buffer = buffer.read(cx);
10864 let original_text = diff
10865 .read(cx)
10866 .base_text()
10867 .as_rope()
10868 .slice(hunk.diff_base_byte_range.clone());
10869 let buffer_snapshot = buffer.snapshot();
10870 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10871 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10872 probe
10873 .0
10874 .start
10875 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10876 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10877 }) {
10878 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10879 Some(())
10880 } else {
10881 None
10882 }
10883 }
10884
10885 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10886 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10887 }
10888
10889 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10890 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10891 }
10892
10893 fn manipulate_lines<M>(
10894 &mut self,
10895 window: &mut Window,
10896 cx: &mut Context<Self>,
10897 mut manipulate: M,
10898 ) where
10899 M: FnMut(&str) -> LineManipulationResult,
10900 {
10901 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10902
10903 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10904 let buffer = self.buffer.read(cx).snapshot(cx);
10905
10906 let mut edits = Vec::new();
10907
10908 let selections = self.selections.all::<Point>(cx);
10909 let mut selections = selections.iter().peekable();
10910 let mut contiguous_row_selections = Vec::new();
10911 let mut new_selections = Vec::new();
10912 let mut added_lines = 0;
10913 let mut removed_lines = 0;
10914
10915 while let Some(selection) = selections.next() {
10916 let (start_row, end_row) = consume_contiguous_rows(
10917 &mut contiguous_row_selections,
10918 selection,
10919 &display_map,
10920 &mut selections,
10921 );
10922
10923 let start_point = Point::new(start_row.0, 0);
10924 let end_point = Point::new(
10925 end_row.previous_row().0,
10926 buffer.line_len(end_row.previous_row()),
10927 );
10928 let text = buffer
10929 .text_for_range(start_point..end_point)
10930 .collect::<String>();
10931
10932 let LineManipulationResult {
10933 new_text,
10934 line_count_before,
10935 line_count_after,
10936 } = manipulate(&text);
10937
10938 edits.push((start_point..end_point, new_text));
10939
10940 // Selections must change based on added and removed line count
10941 let start_row =
10942 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10943 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10944 new_selections.push(Selection {
10945 id: selection.id,
10946 start: start_row,
10947 end: end_row,
10948 goal: SelectionGoal::None,
10949 reversed: selection.reversed,
10950 });
10951
10952 if line_count_after > line_count_before {
10953 added_lines += line_count_after - line_count_before;
10954 } else if line_count_before > line_count_after {
10955 removed_lines += line_count_before - line_count_after;
10956 }
10957 }
10958
10959 self.transact(window, cx, |this, window, cx| {
10960 let buffer = this.buffer.update(cx, |buffer, cx| {
10961 buffer.edit(edits, None, cx);
10962 buffer.snapshot(cx)
10963 });
10964
10965 // Recalculate offsets on newly edited buffer
10966 let new_selections = new_selections
10967 .iter()
10968 .map(|s| {
10969 let start_point = Point::new(s.start.0, 0);
10970 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10971 Selection {
10972 id: s.id,
10973 start: buffer.point_to_offset(start_point),
10974 end: buffer.point_to_offset(end_point),
10975 goal: s.goal,
10976 reversed: s.reversed,
10977 }
10978 })
10979 .collect();
10980
10981 this.change_selections(Default::default(), window, cx, |s| {
10982 s.select(new_selections);
10983 });
10984
10985 this.request_autoscroll(Autoscroll::fit(), cx);
10986 });
10987 }
10988
10989 fn manipulate_immutable_lines<Fn>(
10990 &mut self,
10991 window: &mut Window,
10992 cx: &mut Context<Self>,
10993 mut callback: Fn,
10994 ) where
10995 Fn: FnMut(&mut Vec<&str>),
10996 {
10997 self.manipulate_lines(window, cx, |text| {
10998 let mut lines: Vec<&str> = text.split('\n').collect();
10999 let line_count_before = lines.len();
11000
11001 callback(&mut lines);
11002
11003 LineManipulationResult {
11004 new_text: lines.join("\n"),
11005 line_count_before,
11006 line_count_after: lines.len(),
11007 }
11008 });
11009 }
11010
11011 fn manipulate_mutable_lines<Fn>(
11012 &mut self,
11013 window: &mut Window,
11014 cx: &mut Context<Self>,
11015 mut callback: Fn,
11016 ) where
11017 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11018 {
11019 self.manipulate_lines(window, cx, |text| {
11020 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11021 let line_count_before = lines.len();
11022
11023 callback(&mut lines);
11024
11025 LineManipulationResult {
11026 new_text: lines.join("\n"),
11027 line_count_before,
11028 line_count_after: lines.len(),
11029 }
11030 });
11031 }
11032
11033 pub fn convert_indentation_to_spaces(
11034 &mut self,
11035 _: &ConvertIndentationToSpaces,
11036 window: &mut Window,
11037 cx: &mut Context<Self>,
11038 ) {
11039 let settings = self.buffer.read(cx).language_settings(cx);
11040 let tab_size = settings.tab_size.get() as usize;
11041
11042 self.manipulate_mutable_lines(window, cx, |lines| {
11043 // Allocates a reasonably sized scratch buffer once for the whole loop
11044 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11045 // Avoids recomputing spaces that could be inserted many times
11046 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11047 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11048 .collect();
11049
11050 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11051 let mut chars = line.as_ref().chars();
11052 let mut col = 0;
11053 let mut changed = false;
11054
11055 while let Some(ch) = chars.next() {
11056 match ch {
11057 ' ' => {
11058 reindented_line.push(' ');
11059 col += 1;
11060 }
11061 '\t' => {
11062 // \t are converted to spaces depending on the current column
11063 let spaces_len = tab_size - (col % tab_size);
11064 reindented_line.extend(&space_cache[spaces_len - 1]);
11065 col += spaces_len;
11066 changed = true;
11067 }
11068 _ => {
11069 // If we dont append before break, the character is consumed
11070 reindented_line.push(ch);
11071 break;
11072 }
11073 }
11074 }
11075
11076 if !changed {
11077 reindented_line.clear();
11078 continue;
11079 }
11080 // Append the rest of the line and replace old reference with new one
11081 reindented_line.extend(chars);
11082 *line = Cow::Owned(reindented_line.clone());
11083 reindented_line.clear();
11084 }
11085 });
11086 }
11087
11088 pub fn convert_indentation_to_tabs(
11089 &mut self,
11090 _: &ConvertIndentationToTabs,
11091 window: &mut Window,
11092 cx: &mut Context<Self>,
11093 ) {
11094 let settings = self.buffer.read(cx).language_settings(cx);
11095 let tab_size = settings.tab_size.get() as usize;
11096
11097 self.manipulate_mutable_lines(window, cx, |lines| {
11098 // Allocates a reasonably sized buffer once for the whole loop
11099 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11100 // Avoids recomputing spaces that could be inserted many times
11101 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11102 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11103 .collect();
11104
11105 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11106 let mut chars = line.chars();
11107 let mut spaces_count = 0;
11108 let mut first_non_indent_char = None;
11109 let mut changed = false;
11110
11111 while let Some(ch) = chars.next() {
11112 match ch {
11113 ' ' => {
11114 // Keep track of spaces. Append \t when we reach tab_size
11115 spaces_count += 1;
11116 changed = true;
11117 if spaces_count == tab_size {
11118 reindented_line.push('\t');
11119 spaces_count = 0;
11120 }
11121 }
11122 '\t' => {
11123 reindented_line.push('\t');
11124 spaces_count = 0;
11125 }
11126 _ => {
11127 // Dont append it yet, we might have remaining spaces
11128 first_non_indent_char = Some(ch);
11129 break;
11130 }
11131 }
11132 }
11133
11134 if !changed {
11135 reindented_line.clear();
11136 continue;
11137 }
11138 // Remaining spaces that didn't make a full tab stop
11139 if spaces_count > 0 {
11140 reindented_line.extend(&space_cache[spaces_count - 1]);
11141 }
11142 // If we consume an extra character that was not indentation, add it back
11143 if let Some(extra_char) = first_non_indent_char {
11144 reindented_line.push(extra_char);
11145 }
11146 // Append the rest of the line and replace old reference with new one
11147 reindented_line.extend(chars);
11148 *line = Cow::Owned(reindented_line.clone());
11149 reindented_line.clear();
11150 }
11151 });
11152 }
11153
11154 pub fn convert_to_upper_case(
11155 &mut self,
11156 _: &ConvertToUpperCase,
11157 window: &mut Window,
11158 cx: &mut Context<Self>,
11159 ) {
11160 self.manipulate_text(window, cx, |text| text.to_uppercase())
11161 }
11162
11163 pub fn convert_to_lower_case(
11164 &mut self,
11165 _: &ConvertToLowerCase,
11166 window: &mut Window,
11167 cx: &mut Context<Self>,
11168 ) {
11169 self.manipulate_text(window, cx, |text| text.to_lowercase())
11170 }
11171
11172 pub fn convert_to_title_case(
11173 &mut self,
11174 _: &ConvertToTitleCase,
11175 window: &mut Window,
11176 cx: &mut Context<Self>,
11177 ) {
11178 self.manipulate_text(window, cx, |text| {
11179 text.split('\n')
11180 .map(|line| line.to_case(Case::Title))
11181 .join("\n")
11182 })
11183 }
11184
11185 pub fn convert_to_snake_case(
11186 &mut self,
11187 _: &ConvertToSnakeCase,
11188 window: &mut Window,
11189 cx: &mut Context<Self>,
11190 ) {
11191 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11192 }
11193
11194 pub fn convert_to_kebab_case(
11195 &mut self,
11196 _: &ConvertToKebabCase,
11197 window: &mut Window,
11198 cx: &mut Context<Self>,
11199 ) {
11200 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11201 }
11202
11203 pub fn convert_to_upper_camel_case(
11204 &mut self,
11205 _: &ConvertToUpperCamelCase,
11206 window: &mut Window,
11207 cx: &mut Context<Self>,
11208 ) {
11209 self.manipulate_text(window, cx, |text| {
11210 text.split('\n')
11211 .map(|line| line.to_case(Case::UpperCamel))
11212 .join("\n")
11213 })
11214 }
11215
11216 pub fn convert_to_lower_camel_case(
11217 &mut self,
11218 _: &ConvertToLowerCamelCase,
11219 window: &mut Window,
11220 cx: &mut Context<Self>,
11221 ) {
11222 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11223 }
11224
11225 pub fn convert_to_opposite_case(
11226 &mut self,
11227 _: &ConvertToOppositeCase,
11228 window: &mut Window,
11229 cx: &mut Context<Self>,
11230 ) {
11231 self.manipulate_text(window, cx, |text| {
11232 text.chars()
11233 .fold(String::with_capacity(text.len()), |mut t, c| {
11234 if c.is_uppercase() {
11235 t.extend(c.to_lowercase());
11236 } else {
11237 t.extend(c.to_uppercase());
11238 }
11239 t
11240 })
11241 })
11242 }
11243
11244 pub fn convert_to_sentence_case(
11245 &mut self,
11246 _: &ConvertToSentenceCase,
11247 window: &mut Window,
11248 cx: &mut Context<Self>,
11249 ) {
11250 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11251 }
11252
11253 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11254 self.manipulate_text(window, cx, |text| {
11255 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11256 if has_upper_case_characters {
11257 text.to_lowercase()
11258 } else {
11259 text.to_uppercase()
11260 }
11261 })
11262 }
11263
11264 pub fn convert_to_rot13(
11265 &mut self,
11266 _: &ConvertToRot13,
11267 window: &mut Window,
11268 cx: &mut Context<Self>,
11269 ) {
11270 self.manipulate_text(window, cx, |text| {
11271 text.chars()
11272 .map(|c| match c {
11273 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11274 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11275 _ => c,
11276 })
11277 .collect()
11278 })
11279 }
11280
11281 pub fn convert_to_rot47(
11282 &mut self,
11283 _: &ConvertToRot47,
11284 window: &mut Window,
11285 cx: &mut Context<Self>,
11286 ) {
11287 self.manipulate_text(window, cx, |text| {
11288 text.chars()
11289 .map(|c| {
11290 let code_point = c as u32;
11291 if code_point >= 33 && code_point <= 126 {
11292 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11293 }
11294 c
11295 })
11296 .collect()
11297 })
11298 }
11299
11300 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11301 where
11302 Fn: FnMut(&str) -> String,
11303 {
11304 let buffer = self.buffer.read(cx).snapshot(cx);
11305
11306 let mut new_selections = Vec::new();
11307 let mut edits = Vec::new();
11308 let mut selection_adjustment = 0i32;
11309
11310 for selection in self.selections.all::<usize>(cx) {
11311 let selection_is_empty = selection.is_empty();
11312
11313 let (start, end) = if selection_is_empty {
11314 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11315 (word_range.start, word_range.end)
11316 } else {
11317 (selection.start, selection.end)
11318 };
11319
11320 let text = buffer.text_for_range(start..end).collect::<String>();
11321 let old_length = text.len() as i32;
11322 let text = callback(&text);
11323
11324 new_selections.push(Selection {
11325 start: (start as i32 - selection_adjustment) as usize,
11326 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11327 goal: SelectionGoal::None,
11328 ..selection
11329 });
11330
11331 selection_adjustment += old_length - text.len() as i32;
11332
11333 edits.push((start..end, text));
11334 }
11335
11336 self.transact(window, cx, |this, window, cx| {
11337 this.buffer.update(cx, |buffer, cx| {
11338 buffer.edit(edits, None, cx);
11339 });
11340
11341 this.change_selections(Default::default(), window, cx, |s| {
11342 s.select(new_selections);
11343 });
11344
11345 this.request_autoscroll(Autoscroll::fit(), cx);
11346 });
11347 }
11348
11349 pub fn move_selection_on_drop(
11350 &mut self,
11351 selection: &Selection<Anchor>,
11352 target: DisplayPoint,
11353 is_cut: bool,
11354 window: &mut Window,
11355 cx: &mut Context<Self>,
11356 ) {
11357 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11358 let buffer = &display_map.buffer_snapshot;
11359 let mut edits = Vec::new();
11360 let insert_point = display_map
11361 .clip_point(target, Bias::Left)
11362 .to_point(&display_map);
11363 let text = buffer
11364 .text_for_range(selection.start..selection.end)
11365 .collect::<String>();
11366 if is_cut {
11367 edits.push(((selection.start..selection.end), String::new()));
11368 }
11369 let insert_anchor = buffer.anchor_before(insert_point);
11370 edits.push(((insert_anchor..insert_anchor), text));
11371 let last_edit_start = insert_anchor.bias_left(buffer);
11372 let last_edit_end = insert_anchor.bias_right(buffer);
11373 self.transact(window, cx, |this, window, cx| {
11374 this.buffer.update(cx, |buffer, cx| {
11375 buffer.edit(edits, None, cx);
11376 });
11377 this.change_selections(Default::default(), window, cx, |s| {
11378 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11379 });
11380 });
11381 }
11382
11383 pub fn clear_selection_drag_state(&mut self) {
11384 self.selection_drag_state = SelectionDragState::None;
11385 }
11386
11387 pub fn duplicate(
11388 &mut self,
11389 upwards: bool,
11390 whole_lines: bool,
11391 window: &mut Window,
11392 cx: &mut Context<Self>,
11393 ) {
11394 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11395
11396 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11397 let buffer = &display_map.buffer_snapshot;
11398 let selections = self.selections.all::<Point>(cx);
11399
11400 let mut edits = Vec::new();
11401 let mut selections_iter = selections.iter().peekable();
11402 while let Some(selection) = selections_iter.next() {
11403 let mut rows = selection.spanned_rows(false, &display_map);
11404 // duplicate line-wise
11405 if whole_lines || selection.start == selection.end {
11406 // Avoid duplicating the same lines twice.
11407 while let Some(next_selection) = selections_iter.peek() {
11408 let next_rows = next_selection.spanned_rows(false, &display_map);
11409 if next_rows.start < rows.end {
11410 rows.end = next_rows.end;
11411 selections_iter.next().unwrap();
11412 } else {
11413 break;
11414 }
11415 }
11416
11417 // Copy the text from the selected row region and splice it either at the start
11418 // or end of the region.
11419 let start = Point::new(rows.start.0, 0);
11420 let end = Point::new(
11421 rows.end.previous_row().0,
11422 buffer.line_len(rows.end.previous_row()),
11423 );
11424 let text = buffer
11425 .text_for_range(start..end)
11426 .chain(Some("\n"))
11427 .collect::<String>();
11428 let insert_location = if upwards {
11429 Point::new(rows.end.0, 0)
11430 } else {
11431 start
11432 };
11433 edits.push((insert_location..insert_location, text));
11434 } else {
11435 // duplicate character-wise
11436 let start = selection.start;
11437 let end = selection.end;
11438 let text = buffer.text_for_range(start..end).collect::<String>();
11439 edits.push((selection.end..selection.end, text));
11440 }
11441 }
11442
11443 self.transact(window, cx, |this, _, cx| {
11444 this.buffer.update(cx, |buffer, cx| {
11445 buffer.edit(edits, None, cx);
11446 });
11447
11448 this.request_autoscroll(Autoscroll::fit(), cx);
11449 });
11450 }
11451
11452 pub fn duplicate_line_up(
11453 &mut self,
11454 _: &DuplicateLineUp,
11455 window: &mut Window,
11456 cx: &mut Context<Self>,
11457 ) {
11458 self.duplicate(true, true, window, cx);
11459 }
11460
11461 pub fn duplicate_line_down(
11462 &mut self,
11463 _: &DuplicateLineDown,
11464 window: &mut Window,
11465 cx: &mut Context<Self>,
11466 ) {
11467 self.duplicate(false, true, window, cx);
11468 }
11469
11470 pub fn duplicate_selection(
11471 &mut self,
11472 _: &DuplicateSelection,
11473 window: &mut Window,
11474 cx: &mut Context<Self>,
11475 ) {
11476 self.duplicate(false, false, window, cx);
11477 }
11478
11479 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11480 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11481 if self.mode.is_single_line() {
11482 cx.propagate();
11483 return;
11484 }
11485
11486 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11487 let buffer = self.buffer.read(cx).snapshot(cx);
11488
11489 let mut edits = Vec::new();
11490 let mut unfold_ranges = Vec::new();
11491 let mut refold_creases = Vec::new();
11492
11493 let selections = self.selections.all::<Point>(cx);
11494 let mut selections = selections.iter().peekable();
11495 let mut contiguous_row_selections = Vec::new();
11496 let mut new_selections = Vec::new();
11497
11498 while let Some(selection) = selections.next() {
11499 // Find all the selections that span a contiguous row range
11500 let (start_row, end_row) = consume_contiguous_rows(
11501 &mut contiguous_row_selections,
11502 selection,
11503 &display_map,
11504 &mut selections,
11505 );
11506
11507 // Move the text spanned by the row range to be before the line preceding the row range
11508 if start_row.0 > 0 {
11509 let range_to_move = Point::new(
11510 start_row.previous_row().0,
11511 buffer.line_len(start_row.previous_row()),
11512 )
11513 ..Point::new(
11514 end_row.previous_row().0,
11515 buffer.line_len(end_row.previous_row()),
11516 );
11517 let insertion_point = display_map
11518 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11519 .0;
11520
11521 // Don't move lines across excerpts
11522 if buffer
11523 .excerpt_containing(insertion_point..range_to_move.end)
11524 .is_some()
11525 {
11526 let text = buffer
11527 .text_for_range(range_to_move.clone())
11528 .flat_map(|s| s.chars())
11529 .skip(1)
11530 .chain(['\n'])
11531 .collect::<String>();
11532
11533 edits.push((
11534 buffer.anchor_after(range_to_move.start)
11535 ..buffer.anchor_before(range_to_move.end),
11536 String::new(),
11537 ));
11538 let insertion_anchor = buffer.anchor_after(insertion_point);
11539 edits.push((insertion_anchor..insertion_anchor, text));
11540
11541 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11542
11543 // Move selections up
11544 new_selections.extend(contiguous_row_selections.drain(..).map(
11545 |mut selection| {
11546 selection.start.row -= row_delta;
11547 selection.end.row -= row_delta;
11548 selection
11549 },
11550 ));
11551
11552 // Move folds up
11553 unfold_ranges.push(range_to_move.clone());
11554 for fold in display_map.folds_in_range(
11555 buffer.anchor_before(range_to_move.start)
11556 ..buffer.anchor_after(range_to_move.end),
11557 ) {
11558 let mut start = fold.range.start.to_point(&buffer);
11559 let mut end = fold.range.end.to_point(&buffer);
11560 start.row -= row_delta;
11561 end.row -= row_delta;
11562 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11563 }
11564 }
11565 }
11566
11567 // If we didn't move line(s), preserve the existing selections
11568 new_selections.append(&mut contiguous_row_selections);
11569 }
11570
11571 self.transact(window, cx, |this, window, cx| {
11572 this.unfold_ranges(&unfold_ranges, true, true, cx);
11573 this.buffer.update(cx, |buffer, cx| {
11574 for (range, text) in edits {
11575 buffer.edit([(range, text)], None, cx);
11576 }
11577 });
11578 this.fold_creases(refold_creases, true, window, cx);
11579 this.change_selections(Default::default(), window, cx, |s| {
11580 s.select(new_selections);
11581 })
11582 });
11583 }
11584
11585 pub fn move_line_down(
11586 &mut self,
11587 _: &MoveLineDown,
11588 window: &mut Window,
11589 cx: &mut Context<Self>,
11590 ) {
11591 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11592 if self.mode.is_single_line() {
11593 cx.propagate();
11594 return;
11595 }
11596
11597 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11598 let buffer = self.buffer.read(cx).snapshot(cx);
11599
11600 let mut edits = Vec::new();
11601 let mut unfold_ranges = Vec::new();
11602 let mut refold_creases = Vec::new();
11603
11604 let selections = self.selections.all::<Point>(cx);
11605 let mut selections = selections.iter().peekable();
11606 let mut contiguous_row_selections = Vec::new();
11607 let mut new_selections = Vec::new();
11608
11609 while let Some(selection) = selections.next() {
11610 // Find all the selections that span a contiguous row range
11611 let (start_row, end_row) = consume_contiguous_rows(
11612 &mut contiguous_row_selections,
11613 selection,
11614 &display_map,
11615 &mut selections,
11616 );
11617
11618 // Move the text spanned by the row range to be after the last line of the row range
11619 if end_row.0 <= buffer.max_point().row {
11620 let range_to_move =
11621 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11622 let insertion_point = display_map
11623 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11624 .0;
11625
11626 // Don't move lines across excerpt boundaries
11627 if buffer
11628 .excerpt_containing(range_to_move.start..insertion_point)
11629 .is_some()
11630 {
11631 let mut text = String::from("\n");
11632 text.extend(buffer.text_for_range(range_to_move.clone()));
11633 text.pop(); // Drop trailing newline
11634 edits.push((
11635 buffer.anchor_after(range_to_move.start)
11636 ..buffer.anchor_before(range_to_move.end),
11637 String::new(),
11638 ));
11639 let insertion_anchor = buffer.anchor_after(insertion_point);
11640 edits.push((insertion_anchor..insertion_anchor, text));
11641
11642 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11643
11644 // Move selections down
11645 new_selections.extend(contiguous_row_selections.drain(..).map(
11646 |mut selection| {
11647 selection.start.row += row_delta;
11648 selection.end.row += row_delta;
11649 selection
11650 },
11651 ));
11652
11653 // Move folds down
11654 unfold_ranges.push(range_to_move.clone());
11655 for fold in display_map.folds_in_range(
11656 buffer.anchor_before(range_to_move.start)
11657 ..buffer.anchor_after(range_to_move.end),
11658 ) {
11659 let mut start = fold.range.start.to_point(&buffer);
11660 let mut end = fold.range.end.to_point(&buffer);
11661 start.row += row_delta;
11662 end.row += row_delta;
11663 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11664 }
11665 }
11666 }
11667
11668 // If we didn't move line(s), preserve the existing selections
11669 new_selections.append(&mut contiguous_row_selections);
11670 }
11671
11672 self.transact(window, cx, |this, window, cx| {
11673 this.unfold_ranges(&unfold_ranges, true, true, cx);
11674 this.buffer.update(cx, |buffer, cx| {
11675 for (range, text) in edits {
11676 buffer.edit([(range, text)], None, cx);
11677 }
11678 });
11679 this.fold_creases(refold_creases, true, window, cx);
11680 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11681 });
11682 }
11683
11684 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11685 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11686 let text_layout_details = &self.text_layout_details(window);
11687 self.transact(window, cx, |this, window, cx| {
11688 let edits = this.change_selections(Default::default(), window, cx, |s| {
11689 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11690 s.move_with(|display_map, selection| {
11691 if !selection.is_empty() {
11692 return;
11693 }
11694
11695 let mut head = selection.head();
11696 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11697 if head.column() == display_map.line_len(head.row()) {
11698 transpose_offset = display_map
11699 .buffer_snapshot
11700 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11701 }
11702
11703 if transpose_offset == 0 {
11704 return;
11705 }
11706
11707 *head.column_mut() += 1;
11708 head = display_map.clip_point(head, Bias::Right);
11709 let goal = SelectionGoal::HorizontalPosition(
11710 display_map
11711 .x_for_display_point(head, text_layout_details)
11712 .into(),
11713 );
11714 selection.collapse_to(head, goal);
11715
11716 let transpose_start = display_map
11717 .buffer_snapshot
11718 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11719 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11720 let transpose_end = display_map
11721 .buffer_snapshot
11722 .clip_offset(transpose_offset + 1, Bias::Right);
11723 if let Some(ch) =
11724 display_map.buffer_snapshot.chars_at(transpose_start).next()
11725 {
11726 edits.push((transpose_start..transpose_offset, String::new()));
11727 edits.push((transpose_end..transpose_end, ch.to_string()));
11728 }
11729 }
11730 });
11731 edits
11732 });
11733 this.buffer
11734 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11735 let selections = this.selections.all::<usize>(cx);
11736 this.change_selections(Default::default(), window, cx, |s| {
11737 s.select(selections);
11738 });
11739 });
11740 }
11741
11742 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11743 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11744 if self.mode.is_single_line() {
11745 cx.propagate();
11746 return;
11747 }
11748
11749 self.rewrap_impl(RewrapOptions::default(), cx)
11750 }
11751
11752 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11753 let buffer = self.buffer.read(cx).snapshot(cx);
11754 let selections = self.selections.all::<Point>(cx);
11755
11756 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11757 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11758 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11759 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11760 .peekable();
11761
11762 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11763 row
11764 } else {
11765 return Vec::new();
11766 };
11767
11768 let language_settings = buffer.language_settings_at(selection.head(), cx);
11769 let language_scope = buffer.language_scope_at(selection.head());
11770
11771 let indent_and_prefix_for_row =
11772 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11773 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11774 let (comment_prefix, rewrap_prefix) =
11775 if let Some(language_scope) = &language_scope {
11776 let indent_end = Point::new(row, indent.len);
11777 let comment_prefix = language_scope
11778 .line_comment_prefixes()
11779 .iter()
11780 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11781 .map(|prefix| prefix.to_string());
11782 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11783 let line_text_after_indent = buffer
11784 .text_for_range(indent_end..line_end)
11785 .collect::<String>();
11786 let rewrap_prefix = language_scope
11787 .rewrap_prefixes()
11788 .iter()
11789 .find_map(|prefix_regex| {
11790 prefix_regex.find(&line_text_after_indent).map(|mat| {
11791 if mat.start() == 0 {
11792 Some(mat.as_str().to_string())
11793 } else {
11794 None
11795 }
11796 })
11797 })
11798 .flatten();
11799 (comment_prefix, rewrap_prefix)
11800 } else {
11801 (None, None)
11802 };
11803 (indent, comment_prefix, rewrap_prefix)
11804 };
11805
11806 let mut ranges = Vec::new();
11807 let from_empty_selection = selection.is_empty();
11808
11809 let mut current_range_start = first_row;
11810 let mut prev_row = first_row;
11811 let (
11812 mut current_range_indent,
11813 mut current_range_comment_prefix,
11814 mut current_range_rewrap_prefix,
11815 ) = indent_and_prefix_for_row(first_row);
11816
11817 for row in non_blank_rows_iter.skip(1) {
11818 let has_paragraph_break = row > prev_row + 1;
11819
11820 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11821 indent_and_prefix_for_row(row);
11822
11823 let has_indent_change = row_indent != current_range_indent;
11824 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11825
11826 let has_boundary_change = has_comment_change
11827 || row_rewrap_prefix.is_some()
11828 || (has_indent_change && current_range_comment_prefix.is_some());
11829
11830 if has_paragraph_break || has_boundary_change {
11831 ranges.push((
11832 language_settings.clone(),
11833 Point::new(current_range_start, 0)
11834 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11835 current_range_indent,
11836 current_range_comment_prefix.clone(),
11837 current_range_rewrap_prefix.clone(),
11838 from_empty_selection,
11839 ));
11840 current_range_start = row;
11841 current_range_indent = row_indent;
11842 current_range_comment_prefix = row_comment_prefix;
11843 current_range_rewrap_prefix = row_rewrap_prefix;
11844 }
11845 prev_row = row;
11846 }
11847
11848 ranges.push((
11849 language_settings.clone(),
11850 Point::new(current_range_start, 0)
11851 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11852 current_range_indent,
11853 current_range_comment_prefix,
11854 current_range_rewrap_prefix,
11855 from_empty_selection,
11856 ));
11857
11858 ranges
11859 });
11860
11861 let mut edits = Vec::new();
11862 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11863
11864 for (
11865 language_settings,
11866 wrap_range,
11867 indent_size,
11868 comment_prefix,
11869 rewrap_prefix,
11870 from_empty_selection,
11871 ) in wrap_ranges
11872 {
11873 let mut start_row = wrap_range.start.row;
11874 let mut end_row = wrap_range.end.row;
11875
11876 // Skip selections that overlap with a range that has already been rewrapped.
11877 let selection_range = start_row..end_row;
11878 if rewrapped_row_ranges
11879 .iter()
11880 .any(|range| range.overlaps(&selection_range))
11881 {
11882 continue;
11883 }
11884
11885 let tab_size = language_settings.tab_size;
11886
11887 let indent_prefix = indent_size.chars().collect::<String>();
11888 let mut line_prefix = indent_prefix.clone();
11889 let mut inside_comment = false;
11890 if let Some(prefix) = &comment_prefix {
11891 line_prefix.push_str(prefix);
11892 inside_comment = true;
11893 }
11894 if let Some(prefix) = &rewrap_prefix {
11895 line_prefix.push_str(prefix);
11896 }
11897
11898 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11899 RewrapBehavior::InComments => inside_comment,
11900 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11901 RewrapBehavior::Anywhere => true,
11902 };
11903
11904 let should_rewrap = options.override_language_settings
11905 || allow_rewrap_based_on_language
11906 || self.hard_wrap.is_some();
11907 if !should_rewrap {
11908 continue;
11909 }
11910
11911 if from_empty_selection {
11912 'expand_upwards: while start_row > 0 {
11913 let prev_row = start_row - 1;
11914 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11915 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11916 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11917 {
11918 start_row = prev_row;
11919 } else {
11920 break 'expand_upwards;
11921 }
11922 }
11923
11924 'expand_downwards: while end_row < buffer.max_point().row {
11925 let next_row = end_row + 1;
11926 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11927 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11928 && !buffer.is_line_blank(MultiBufferRow(next_row))
11929 {
11930 end_row = next_row;
11931 } else {
11932 break 'expand_downwards;
11933 }
11934 }
11935 }
11936
11937 let start = Point::new(start_row, 0);
11938 let start_offset = start.to_offset(&buffer);
11939 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11940 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11941 let Some(lines_without_prefixes) = selection_text
11942 .lines()
11943 .enumerate()
11944 .map(|(ix, line)| {
11945 let line_trimmed = line.trim_start();
11946 if rewrap_prefix.is_some() && ix > 0 {
11947 Ok(line_trimmed)
11948 } else {
11949 line_trimmed
11950 .strip_prefix(&line_prefix.trim_start())
11951 .with_context(|| {
11952 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11953 })
11954 }
11955 })
11956 .collect::<Result<Vec<_>, _>>()
11957 .log_err()
11958 else {
11959 continue;
11960 };
11961
11962 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11963 buffer
11964 .language_settings_at(Point::new(start_row, 0), cx)
11965 .preferred_line_length as usize
11966 });
11967
11968 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11969 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11970 } else {
11971 line_prefix.clone()
11972 };
11973
11974 let wrapped_text = wrap_with_prefix(
11975 line_prefix,
11976 subsequent_lines_prefix,
11977 lines_without_prefixes.join("\n"),
11978 wrap_column,
11979 tab_size,
11980 options.preserve_existing_whitespace,
11981 );
11982
11983 // TODO: should always use char-based diff while still supporting cursor behavior that
11984 // matches vim.
11985 let mut diff_options = DiffOptions::default();
11986 if options.override_language_settings {
11987 diff_options.max_word_diff_len = 0;
11988 diff_options.max_word_diff_line_count = 0;
11989 } else {
11990 diff_options.max_word_diff_len = usize::MAX;
11991 diff_options.max_word_diff_line_count = usize::MAX;
11992 }
11993
11994 for (old_range, new_text) in
11995 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11996 {
11997 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11998 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11999 edits.push((edit_start..edit_end, new_text));
12000 }
12001
12002 rewrapped_row_ranges.push(start_row..=end_row);
12003 }
12004
12005 self.buffer
12006 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12007 }
12008
12009 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
12010 let mut text = String::new();
12011 let buffer = self.buffer.read(cx).snapshot(cx);
12012 let mut selections = self.selections.all::<Point>(cx);
12013 let mut clipboard_selections = Vec::with_capacity(selections.len());
12014 {
12015 let max_point = buffer.max_point();
12016 let mut is_first = true;
12017 for selection in &mut selections {
12018 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12019 if is_entire_line {
12020 selection.start = Point::new(selection.start.row, 0);
12021 if !selection.is_empty() && selection.end.column == 0 {
12022 selection.end = cmp::min(max_point, selection.end);
12023 } else {
12024 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12025 }
12026 selection.goal = SelectionGoal::None;
12027 }
12028 if is_first {
12029 is_first = false;
12030 } else {
12031 text += "\n";
12032 }
12033 let mut len = 0;
12034 for chunk in buffer.text_for_range(selection.start..selection.end) {
12035 text.push_str(chunk);
12036 len += chunk.len();
12037 }
12038 clipboard_selections.push(ClipboardSelection {
12039 len,
12040 is_entire_line,
12041 first_line_indent: buffer
12042 .indent_size_for_line(MultiBufferRow(selection.start.row))
12043 .len,
12044 });
12045 }
12046 }
12047
12048 self.transact(window, cx, |this, window, cx| {
12049 this.change_selections(Default::default(), window, cx, |s| {
12050 s.select(selections);
12051 });
12052 this.insert("", window, cx);
12053 });
12054 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12055 }
12056
12057 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12058 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12059 let item = self.cut_common(window, cx);
12060 cx.write_to_clipboard(item);
12061 }
12062
12063 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12064 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12065 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12066 s.move_with(|snapshot, sel| {
12067 if sel.is_empty() {
12068 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12069 }
12070 });
12071 });
12072 let item = self.cut_common(window, cx);
12073 cx.set_global(KillRing(item))
12074 }
12075
12076 pub fn kill_ring_yank(
12077 &mut self,
12078 _: &KillRingYank,
12079 window: &mut Window,
12080 cx: &mut Context<Self>,
12081 ) {
12082 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12083 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12084 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12085 (kill_ring.text().to_string(), kill_ring.metadata_json())
12086 } else {
12087 return;
12088 }
12089 } else {
12090 return;
12091 };
12092 self.do_paste(&text, metadata, false, window, cx);
12093 }
12094
12095 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12096 self.do_copy(true, cx);
12097 }
12098
12099 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12100 self.do_copy(false, cx);
12101 }
12102
12103 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12104 let selections = self.selections.all::<Point>(cx);
12105 let buffer = self.buffer.read(cx).read(cx);
12106 let mut text = String::new();
12107
12108 let mut clipboard_selections = Vec::with_capacity(selections.len());
12109 {
12110 let max_point = buffer.max_point();
12111 let mut is_first = true;
12112 for selection in &selections {
12113 let mut start = selection.start;
12114 let mut end = selection.end;
12115 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12116 if is_entire_line {
12117 start = Point::new(start.row, 0);
12118 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12119 }
12120
12121 let mut trimmed_selections = Vec::new();
12122 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12123 let row = MultiBufferRow(start.row);
12124 let first_indent = buffer.indent_size_for_line(row);
12125 if first_indent.len == 0 || start.column > first_indent.len {
12126 trimmed_selections.push(start..end);
12127 } else {
12128 trimmed_selections.push(
12129 Point::new(row.0, first_indent.len)
12130 ..Point::new(row.0, buffer.line_len(row)),
12131 );
12132 for row in start.row + 1..=end.row {
12133 let mut line_len = buffer.line_len(MultiBufferRow(row));
12134 if row == end.row {
12135 line_len = end.column;
12136 }
12137 if line_len == 0 {
12138 trimmed_selections
12139 .push(Point::new(row, 0)..Point::new(row, line_len));
12140 continue;
12141 }
12142 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12143 if row_indent_size.len >= first_indent.len {
12144 trimmed_selections.push(
12145 Point::new(row, first_indent.len)..Point::new(row, line_len),
12146 );
12147 } else {
12148 trimmed_selections.clear();
12149 trimmed_selections.push(start..end);
12150 break;
12151 }
12152 }
12153 }
12154 } else {
12155 trimmed_selections.push(start..end);
12156 }
12157
12158 for trimmed_range in trimmed_selections {
12159 if is_first {
12160 is_first = false;
12161 } else {
12162 text += "\n";
12163 }
12164 let mut len = 0;
12165 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12166 text.push_str(chunk);
12167 len += chunk.len();
12168 }
12169 clipboard_selections.push(ClipboardSelection {
12170 len,
12171 is_entire_line,
12172 first_line_indent: buffer
12173 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12174 .len,
12175 });
12176 }
12177 }
12178 }
12179
12180 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12181 text,
12182 clipboard_selections,
12183 ));
12184 }
12185
12186 pub fn do_paste(
12187 &mut self,
12188 text: &String,
12189 clipboard_selections: Option<Vec<ClipboardSelection>>,
12190 handle_entire_lines: bool,
12191 window: &mut Window,
12192 cx: &mut Context<Self>,
12193 ) {
12194 if self.read_only(cx) {
12195 return;
12196 }
12197
12198 let clipboard_text = Cow::Borrowed(text);
12199
12200 self.transact(window, cx, |this, window, cx| {
12201 let had_active_edit_prediction = this.has_active_edit_prediction();
12202
12203 if let Some(mut clipboard_selections) = clipboard_selections {
12204 let old_selections = this.selections.all::<usize>(cx);
12205 let all_selections_were_entire_line =
12206 clipboard_selections.iter().all(|s| s.is_entire_line);
12207 let first_selection_indent_column =
12208 clipboard_selections.first().map(|s| s.first_line_indent);
12209 if clipboard_selections.len() != old_selections.len() {
12210 clipboard_selections.drain(..);
12211 }
12212 let cursor_offset = this.selections.last::<usize>(cx).head();
12213 let mut auto_indent_on_paste = true;
12214
12215 this.buffer.update(cx, |buffer, cx| {
12216 let snapshot = buffer.read(cx);
12217 auto_indent_on_paste = snapshot
12218 .language_settings_at(cursor_offset, cx)
12219 .auto_indent_on_paste;
12220
12221 let mut start_offset = 0;
12222 let mut edits = Vec::new();
12223 let mut original_indent_columns = Vec::new();
12224 for (ix, selection) in old_selections.iter().enumerate() {
12225 let to_insert;
12226 let entire_line;
12227 let original_indent_column;
12228 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12229 let end_offset = start_offset + clipboard_selection.len;
12230 to_insert = &clipboard_text[start_offset..end_offset];
12231 entire_line = clipboard_selection.is_entire_line;
12232 start_offset = end_offset + 1;
12233 original_indent_column = Some(clipboard_selection.first_line_indent);
12234 } else {
12235 to_insert = clipboard_text.as_str();
12236 entire_line = all_selections_were_entire_line;
12237 original_indent_column = first_selection_indent_column
12238 }
12239
12240 // If the corresponding selection was empty when this slice of the
12241 // clipboard text was written, then the entire line containing the
12242 // selection was copied. If this selection is also currently empty,
12243 // then paste the line before the current line of the buffer.
12244 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12245 let column = selection.start.to_point(&snapshot).column as usize;
12246 let line_start = selection.start - column;
12247 line_start..line_start
12248 } else {
12249 selection.range()
12250 };
12251
12252 edits.push((range, to_insert));
12253 original_indent_columns.push(original_indent_column);
12254 }
12255 drop(snapshot);
12256
12257 buffer.edit(
12258 edits,
12259 if auto_indent_on_paste {
12260 Some(AutoindentMode::Block {
12261 original_indent_columns,
12262 })
12263 } else {
12264 None
12265 },
12266 cx,
12267 );
12268 });
12269
12270 let selections = this.selections.all::<usize>(cx);
12271 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12272 } else {
12273 this.insert(&clipboard_text, window, cx);
12274 }
12275
12276 let trigger_in_words =
12277 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12278
12279 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12280 });
12281 }
12282
12283 pub fn diff_clipboard_with_selection(
12284 &mut self,
12285 _: &DiffClipboardWithSelection,
12286 window: &mut Window,
12287 cx: &mut Context<Self>,
12288 ) {
12289 let selections = self.selections.all::<usize>(cx);
12290
12291 if selections.is_empty() {
12292 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12293 return;
12294 };
12295
12296 let clipboard_text = match cx.read_from_clipboard() {
12297 Some(item) => match item.entries().first() {
12298 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12299 _ => None,
12300 },
12301 None => None,
12302 };
12303
12304 let Some(clipboard_text) = clipboard_text else {
12305 log::warn!("Clipboard doesn't contain text.");
12306 return;
12307 };
12308
12309 window.dispatch_action(
12310 Box::new(DiffClipboardWithSelectionData {
12311 clipboard_text,
12312 editor: cx.entity(),
12313 }),
12314 cx,
12315 );
12316 }
12317
12318 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12319 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12320 if let Some(item) = cx.read_from_clipboard() {
12321 let entries = item.entries();
12322
12323 match entries.first() {
12324 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12325 // of all the pasted entries.
12326 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12327 .do_paste(
12328 clipboard_string.text(),
12329 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12330 true,
12331 window,
12332 cx,
12333 ),
12334 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12335 }
12336 }
12337 }
12338
12339 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12340 if self.read_only(cx) {
12341 return;
12342 }
12343
12344 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12345
12346 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12347 if let Some((selections, _)) =
12348 self.selection_history.transaction(transaction_id).cloned()
12349 {
12350 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12351 s.select_anchors(selections.to_vec());
12352 });
12353 } else {
12354 log::error!(
12355 "No entry in selection_history found for undo. \
12356 This may correspond to a bug where undo does not update the selection. \
12357 If this is occurring, please add details to \
12358 https://github.com/zed-industries/zed/issues/22692"
12359 );
12360 }
12361 self.request_autoscroll(Autoscroll::fit(), cx);
12362 self.unmark_text(window, cx);
12363 self.refresh_edit_prediction(true, false, window, cx);
12364 cx.emit(EditorEvent::Edited { transaction_id });
12365 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12366 }
12367 }
12368
12369 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12370 if self.read_only(cx) {
12371 return;
12372 }
12373
12374 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12375
12376 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12377 if let Some((_, Some(selections))) =
12378 self.selection_history.transaction(transaction_id).cloned()
12379 {
12380 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12381 s.select_anchors(selections.to_vec());
12382 });
12383 } else {
12384 log::error!(
12385 "No entry in selection_history found for redo. \
12386 This may correspond to a bug where undo does not update the selection. \
12387 If this is occurring, please add details to \
12388 https://github.com/zed-industries/zed/issues/22692"
12389 );
12390 }
12391 self.request_autoscroll(Autoscroll::fit(), cx);
12392 self.unmark_text(window, cx);
12393 self.refresh_edit_prediction(true, false, window, cx);
12394 cx.emit(EditorEvent::Edited { transaction_id });
12395 }
12396 }
12397
12398 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12399 self.buffer
12400 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12401 }
12402
12403 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12404 self.buffer
12405 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12406 }
12407
12408 pub fn move_left(&mut self, _: &MoveLeft, 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_with(|map, selection| {
12412 let cursor = if selection.is_empty() {
12413 movement::left(map, selection.start)
12414 } else {
12415 selection.start
12416 };
12417 selection.collapse_to(cursor, SelectionGoal::None);
12418 });
12419 })
12420 }
12421
12422 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12423 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12424 self.change_selections(Default::default(), window, cx, |s| {
12425 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12426 })
12427 }
12428
12429 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12430 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12431 self.change_selections(Default::default(), window, cx, |s| {
12432 s.move_with(|map, selection| {
12433 let cursor = if selection.is_empty() {
12434 movement::right(map, selection.end)
12435 } else {
12436 selection.end
12437 };
12438 selection.collapse_to(cursor, SelectionGoal::None)
12439 });
12440 })
12441 }
12442
12443 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12444 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12445 self.change_selections(Default::default(), window, cx, |s| {
12446 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12447 })
12448 }
12449
12450 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12451 if self.take_rename(true, window, cx).is_some() {
12452 return;
12453 }
12454
12455 if self.mode.is_single_line() {
12456 cx.propagate();
12457 return;
12458 }
12459
12460 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12461
12462 let text_layout_details = &self.text_layout_details(window);
12463 let selection_count = self.selections.count();
12464 let first_selection = self.selections.first_anchor();
12465
12466 self.change_selections(Default::default(), window, cx, |s| {
12467 s.move_with(|map, selection| {
12468 if !selection.is_empty() {
12469 selection.goal = SelectionGoal::None;
12470 }
12471 let (cursor, goal) = movement::up(
12472 map,
12473 selection.start,
12474 selection.goal,
12475 false,
12476 text_layout_details,
12477 );
12478 selection.collapse_to(cursor, goal);
12479 });
12480 });
12481
12482 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12483 {
12484 cx.propagate();
12485 }
12486 }
12487
12488 pub fn move_up_by_lines(
12489 &mut self,
12490 action: &MoveUpByLines,
12491 window: &mut Window,
12492 cx: &mut Context<Self>,
12493 ) {
12494 if self.take_rename(true, window, cx).is_some() {
12495 return;
12496 }
12497
12498 if self.mode.is_single_line() {
12499 cx.propagate();
12500 return;
12501 }
12502
12503 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12504
12505 let text_layout_details = &self.text_layout_details(window);
12506
12507 self.change_selections(Default::default(), window, cx, |s| {
12508 s.move_with(|map, selection| {
12509 if !selection.is_empty() {
12510 selection.goal = SelectionGoal::None;
12511 }
12512 let (cursor, goal) = movement::up_by_rows(
12513 map,
12514 selection.start,
12515 action.lines,
12516 selection.goal,
12517 false,
12518 text_layout_details,
12519 );
12520 selection.collapse_to(cursor, goal);
12521 });
12522 })
12523 }
12524
12525 pub fn move_down_by_lines(
12526 &mut self,
12527 action: &MoveDownByLines,
12528 window: &mut Window,
12529 cx: &mut Context<Self>,
12530 ) {
12531 if self.take_rename(true, window, cx).is_some() {
12532 return;
12533 }
12534
12535 if self.mode.is_single_line() {
12536 cx.propagate();
12537 return;
12538 }
12539
12540 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12541
12542 let text_layout_details = &self.text_layout_details(window);
12543
12544 self.change_selections(Default::default(), window, cx, |s| {
12545 s.move_with(|map, selection| {
12546 if !selection.is_empty() {
12547 selection.goal = SelectionGoal::None;
12548 }
12549 let (cursor, goal) = movement::down_by_rows(
12550 map,
12551 selection.start,
12552 action.lines,
12553 selection.goal,
12554 false,
12555 text_layout_details,
12556 );
12557 selection.collapse_to(cursor, goal);
12558 });
12559 })
12560 }
12561
12562 pub fn select_down_by_lines(
12563 &mut self,
12564 action: &SelectDownByLines,
12565 window: &mut Window,
12566 cx: &mut Context<Self>,
12567 ) {
12568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12569 let text_layout_details = &self.text_layout_details(window);
12570 self.change_selections(Default::default(), window, cx, |s| {
12571 s.move_heads_with(|map, head, goal| {
12572 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12573 })
12574 })
12575 }
12576
12577 pub fn select_up_by_lines(
12578 &mut self,
12579 action: &SelectUpByLines,
12580 window: &mut Window,
12581 cx: &mut Context<Self>,
12582 ) {
12583 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12584 let text_layout_details = &self.text_layout_details(window);
12585 self.change_selections(Default::default(), window, cx, |s| {
12586 s.move_heads_with(|map, head, goal| {
12587 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12588 })
12589 })
12590 }
12591
12592 pub fn select_page_up(
12593 &mut self,
12594 _: &SelectPageUp,
12595 window: &mut Window,
12596 cx: &mut Context<Self>,
12597 ) {
12598 let Some(row_count) = self.visible_row_count() else {
12599 return;
12600 };
12601
12602 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12603
12604 let text_layout_details = &self.text_layout_details(window);
12605
12606 self.change_selections(Default::default(), window, cx, |s| {
12607 s.move_heads_with(|map, head, goal| {
12608 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12609 })
12610 })
12611 }
12612
12613 pub fn move_page_up(
12614 &mut self,
12615 action: &MovePageUp,
12616 window: &mut Window,
12617 cx: &mut Context<Self>,
12618 ) {
12619 if self.take_rename(true, window, cx).is_some() {
12620 return;
12621 }
12622
12623 if self
12624 .context_menu
12625 .borrow_mut()
12626 .as_mut()
12627 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12628 .unwrap_or(false)
12629 {
12630 return;
12631 }
12632
12633 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12634 cx.propagate();
12635 return;
12636 }
12637
12638 let Some(row_count) = self.visible_row_count() else {
12639 return;
12640 };
12641
12642 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12643
12644 let effects = if action.center_cursor {
12645 SelectionEffects::scroll(Autoscroll::center())
12646 } else {
12647 SelectionEffects::default()
12648 };
12649
12650 let text_layout_details = &self.text_layout_details(window);
12651
12652 self.change_selections(effects, window, cx, |s| {
12653 s.move_with(|map, selection| {
12654 if !selection.is_empty() {
12655 selection.goal = SelectionGoal::None;
12656 }
12657 let (cursor, goal) = movement::up_by_rows(
12658 map,
12659 selection.end,
12660 row_count,
12661 selection.goal,
12662 false,
12663 text_layout_details,
12664 );
12665 selection.collapse_to(cursor, goal);
12666 });
12667 });
12668 }
12669
12670 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12671 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12672 let text_layout_details = &self.text_layout_details(window);
12673 self.change_selections(Default::default(), window, cx, |s| {
12674 s.move_heads_with(|map, head, goal| {
12675 movement::up(map, head, goal, false, text_layout_details)
12676 })
12677 })
12678 }
12679
12680 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12681 self.take_rename(true, window, cx);
12682
12683 if self.mode.is_single_line() {
12684 cx.propagate();
12685 return;
12686 }
12687
12688 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12689
12690 let text_layout_details = &self.text_layout_details(window);
12691 let selection_count = self.selections.count();
12692 let first_selection = self.selections.first_anchor();
12693
12694 self.change_selections(Default::default(), window, cx, |s| {
12695 s.move_with(|map, selection| {
12696 if !selection.is_empty() {
12697 selection.goal = SelectionGoal::None;
12698 }
12699 let (cursor, goal) = movement::down(
12700 map,
12701 selection.end,
12702 selection.goal,
12703 false,
12704 text_layout_details,
12705 );
12706 selection.collapse_to(cursor, goal);
12707 });
12708 });
12709
12710 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12711 {
12712 cx.propagate();
12713 }
12714 }
12715
12716 pub fn select_page_down(
12717 &mut self,
12718 _: &SelectPageDown,
12719 window: &mut Window,
12720 cx: &mut Context<Self>,
12721 ) {
12722 let Some(row_count) = self.visible_row_count() else {
12723 return;
12724 };
12725
12726 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12727
12728 let text_layout_details = &self.text_layout_details(window);
12729
12730 self.change_selections(Default::default(), window, cx, |s| {
12731 s.move_heads_with(|map, head, goal| {
12732 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12733 })
12734 })
12735 }
12736
12737 pub fn move_page_down(
12738 &mut self,
12739 action: &MovePageDown,
12740 window: &mut Window,
12741 cx: &mut Context<Self>,
12742 ) {
12743 if self.take_rename(true, window, cx).is_some() {
12744 return;
12745 }
12746
12747 if self
12748 .context_menu
12749 .borrow_mut()
12750 .as_mut()
12751 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12752 .unwrap_or(false)
12753 {
12754 return;
12755 }
12756
12757 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12758 cx.propagate();
12759 return;
12760 }
12761
12762 let Some(row_count) = self.visible_row_count() else {
12763 return;
12764 };
12765
12766 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12767
12768 let effects = if action.center_cursor {
12769 SelectionEffects::scroll(Autoscroll::center())
12770 } else {
12771 SelectionEffects::default()
12772 };
12773
12774 let text_layout_details = &self.text_layout_details(window);
12775 self.change_selections(effects, window, cx, |s| {
12776 s.move_with(|map, selection| {
12777 if !selection.is_empty() {
12778 selection.goal = SelectionGoal::None;
12779 }
12780 let (cursor, goal) = movement::down_by_rows(
12781 map,
12782 selection.end,
12783 row_count,
12784 selection.goal,
12785 false,
12786 text_layout_details,
12787 );
12788 selection.collapse_to(cursor, goal);
12789 });
12790 });
12791 }
12792
12793 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12794 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12795 let text_layout_details = &self.text_layout_details(window);
12796 self.change_selections(Default::default(), window, cx, |s| {
12797 s.move_heads_with(|map, head, goal| {
12798 movement::down(map, head, goal, false, text_layout_details)
12799 })
12800 });
12801 }
12802
12803 pub fn context_menu_first(
12804 &mut self,
12805 _: &ContextMenuFirst,
12806 window: &mut Window,
12807 cx: &mut Context<Self>,
12808 ) {
12809 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12810 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12811 }
12812 }
12813
12814 pub fn context_menu_prev(
12815 &mut self,
12816 _: &ContextMenuPrevious,
12817 window: &mut Window,
12818 cx: &mut Context<Self>,
12819 ) {
12820 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12821 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12822 }
12823 }
12824
12825 pub fn context_menu_next(
12826 &mut self,
12827 _: &ContextMenuNext,
12828 window: &mut Window,
12829 cx: &mut Context<Self>,
12830 ) {
12831 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12832 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12833 }
12834 }
12835
12836 pub fn context_menu_last(
12837 &mut self,
12838 _: &ContextMenuLast,
12839 window: &mut Window,
12840 cx: &mut Context<Self>,
12841 ) {
12842 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12843 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12844 }
12845 }
12846
12847 pub fn signature_help_prev(
12848 &mut self,
12849 _: &SignatureHelpPrevious,
12850 _: &mut Window,
12851 cx: &mut Context<Self>,
12852 ) {
12853 if let Some(popover) = self.signature_help_state.popover_mut() {
12854 if popover.current_signature == 0 {
12855 popover.current_signature = popover.signatures.len() - 1;
12856 } else {
12857 popover.current_signature -= 1;
12858 }
12859 cx.notify();
12860 }
12861 }
12862
12863 pub fn signature_help_next(
12864 &mut self,
12865 _: &SignatureHelpNext,
12866 _: &mut Window,
12867 cx: &mut Context<Self>,
12868 ) {
12869 if let Some(popover) = self.signature_help_state.popover_mut() {
12870 if popover.current_signature + 1 == popover.signatures.len() {
12871 popover.current_signature = 0;
12872 } else {
12873 popover.current_signature += 1;
12874 }
12875 cx.notify();
12876 }
12877 }
12878
12879 pub fn move_to_previous_word_start(
12880 &mut self,
12881 _: &MoveToPreviousWordStart,
12882 window: &mut Window,
12883 cx: &mut Context<Self>,
12884 ) {
12885 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12886 self.change_selections(Default::default(), window, cx, |s| {
12887 s.move_cursors_with(|map, head, _| {
12888 (
12889 movement::previous_word_start(map, head),
12890 SelectionGoal::None,
12891 )
12892 });
12893 })
12894 }
12895
12896 pub fn move_to_previous_subword_start(
12897 &mut self,
12898 _: &MoveToPreviousSubwordStart,
12899 window: &mut Window,
12900 cx: &mut Context<Self>,
12901 ) {
12902 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12903 self.change_selections(Default::default(), window, cx, |s| {
12904 s.move_cursors_with(|map, head, _| {
12905 (
12906 movement::previous_subword_start(map, head),
12907 SelectionGoal::None,
12908 )
12909 });
12910 })
12911 }
12912
12913 pub fn select_to_previous_word_start(
12914 &mut self,
12915 _: &SelectToPreviousWordStart,
12916 window: &mut Window,
12917 cx: &mut Context<Self>,
12918 ) {
12919 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12920 self.change_selections(Default::default(), window, cx, |s| {
12921 s.move_heads_with(|map, head, _| {
12922 (
12923 movement::previous_word_start(map, head),
12924 SelectionGoal::None,
12925 )
12926 });
12927 })
12928 }
12929
12930 pub fn select_to_previous_subword_start(
12931 &mut self,
12932 _: &SelectToPreviousSubwordStart,
12933 window: &mut Window,
12934 cx: &mut Context<Self>,
12935 ) {
12936 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12937 self.change_selections(Default::default(), window, cx, |s| {
12938 s.move_heads_with(|map, head, _| {
12939 (
12940 movement::previous_subword_start(map, head),
12941 SelectionGoal::None,
12942 )
12943 });
12944 })
12945 }
12946
12947 pub fn delete_to_previous_word_start(
12948 &mut self,
12949 action: &DeleteToPreviousWordStart,
12950 window: &mut Window,
12951 cx: &mut Context<Self>,
12952 ) {
12953 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12954 self.transact(window, cx, |this, window, cx| {
12955 this.select_autoclose_pair(window, cx);
12956 this.change_selections(Default::default(), window, cx, |s| {
12957 s.move_with(|map, selection| {
12958 if selection.is_empty() {
12959 let cursor = if action.ignore_newlines {
12960 movement::previous_word_start(map, selection.head())
12961 } else {
12962 movement::previous_word_start_or_newline(map, selection.head())
12963 };
12964 selection.set_head(cursor, SelectionGoal::None);
12965 }
12966 });
12967 });
12968 this.insert("", window, cx);
12969 });
12970 }
12971
12972 pub fn delete_to_previous_subword_start(
12973 &mut self,
12974 _: &DeleteToPreviousSubwordStart,
12975 window: &mut Window,
12976 cx: &mut Context<Self>,
12977 ) {
12978 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12979 self.transact(window, cx, |this, window, cx| {
12980 this.select_autoclose_pair(window, cx);
12981 this.change_selections(Default::default(), window, cx, |s| {
12982 s.move_with(|map, selection| {
12983 if selection.is_empty() {
12984 let cursor = movement::previous_subword_start(map, selection.head());
12985 selection.set_head(cursor, SelectionGoal::None);
12986 }
12987 });
12988 });
12989 this.insert("", window, cx);
12990 });
12991 }
12992
12993 pub fn move_to_next_word_end(
12994 &mut self,
12995 _: &MoveToNextWordEnd,
12996 window: &mut Window,
12997 cx: &mut Context<Self>,
12998 ) {
12999 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13000 self.change_selections(Default::default(), window, cx, |s| {
13001 s.move_cursors_with(|map, head, _| {
13002 (movement::next_word_end(map, head), SelectionGoal::None)
13003 });
13004 })
13005 }
13006
13007 pub fn move_to_next_subword_end(
13008 &mut self,
13009 _: &MoveToNextSubwordEnd,
13010 window: &mut Window,
13011 cx: &mut Context<Self>,
13012 ) {
13013 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13014 self.change_selections(Default::default(), window, cx, |s| {
13015 s.move_cursors_with(|map, head, _| {
13016 (movement::next_subword_end(map, head), SelectionGoal::None)
13017 });
13018 })
13019 }
13020
13021 pub fn select_to_next_word_end(
13022 &mut self,
13023 _: &SelectToNextWordEnd,
13024 window: &mut Window,
13025 cx: &mut Context<Self>,
13026 ) {
13027 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13028 self.change_selections(Default::default(), window, cx, |s| {
13029 s.move_heads_with(|map, head, _| {
13030 (movement::next_word_end(map, head), SelectionGoal::None)
13031 });
13032 })
13033 }
13034
13035 pub fn select_to_next_subword_end(
13036 &mut self,
13037 _: &SelectToNextSubwordEnd,
13038 window: &mut Window,
13039 cx: &mut Context<Self>,
13040 ) {
13041 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13042 self.change_selections(Default::default(), window, cx, |s| {
13043 s.move_heads_with(|map, head, _| {
13044 (movement::next_subword_end(map, head), SelectionGoal::None)
13045 });
13046 })
13047 }
13048
13049 pub fn delete_to_next_word_end(
13050 &mut self,
13051 action: &DeleteToNextWordEnd,
13052 window: &mut Window,
13053 cx: &mut Context<Self>,
13054 ) {
13055 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13056 self.transact(window, cx, |this, window, cx| {
13057 this.change_selections(Default::default(), window, cx, |s| {
13058 s.move_with(|map, selection| {
13059 if selection.is_empty() {
13060 let cursor = if action.ignore_newlines {
13061 movement::next_word_end(map, selection.head())
13062 } else {
13063 movement::next_word_end_or_newline(map, selection.head())
13064 };
13065 selection.set_head(cursor, SelectionGoal::None);
13066 }
13067 });
13068 });
13069 this.insert("", window, cx);
13070 });
13071 }
13072
13073 pub fn delete_to_next_subword_end(
13074 &mut self,
13075 _: &DeleteToNextSubwordEnd,
13076 window: &mut Window,
13077 cx: &mut Context<Self>,
13078 ) {
13079 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13080 self.transact(window, cx, |this, window, cx| {
13081 this.change_selections(Default::default(), window, cx, |s| {
13082 s.move_with(|map, selection| {
13083 if selection.is_empty() {
13084 let cursor = movement::next_subword_end(map, selection.head());
13085 selection.set_head(cursor, SelectionGoal::None);
13086 }
13087 });
13088 });
13089 this.insert("", window, cx);
13090 });
13091 }
13092
13093 pub fn move_to_beginning_of_line(
13094 &mut self,
13095 action: &MoveToBeginningOfLine,
13096 window: &mut Window,
13097 cx: &mut Context<Self>,
13098 ) {
13099 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13100 self.change_selections(Default::default(), window, cx, |s| {
13101 s.move_cursors_with(|map, head, _| {
13102 (
13103 movement::indented_line_beginning(
13104 map,
13105 head,
13106 action.stop_at_soft_wraps,
13107 action.stop_at_indent,
13108 ),
13109 SelectionGoal::None,
13110 )
13111 });
13112 })
13113 }
13114
13115 pub fn select_to_beginning_of_line(
13116 &mut self,
13117 action: &SelectToBeginningOfLine,
13118 window: &mut Window,
13119 cx: &mut Context<Self>,
13120 ) {
13121 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13122 self.change_selections(Default::default(), window, cx, |s| {
13123 s.move_heads_with(|map, head, _| {
13124 (
13125 movement::indented_line_beginning(
13126 map,
13127 head,
13128 action.stop_at_soft_wraps,
13129 action.stop_at_indent,
13130 ),
13131 SelectionGoal::None,
13132 )
13133 });
13134 });
13135 }
13136
13137 pub fn delete_to_beginning_of_line(
13138 &mut self,
13139 action: &DeleteToBeginningOfLine,
13140 window: &mut Window,
13141 cx: &mut Context<Self>,
13142 ) {
13143 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13144 self.transact(window, cx, |this, window, cx| {
13145 this.change_selections(Default::default(), window, cx, |s| {
13146 s.move_with(|_, selection| {
13147 selection.reversed = true;
13148 });
13149 });
13150
13151 this.select_to_beginning_of_line(
13152 &SelectToBeginningOfLine {
13153 stop_at_soft_wraps: false,
13154 stop_at_indent: action.stop_at_indent,
13155 },
13156 window,
13157 cx,
13158 );
13159 this.backspace(&Backspace, window, cx);
13160 });
13161 }
13162
13163 pub fn move_to_end_of_line(
13164 &mut self,
13165 action: &MoveToEndOfLine,
13166 window: &mut Window,
13167 cx: &mut Context<Self>,
13168 ) {
13169 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13170 self.change_selections(Default::default(), window, cx, |s| {
13171 s.move_cursors_with(|map, head, _| {
13172 (
13173 movement::line_end(map, head, action.stop_at_soft_wraps),
13174 SelectionGoal::None,
13175 )
13176 });
13177 })
13178 }
13179
13180 pub fn select_to_end_of_line(
13181 &mut self,
13182 action: &SelectToEndOfLine,
13183 window: &mut Window,
13184 cx: &mut Context<Self>,
13185 ) {
13186 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13187 self.change_selections(Default::default(), window, cx, |s| {
13188 s.move_heads_with(|map, head, _| {
13189 (
13190 movement::line_end(map, head, action.stop_at_soft_wraps),
13191 SelectionGoal::None,
13192 )
13193 });
13194 })
13195 }
13196
13197 pub fn delete_to_end_of_line(
13198 &mut self,
13199 _: &DeleteToEndOfLine,
13200 window: &mut Window,
13201 cx: &mut Context<Self>,
13202 ) {
13203 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13204 self.transact(window, cx, |this, window, cx| {
13205 this.select_to_end_of_line(
13206 &SelectToEndOfLine {
13207 stop_at_soft_wraps: false,
13208 },
13209 window,
13210 cx,
13211 );
13212 this.delete(&Delete, window, cx);
13213 });
13214 }
13215
13216 pub fn cut_to_end_of_line(
13217 &mut self,
13218 _: &CutToEndOfLine,
13219 window: &mut Window,
13220 cx: &mut Context<Self>,
13221 ) {
13222 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13223 self.transact(window, cx, |this, window, cx| {
13224 this.select_to_end_of_line(
13225 &SelectToEndOfLine {
13226 stop_at_soft_wraps: false,
13227 },
13228 window,
13229 cx,
13230 );
13231 this.cut(&Cut, window, cx);
13232 });
13233 }
13234
13235 pub fn move_to_start_of_paragraph(
13236 &mut self,
13237 _: &MoveToStartOfParagraph,
13238 window: &mut Window,
13239 cx: &mut Context<Self>,
13240 ) {
13241 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13242 cx.propagate();
13243 return;
13244 }
13245 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13246 self.change_selections(Default::default(), window, cx, |s| {
13247 s.move_with(|map, selection| {
13248 selection.collapse_to(
13249 movement::start_of_paragraph(map, selection.head(), 1),
13250 SelectionGoal::None,
13251 )
13252 });
13253 })
13254 }
13255
13256 pub fn move_to_end_of_paragraph(
13257 &mut self,
13258 _: &MoveToEndOfParagraph,
13259 window: &mut Window,
13260 cx: &mut Context<Self>,
13261 ) {
13262 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13263 cx.propagate();
13264 return;
13265 }
13266 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13267 self.change_selections(Default::default(), window, cx, |s| {
13268 s.move_with(|map, selection| {
13269 selection.collapse_to(
13270 movement::end_of_paragraph(map, selection.head(), 1),
13271 SelectionGoal::None,
13272 )
13273 });
13274 })
13275 }
13276
13277 pub fn select_to_start_of_paragraph(
13278 &mut self,
13279 _: &SelectToStartOfParagraph,
13280 window: &mut Window,
13281 cx: &mut Context<Self>,
13282 ) {
13283 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13284 cx.propagate();
13285 return;
13286 }
13287 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13288 self.change_selections(Default::default(), window, cx, |s| {
13289 s.move_heads_with(|map, head, _| {
13290 (
13291 movement::start_of_paragraph(map, head, 1),
13292 SelectionGoal::None,
13293 )
13294 });
13295 })
13296 }
13297
13298 pub fn select_to_end_of_paragraph(
13299 &mut self,
13300 _: &SelectToEndOfParagraph,
13301 window: &mut Window,
13302 cx: &mut Context<Self>,
13303 ) {
13304 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13305 cx.propagate();
13306 return;
13307 }
13308 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13309 self.change_selections(Default::default(), window, cx, |s| {
13310 s.move_heads_with(|map, head, _| {
13311 (
13312 movement::end_of_paragraph(map, head, 1),
13313 SelectionGoal::None,
13314 )
13315 });
13316 })
13317 }
13318
13319 pub fn move_to_start_of_excerpt(
13320 &mut self,
13321 _: &MoveToStartOfExcerpt,
13322 window: &mut Window,
13323 cx: &mut Context<Self>,
13324 ) {
13325 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13326 cx.propagate();
13327 return;
13328 }
13329 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13330 self.change_selections(Default::default(), window, cx, |s| {
13331 s.move_with(|map, selection| {
13332 selection.collapse_to(
13333 movement::start_of_excerpt(
13334 map,
13335 selection.head(),
13336 workspace::searchable::Direction::Prev,
13337 ),
13338 SelectionGoal::None,
13339 )
13340 });
13341 })
13342 }
13343
13344 pub fn move_to_start_of_next_excerpt(
13345 &mut self,
13346 _: &MoveToStartOfNextExcerpt,
13347 window: &mut Window,
13348 cx: &mut Context<Self>,
13349 ) {
13350 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13351 cx.propagate();
13352 return;
13353 }
13354
13355 self.change_selections(Default::default(), window, cx, |s| {
13356 s.move_with(|map, selection| {
13357 selection.collapse_to(
13358 movement::start_of_excerpt(
13359 map,
13360 selection.head(),
13361 workspace::searchable::Direction::Next,
13362 ),
13363 SelectionGoal::None,
13364 )
13365 });
13366 })
13367 }
13368
13369 pub fn move_to_end_of_excerpt(
13370 &mut self,
13371 _: &MoveToEndOfExcerpt,
13372 window: &mut Window,
13373 cx: &mut Context<Self>,
13374 ) {
13375 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13376 cx.propagate();
13377 return;
13378 }
13379 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13380 self.change_selections(Default::default(), window, cx, |s| {
13381 s.move_with(|map, selection| {
13382 selection.collapse_to(
13383 movement::end_of_excerpt(
13384 map,
13385 selection.head(),
13386 workspace::searchable::Direction::Next,
13387 ),
13388 SelectionGoal::None,
13389 )
13390 });
13391 })
13392 }
13393
13394 pub fn move_to_end_of_previous_excerpt(
13395 &mut self,
13396 _: &MoveToEndOfPreviousExcerpt,
13397 window: &mut Window,
13398 cx: &mut Context<Self>,
13399 ) {
13400 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13401 cx.propagate();
13402 return;
13403 }
13404 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13405 self.change_selections(Default::default(), window, cx, |s| {
13406 s.move_with(|map, selection| {
13407 selection.collapse_to(
13408 movement::end_of_excerpt(
13409 map,
13410 selection.head(),
13411 workspace::searchable::Direction::Prev,
13412 ),
13413 SelectionGoal::None,
13414 )
13415 });
13416 })
13417 }
13418
13419 pub fn select_to_start_of_excerpt(
13420 &mut self,
13421 _: &SelectToStartOfExcerpt,
13422 window: &mut Window,
13423 cx: &mut Context<Self>,
13424 ) {
13425 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13426 cx.propagate();
13427 return;
13428 }
13429 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13430 self.change_selections(Default::default(), window, cx, |s| {
13431 s.move_heads_with(|map, head, _| {
13432 (
13433 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13434 SelectionGoal::None,
13435 )
13436 });
13437 })
13438 }
13439
13440 pub fn select_to_start_of_next_excerpt(
13441 &mut self,
13442 _: &SelectToStartOfNextExcerpt,
13443 window: &mut Window,
13444 cx: &mut Context<Self>,
13445 ) {
13446 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13447 cx.propagate();
13448 return;
13449 }
13450 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13451 self.change_selections(Default::default(), window, cx, |s| {
13452 s.move_heads_with(|map, head, _| {
13453 (
13454 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13455 SelectionGoal::None,
13456 )
13457 });
13458 })
13459 }
13460
13461 pub fn select_to_end_of_excerpt(
13462 &mut self,
13463 _: &SelectToEndOfExcerpt,
13464 window: &mut Window,
13465 cx: &mut Context<Self>,
13466 ) {
13467 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13468 cx.propagate();
13469 return;
13470 }
13471 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13472 self.change_selections(Default::default(), window, cx, |s| {
13473 s.move_heads_with(|map, head, _| {
13474 (
13475 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13476 SelectionGoal::None,
13477 )
13478 });
13479 })
13480 }
13481
13482 pub fn select_to_end_of_previous_excerpt(
13483 &mut self,
13484 _: &SelectToEndOfPreviousExcerpt,
13485 window: &mut Window,
13486 cx: &mut Context<Self>,
13487 ) {
13488 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13489 cx.propagate();
13490 return;
13491 }
13492 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13493 self.change_selections(Default::default(), window, cx, |s| {
13494 s.move_heads_with(|map, head, _| {
13495 (
13496 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13497 SelectionGoal::None,
13498 )
13499 });
13500 })
13501 }
13502
13503 pub fn move_to_beginning(
13504 &mut self,
13505 _: &MoveToBeginning,
13506 window: &mut Window,
13507 cx: &mut Context<Self>,
13508 ) {
13509 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13510 cx.propagate();
13511 return;
13512 }
13513 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13514 self.change_selections(Default::default(), window, cx, |s| {
13515 s.select_ranges(vec![0..0]);
13516 });
13517 }
13518
13519 pub fn select_to_beginning(
13520 &mut self,
13521 _: &SelectToBeginning,
13522 window: &mut Window,
13523 cx: &mut Context<Self>,
13524 ) {
13525 let mut selection = self.selections.last::<Point>(cx);
13526 selection.set_head(Point::zero(), SelectionGoal::None);
13527 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13528 self.change_selections(Default::default(), window, cx, |s| {
13529 s.select(vec![selection]);
13530 });
13531 }
13532
13533 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13534 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13535 cx.propagate();
13536 return;
13537 }
13538 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13539 let cursor = self.buffer.read(cx).read(cx).len();
13540 self.change_selections(Default::default(), window, cx, |s| {
13541 s.select_ranges(vec![cursor..cursor])
13542 });
13543 }
13544
13545 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13546 self.nav_history = nav_history;
13547 }
13548
13549 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13550 self.nav_history.as_ref()
13551 }
13552
13553 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13554 self.push_to_nav_history(
13555 self.selections.newest_anchor().head(),
13556 None,
13557 false,
13558 true,
13559 cx,
13560 );
13561 }
13562
13563 fn push_to_nav_history(
13564 &mut self,
13565 cursor_anchor: Anchor,
13566 new_position: Option<Point>,
13567 is_deactivate: bool,
13568 always: bool,
13569 cx: &mut Context<Self>,
13570 ) {
13571 if let Some(nav_history) = self.nav_history.as_mut() {
13572 let buffer = self.buffer.read(cx).read(cx);
13573 let cursor_position = cursor_anchor.to_point(&buffer);
13574 let scroll_state = self.scroll_manager.anchor();
13575 let scroll_top_row = scroll_state.top_row(&buffer);
13576 drop(buffer);
13577
13578 if let Some(new_position) = new_position {
13579 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13580 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13581 return;
13582 }
13583 }
13584
13585 nav_history.push(
13586 Some(NavigationData {
13587 cursor_anchor,
13588 cursor_position,
13589 scroll_anchor: scroll_state,
13590 scroll_top_row,
13591 }),
13592 cx,
13593 );
13594 cx.emit(EditorEvent::PushedToNavHistory {
13595 anchor: cursor_anchor,
13596 is_deactivate,
13597 })
13598 }
13599 }
13600
13601 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13602 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13603 let buffer = self.buffer.read(cx).snapshot(cx);
13604 let mut selection = self.selections.first::<usize>(cx);
13605 selection.set_head(buffer.len(), SelectionGoal::None);
13606 self.change_selections(Default::default(), window, cx, |s| {
13607 s.select(vec![selection]);
13608 });
13609 }
13610
13611 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13612 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13613 let end = self.buffer.read(cx).read(cx).len();
13614 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13615 s.select_ranges(vec![0..end]);
13616 });
13617 }
13618
13619 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13620 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13621 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13622 let mut selections = self.selections.all::<Point>(cx);
13623 let max_point = display_map.buffer_snapshot.max_point();
13624 for selection in &mut selections {
13625 let rows = selection.spanned_rows(true, &display_map);
13626 selection.start = Point::new(rows.start.0, 0);
13627 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13628 selection.reversed = false;
13629 }
13630 self.change_selections(Default::default(), window, cx, |s| {
13631 s.select(selections);
13632 });
13633 }
13634
13635 pub fn split_selection_into_lines(
13636 &mut self,
13637 action: &SplitSelectionIntoLines,
13638 window: &mut Window,
13639 cx: &mut Context<Self>,
13640 ) {
13641 let selections = self
13642 .selections
13643 .all::<Point>(cx)
13644 .into_iter()
13645 .map(|selection| selection.start..selection.end)
13646 .collect::<Vec<_>>();
13647 self.unfold_ranges(&selections, true, true, cx);
13648
13649 let mut new_selection_ranges = Vec::new();
13650 {
13651 let buffer = self.buffer.read(cx).read(cx);
13652 for selection in selections {
13653 for row in selection.start.row..selection.end.row {
13654 let line_start = Point::new(row, 0);
13655 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13656
13657 if action.keep_selections {
13658 // Keep the selection range for each line
13659 let selection_start = if row == selection.start.row {
13660 selection.start
13661 } else {
13662 line_start
13663 };
13664 new_selection_ranges.push(selection_start..line_end);
13665 } else {
13666 // Collapse to cursor at end of line
13667 new_selection_ranges.push(line_end..line_end);
13668 }
13669 }
13670
13671 let is_multiline_selection = selection.start.row != selection.end.row;
13672 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13673 // so this action feels more ergonomic when paired with other selection operations
13674 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13675 if !should_skip_last {
13676 if action.keep_selections {
13677 if is_multiline_selection {
13678 let line_start = Point::new(selection.end.row, 0);
13679 new_selection_ranges.push(line_start..selection.end);
13680 } else {
13681 new_selection_ranges.push(selection.start..selection.end);
13682 }
13683 } else {
13684 new_selection_ranges.push(selection.end..selection.end);
13685 }
13686 }
13687 }
13688 }
13689 self.change_selections(Default::default(), window, cx, |s| {
13690 s.select_ranges(new_selection_ranges);
13691 });
13692 }
13693
13694 pub fn add_selection_above(
13695 &mut self,
13696 _: &AddSelectionAbove,
13697 window: &mut Window,
13698 cx: &mut Context<Self>,
13699 ) {
13700 self.add_selection(true, window, cx);
13701 }
13702
13703 pub fn add_selection_below(
13704 &mut self,
13705 _: &AddSelectionBelow,
13706 window: &mut Window,
13707 cx: &mut Context<Self>,
13708 ) {
13709 self.add_selection(false, window, cx);
13710 }
13711
13712 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13713 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13714
13715 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13716 let all_selections = self.selections.all::<Point>(cx);
13717 let text_layout_details = self.text_layout_details(window);
13718
13719 let (mut columnar_selections, new_selections_to_columnarize) = {
13720 if let Some(state) = self.add_selections_state.as_ref() {
13721 let columnar_selection_ids: HashSet<_> = state
13722 .groups
13723 .iter()
13724 .flat_map(|group| group.stack.iter())
13725 .copied()
13726 .collect();
13727
13728 all_selections
13729 .into_iter()
13730 .partition(|s| columnar_selection_ids.contains(&s.id))
13731 } else {
13732 (Vec::new(), all_selections)
13733 }
13734 };
13735
13736 let mut state = self
13737 .add_selections_state
13738 .take()
13739 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13740
13741 for selection in new_selections_to_columnarize {
13742 let range = selection.display_range(&display_map).sorted();
13743 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13744 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13745 let positions = start_x.min(end_x)..start_x.max(end_x);
13746 let mut stack = Vec::new();
13747 for row in range.start.row().0..=range.end.row().0 {
13748 if let Some(selection) = self.selections.build_columnar_selection(
13749 &display_map,
13750 DisplayRow(row),
13751 &positions,
13752 selection.reversed,
13753 &text_layout_details,
13754 ) {
13755 stack.push(selection.id);
13756 columnar_selections.push(selection);
13757 }
13758 }
13759 if !stack.is_empty() {
13760 if above {
13761 stack.reverse();
13762 }
13763 state.groups.push(AddSelectionsGroup { above, stack });
13764 }
13765 }
13766
13767 let mut final_selections = Vec::new();
13768 let end_row = if above {
13769 DisplayRow(0)
13770 } else {
13771 display_map.max_point().row()
13772 };
13773
13774 let mut last_added_item_per_group = HashMap::default();
13775 for group in state.groups.iter_mut() {
13776 if let Some(last_id) = group.stack.last() {
13777 last_added_item_per_group.insert(*last_id, group);
13778 }
13779 }
13780
13781 for selection in columnar_selections {
13782 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13783 if above == group.above {
13784 let range = selection.display_range(&display_map).sorted();
13785 debug_assert_eq!(range.start.row(), range.end.row());
13786 let mut row = range.start.row();
13787 let positions =
13788 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13789 px(start)..px(end)
13790 } else {
13791 let start_x =
13792 display_map.x_for_display_point(range.start, &text_layout_details);
13793 let end_x =
13794 display_map.x_for_display_point(range.end, &text_layout_details);
13795 start_x.min(end_x)..start_x.max(end_x)
13796 };
13797
13798 let mut maybe_new_selection = None;
13799 while row != end_row {
13800 if above {
13801 row.0 -= 1;
13802 } else {
13803 row.0 += 1;
13804 }
13805 if let Some(new_selection) = self.selections.build_columnar_selection(
13806 &display_map,
13807 row,
13808 &positions,
13809 selection.reversed,
13810 &text_layout_details,
13811 ) {
13812 maybe_new_selection = Some(new_selection);
13813 break;
13814 }
13815 }
13816
13817 if let Some(new_selection) = maybe_new_selection {
13818 group.stack.push(new_selection.id);
13819 if above {
13820 final_selections.push(new_selection);
13821 final_selections.push(selection);
13822 } else {
13823 final_selections.push(selection);
13824 final_selections.push(new_selection);
13825 }
13826 } else {
13827 final_selections.push(selection);
13828 }
13829 } else {
13830 group.stack.pop();
13831 }
13832 } else {
13833 final_selections.push(selection);
13834 }
13835 }
13836
13837 self.change_selections(Default::default(), window, cx, |s| {
13838 s.select(final_selections);
13839 });
13840
13841 let final_selection_ids: HashSet<_> = self
13842 .selections
13843 .all::<Point>(cx)
13844 .iter()
13845 .map(|s| s.id)
13846 .collect();
13847 state.groups.retain_mut(|group| {
13848 // selections might get merged above so we remove invalid items from stacks
13849 group.stack.retain(|id| final_selection_ids.contains(id));
13850
13851 // single selection in stack can be treated as initial state
13852 group.stack.len() > 1
13853 });
13854
13855 if !state.groups.is_empty() {
13856 self.add_selections_state = Some(state);
13857 }
13858 }
13859
13860 fn select_match_ranges(
13861 &mut self,
13862 range: Range<usize>,
13863 reversed: bool,
13864 replace_newest: bool,
13865 auto_scroll: Option<Autoscroll>,
13866 window: &mut Window,
13867 cx: &mut Context<Editor>,
13868 ) {
13869 self.unfold_ranges(
13870 std::slice::from_ref(&range),
13871 false,
13872 auto_scroll.is_some(),
13873 cx,
13874 );
13875 let effects = if let Some(scroll) = auto_scroll {
13876 SelectionEffects::scroll(scroll)
13877 } else {
13878 SelectionEffects::no_scroll()
13879 };
13880 self.change_selections(effects, window, cx, |s| {
13881 if replace_newest {
13882 s.delete(s.newest_anchor().id);
13883 }
13884 if reversed {
13885 s.insert_range(range.end..range.start);
13886 } else {
13887 s.insert_range(range);
13888 }
13889 });
13890 }
13891
13892 pub fn select_next_match_internal(
13893 &mut self,
13894 display_map: &DisplaySnapshot,
13895 replace_newest: bool,
13896 autoscroll: Option<Autoscroll>,
13897 window: &mut Window,
13898 cx: &mut Context<Self>,
13899 ) -> Result<()> {
13900 let buffer = &display_map.buffer_snapshot;
13901 let mut selections = self.selections.all::<usize>(cx);
13902 if let Some(mut select_next_state) = self.select_next_state.take() {
13903 let query = &select_next_state.query;
13904 if !select_next_state.done {
13905 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13906 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13907 let mut next_selected_range = None;
13908
13909 let bytes_after_last_selection =
13910 buffer.bytes_in_range(last_selection.end..buffer.len());
13911 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13912 let query_matches = query
13913 .stream_find_iter(bytes_after_last_selection)
13914 .map(|result| (last_selection.end, result))
13915 .chain(
13916 query
13917 .stream_find_iter(bytes_before_first_selection)
13918 .map(|result| (0, result)),
13919 );
13920
13921 for (start_offset, query_match) in query_matches {
13922 let query_match = query_match.unwrap(); // can only fail due to I/O
13923 let offset_range =
13924 start_offset + query_match.start()..start_offset + query_match.end();
13925
13926 if !select_next_state.wordwise
13927 || (!buffer.is_inside_word(offset_range.start, false)
13928 && !buffer.is_inside_word(offset_range.end, false))
13929 {
13930 // TODO: This is n^2, because we might check all the selections
13931 if !selections
13932 .iter()
13933 .any(|selection| selection.range().overlaps(&offset_range))
13934 {
13935 next_selected_range = Some(offset_range);
13936 break;
13937 }
13938 }
13939 }
13940
13941 if let Some(next_selected_range) = next_selected_range {
13942 self.select_match_ranges(
13943 next_selected_range,
13944 last_selection.reversed,
13945 replace_newest,
13946 autoscroll,
13947 window,
13948 cx,
13949 );
13950 } else {
13951 select_next_state.done = true;
13952 }
13953 }
13954
13955 self.select_next_state = Some(select_next_state);
13956 } else {
13957 let mut only_carets = true;
13958 let mut same_text_selected = true;
13959 let mut selected_text = None;
13960
13961 let mut selections_iter = selections.iter().peekable();
13962 while let Some(selection) = selections_iter.next() {
13963 if selection.start != selection.end {
13964 only_carets = false;
13965 }
13966
13967 if same_text_selected {
13968 if selected_text.is_none() {
13969 selected_text =
13970 Some(buffer.text_for_range(selection.range()).collect::<String>());
13971 }
13972
13973 if let Some(next_selection) = selections_iter.peek() {
13974 if next_selection.range().len() == selection.range().len() {
13975 let next_selected_text = buffer
13976 .text_for_range(next_selection.range())
13977 .collect::<String>();
13978 if Some(next_selected_text) != selected_text {
13979 same_text_selected = false;
13980 selected_text = None;
13981 }
13982 } else {
13983 same_text_selected = false;
13984 selected_text = None;
13985 }
13986 }
13987 }
13988 }
13989
13990 if only_carets {
13991 for selection in &mut selections {
13992 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13993 selection.start = word_range.start;
13994 selection.end = word_range.end;
13995 selection.goal = SelectionGoal::None;
13996 selection.reversed = false;
13997 self.select_match_ranges(
13998 selection.start..selection.end,
13999 selection.reversed,
14000 replace_newest,
14001 autoscroll,
14002 window,
14003 cx,
14004 );
14005 }
14006
14007 if selections.len() == 1 {
14008 let selection = selections
14009 .last()
14010 .expect("ensured that there's only one selection");
14011 let query = buffer
14012 .text_for_range(selection.start..selection.end)
14013 .collect::<String>();
14014 let is_empty = query.is_empty();
14015 let select_state = SelectNextState {
14016 query: AhoCorasick::new(&[query])?,
14017 wordwise: true,
14018 done: is_empty,
14019 };
14020 self.select_next_state = Some(select_state);
14021 } else {
14022 self.select_next_state = None;
14023 }
14024 } else if let Some(selected_text) = selected_text {
14025 self.select_next_state = Some(SelectNextState {
14026 query: AhoCorasick::new(&[selected_text])?,
14027 wordwise: false,
14028 done: false,
14029 });
14030 self.select_next_match_internal(
14031 display_map,
14032 replace_newest,
14033 autoscroll,
14034 window,
14035 cx,
14036 )?;
14037 }
14038 }
14039 Ok(())
14040 }
14041
14042 pub fn select_all_matches(
14043 &mut self,
14044 _action: &SelectAllMatches,
14045 window: &mut Window,
14046 cx: &mut Context<Self>,
14047 ) -> Result<()> {
14048 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14049
14050 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14051
14052 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14053 let Some(select_next_state) = self.select_next_state.as_mut() else {
14054 return Ok(());
14055 };
14056 if select_next_state.done {
14057 return Ok(());
14058 }
14059
14060 let mut new_selections = Vec::new();
14061
14062 let reversed = self.selections.oldest::<usize>(cx).reversed;
14063 let buffer = &display_map.buffer_snapshot;
14064 let query_matches = select_next_state
14065 .query
14066 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14067
14068 for query_match in query_matches.into_iter() {
14069 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14070 let offset_range = if reversed {
14071 query_match.end()..query_match.start()
14072 } else {
14073 query_match.start()..query_match.end()
14074 };
14075
14076 if !select_next_state.wordwise
14077 || (!buffer.is_inside_word(offset_range.start, false)
14078 && !buffer.is_inside_word(offset_range.end, false))
14079 {
14080 new_selections.push(offset_range.start..offset_range.end);
14081 }
14082 }
14083
14084 select_next_state.done = true;
14085
14086 if new_selections.is_empty() {
14087 log::error!("bug: new_selections is empty in select_all_matches");
14088 return Ok(());
14089 }
14090
14091 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14092 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14093 selections.select_ranges(new_selections)
14094 });
14095
14096 Ok(())
14097 }
14098
14099 pub fn select_next(
14100 &mut self,
14101 action: &SelectNext,
14102 window: &mut Window,
14103 cx: &mut Context<Self>,
14104 ) -> Result<()> {
14105 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14106 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14107 self.select_next_match_internal(
14108 &display_map,
14109 action.replace_newest,
14110 Some(Autoscroll::newest()),
14111 window,
14112 cx,
14113 )?;
14114 Ok(())
14115 }
14116
14117 pub fn select_previous(
14118 &mut self,
14119 action: &SelectPrevious,
14120 window: &mut Window,
14121 cx: &mut Context<Self>,
14122 ) -> Result<()> {
14123 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14124 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14125 let buffer = &display_map.buffer_snapshot;
14126 let mut selections = self.selections.all::<usize>(cx);
14127 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14128 let query = &select_prev_state.query;
14129 if !select_prev_state.done {
14130 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14131 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14132 let mut next_selected_range = None;
14133 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14134 let bytes_before_last_selection =
14135 buffer.reversed_bytes_in_range(0..last_selection.start);
14136 let bytes_after_first_selection =
14137 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14138 let query_matches = query
14139 .stream_find_iter(bytes_before_last_selection)
14140 .map(|result| (last_selection.start, result))
14141 .chain(
14142 query
14143 .stream_find_iter(bytes_after_first_selection)
14144 .map(|result| (buffer.len(), result)),
14145 );
14146 for (end_offset, query_match) in query_matches {
14147 let query_match = query_match.unwrap(); // can only fail due to I/O
14148 let offset_range =
14149 end_offset - query_match.end()..end_offset - query_match.start();
14150
14151 if !select_prev_state.wordwise
14152 || (!buffer.is_inside_word(offset_range.start, false)
14153 && !buffer.is_inside_word(offset_range.end, false))
14154 {
14155 next_selected_range = Some(offset_range);
14156 break;
14157 }
14158 }
14159
14160 if let Some(next_selected_range) = next_selected_range {
14161 self.select_match_ranges(
14162 next_selected_range,
14163 last_selection.reversed,
14164 action.replace_newest,
14165 Some(Autoscroll::newest()),
14166 window,
14167 cx,
14168 );
14169 } else {
14170 select_prev_state.done = true;
14171 }
14172 }
14173
14174 self.select_prev_state = Some(select_prev_state);
14175 } else {
14176 let mut only_carets = true;
14177 let mut same_text_selected = true;
14178 let mut selected_text = None;
14179
14180 let mut selections_iter = selections.iter().peekable();
14181 while let Some(selection) = selections_iter.next() {
14182 if selection.start != selection.end {
14183 only_carets = false;
14184 }
14185
14186 if same_text_selected {
14187 if selected_text.is_none() {
14188 selected_text =
14189 Some(buffer.text_for_range(selection.range()).collect::<String>());
14190 }
14191
14192 if let Some(next_selection) = selections_iter.peek() {
14193 if next_selection.range().len() == selection.range().len() {
14194 let next_selected_text = buffer
14195 .text_for_range(next_selection.range())
14196 .collect::<String>();
14197 if Some(next_selected_text) != selected_text {
14198 same_text_selected = false;
14199 selected_text = None;
14200 }
14201 } else {
14202 same_text_selected = false;
14203 selected_text = None;
14204 }
14205 }
14206 }
14207 }
14208
14209 if only_carets {
14210 for selection in &mut selections {
14211 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14212 selection.start = word_range.start;
14213 selection.end = word_range.end;
14214 selection.goal = SelectionGoal::None;
14215 selection.reversed = false;
14216 self.select_match_ranges(
14217 selection.start..selection.end,
14218 selection.reversed,
14219 action.replace_newest,
14220 Some(Autoscroll::newest()),
14221 window,
14222 cx,
14223 );
14224 }
14225 if selections.len() == 1 {
14226 let selection = selections
14227 .last()
14228 .expect("ensured that there's only one selection");
14229 let query = buffer
14230 .text_for_range(selection.start..selection.end)
14231 .collect::<String>();
14232 let is_empty = query.is_empty();
14233 let select_state = SelectNextState {
14234 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14235 wordwise: true,
14236 done: is_empty,
14237 };
14238 self.select_prev_state = Some(select_state);
14239 } else {
14240 self.select_prev_state = None;
14241 }
14242 } else if let Some(selected_text) = selected_text {
14243 self.select_prev_state = Some(SelectNextState {
14244 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14245 wordwise: false,
14246 done: false,
14247 });
14248 self.select_previous(action, window, cx)?;
14249 }
14250 }
14251 Ok(())
14252 }
14253
14254 pub fn find_next_match(
14255 &mut self,
14256 _: &FindNextMatch,
14257 window: &mut Window,
14258 cx: &mut Context<Self>,
14259 ) -> Result<()> {
14260 let selections = self.selections.disjoint_anchors();
14261 match selections.first() {
14262 Some(first) if selections.len() >= 2 => {
14263 self.change_selections(Default::default(), window, cx, |s| {
14264 s.select_ranges([first.range()]);
14265 });
14266 }
14267 _ => self.select_next(
14268 &SelectNext {
14269 replace_newest: true,
14270 },
14271 window,
14272 cx,
14273 )?,
14274 }
14275 Ok(())
14276 }
14277
14278 pub fn find_previous_match(
14279 &mut self,
14280 _: &FindPreviousMatch,
14281 window: &mut Window,
14282 cx: &mut Context<Self>,
14283 ) -> Result<()> {
14284 let selections = self.selections.disjoint_anchors();
14285 match selections.last() {
14286 Some(last) if selections.len() >= 2 => {
14287 self.change_selections(Default::default(), window, cx, |s| {
14288 s.select_ranges([last.range()]);
14289 });
14290 }
14291 _ => self.select_previous(
14292 &SelectPrevious {
14293 replace_newest: true,
14294 },
14295 window,
14296 cx,
14297 )?,
14298 }
14299 Ok(())
14300 }
14301
14302 pub fn toggle_comments(
14303 &mut self,
14304 action: &ToggleComments,
14305 window: &mut Window,
14306 cx: &mut Context<Self>,
14307 ) {
14308 if self.read_only(cx) {
14309 return;
14310 }
14311 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14312 let text_layout_details = &self.text_layout_details(window);
14313 self.transact(window, cx, |this, window, cx| {
14314 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14315 let mut edits = Vec::new();
14316 let mut selection_edit_ranges = Vec::new();
14317 let mut last_toggled_row = None;
14318 let snapshot = this.buffer.read(cx).read(cx);
14319 let empty_str: Arc<str> = Arc::default();
14320 let mut suffixes_inserted = Vec::new();
14321 let ignore_indent = action.ignore_indent;
14322
14323 fn comment_prefix_range(
14324 snapshot: &MultiBufferSnapshot,
14325 row: MultiBufferRow,
14326 comment_prefix: &str,
14327 comment_prefix_whitespace: &str,
14328 ignore_indent: bool,
14329 ) -> Range<Point> {
14330 let indent_size = if ignore_indent {
14331 0
14332 } else {
14333 snapshot.indent_size_for_line(row).len
14334 };
14335
14336 let start = Point::new(row.0, indent_size);
14337
14338 let mut line_bytes = snapshot
14339 .bytes_in_range(start..snapshot.max_point())
14340 .flatten()
14341 .copied();
14342
14343 // If this line currently begins with the line comment prefix, then record
14344 // the range containing the prefix.
14345 if line_bytes
14346 .by_ref()
14347 .take(comment_prefix.len())
14348 .eq(comment_prefix.bytes())
14349 {
14350 // Include any whitespace that matches the comment prefix.
14351 let matching_whitespace_len = line_bytes
14352 .zip(comment_prefix_whitespace.bytes())
14353 .take_while(|(a, b)| a == b)
14354 .count() as u32;
14355 let end = Point::new(
14356 start.row,
14357 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14358 );
14359 start..end
14360 } else {
14361 start..start
14362 }
14363 }
14364
14365 fn comment_suffix_range(
14366 snapshot: &MultiBufferSnapshot,
14367 row: MultiBufferRow,
14368 comment_suffix: &str,
14369 comment_suffix_has_leading_space: bool,
14370 ) -> Range<Point> {
14371 let end = Point::new(row.0, snapshot.line_len(row));
14372 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14373
14374 let mut line_end_bytes = snapshot
14375 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14376 .flatten()
14377 .copied();
14378
14379 let leading_space_len = if suffix_start_column > 0
14380 && line_end_bytes.next() == Some(b' ')
14381 && comment_suffix_has_leading_space
14382 {
14383 1
14384 } else {
14385 0
14386 };
14387
14388 // If this line currently begins with the line comment prefix, then record
14389 // the range containing the prefix.
14390 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14391 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14392 start..end
14393 } else {
14394 end..end
14395 }
14396 }
14397
14398 // TODO: Handle selections that cross excerpts
14399 for selection in &mut selections {
14400 let start_column = snapshot
14401 .indent_size_for_line(MultiBufferRow(selection.start.row))
14402 .len;
14403 let language = if let Some(language) =
14404 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14405 {
14406 language
14407 } else {
14408 continue;
14409 };
14410
14411 selection_edit_ranges.clear();
14412
14413 // If multiple selections contain a given row, avoid processing that
14414 // row more than once.
14415 let mut start_row = MultiBufferRow(selection.start.row);
14416 if last_toggled_row == Some(start_row) {
14417 start_row = start_row.next_row();
14418 }
14419 let end_row =
14420 if selection.end.row > selection.start.row && selection.end.column == 0 {
14421 MultiBufferRow(selection.end.row - 1)
14422 } else {
14423 MultiBufferRow(selection.end.row)
14424 };
14425 last_toggled_row = Some(end_row);
14426
14427 if start_row > end_row {
14428 continue;
14429 }
14430
14431 // If the language has line comments, toggle those.
14432 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14433
14434 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14435 if ignore_indent {
14436 full_comment_prefixes = full_comment_prefixes
14437 .into_iter()
14438 .map(|s| Arc::from(s.trim_end()))
14439 .collect();
14440 }
14441
14442 if !full_comment_prefixes.is_empty() {
14443 let first_prefix = full_comment_prefixes
14444 .first()
14445 .expect("prefixes is non-empty");
14446 let prefix_trimmed_lengths = full_comment_prefixes
14447 .iter()
14448 .map(|p| p.trim_end_matches(' ').len())
14449 .collect::<SmallVec<[usize; 4]>>();
14450
14451 let mut all_selection_lines_are_comments = true;
14452
14453 for row in start_row.0..=end_row.0 {
14454 let row = MultiBufferRow(row);
14455 if start_row < end_row && snapshot.is_line_blank(row) {
14456 continue;
14457 }
14458
14459 let prefix_range = full_comment_prefixes
14460 .iter()
14461 .zip(prefix_trimmed_lengths.iter().copied())
14462 .map(|(prefix, trimmed_prefix_len)| {
14463 comment_prefix_range(
14464 snapshot.deref(),
14465 row,
14466 &prefix[..trimmed_prefix_len],
14467 &prefix[trimmed_prefix_len..],
14468 ignore_indent,
14469 )
14470 })
14471 .max_by_key(|range| range.end.column - range.start.column)
14472 .expect("prefixes is non-empty");
14473
14474 if prefix_range.is_empty() {
14475 all_selection_lines_are_comments = false;
14476 }
14477
14478 selection_edit_ranges.push(prefix_range);
14479 }
14480
14481 if all_selection_lines_are_comments {
14482 edits.extend(
14483 selection_edit_ranges
14484 .iter()
14485 .cloned()
14486 .map(|range| (range, empty_str.clone())),
14487 );
14488 } else {
14489 let min_column = selection_edit_ranges
14490 .iter()
14491 .map(|range| range.start.column)
14492 .min()
14493 .unwrap_or(0);
14494 edits.extend(selection_edit_ranges.iter().map(|range| {
14495 let position = Point::new(range.start.row, min_column);
14496 (position..position, first_prefix.clone())
14497 }));
14498 }
14499 } else if let Some(BlockCommentConfig {
14500 start: full_comment_prefix,
14501 end: comment_suffix,
14502 ..
14503 }) = language.block_comment()
14504 {
14505 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14506 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14507 let prefix_range = comment_prefix_range(
14508 snapshot.deref(),
14509 start_row,
14510 comment_prefix,
14511 comment_prefix_whitespace,
14512 ignore_indent,
14513 );
14514 let suffix_range = comment_suffix_range(
14515 snapshot.deref(),
14516 end_row,
14517 comment_suffix.trim_start_matches(' '),
14518 comment_suffix.starts_with(' '),
14519 );
14520
14521 if prefix_range.is_empty() || suffix_range.is_empty() {
14522 edits.push((
14523 prefix_range.start..prefix_range.start,
14524 full_comment_prefix.clone(),
14525 ));
14526 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14527 suffixes_inserted.push((end_row, comment_suffix.len()));
14528 } else {
14529 edits.push((prefix_range, empty_str.clone()));
14530 edits.push((suffix_range, empty_str.clone()));
14531 }
14532 } else {
14533 continue;
14534 }
14535 }
14536
14537 drop(snapshot);
14538 this.buffer.update(cx, |buffer, cx| {
14539 buffer.edit(edits, None, cx);
14540 });
14541
14542 // Adjust selections so that they end before any comment suffixes that
14543 // were inserted.
14544 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14545 let mut selections = this.selections.all::<Point>(cx);
14546 let snapshot = this.buffer.read(cx).read(cx);
14547 for selection in &mut selections {
14548 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14549 match row.cmp(&MultiBufferRow(selection.end.row)) {
14550 Ordering::Less => {
14551 suffixes_inserted.next();
14552 continue;
14553 }
14554 Ordering::Greater => break,
14555 Ordering::Equal => {
14556 if selection.end.column == snapshot.line_len(row) {
14557 if selection.is_empty() {
14558 selection.start.column -= suffix_len as u32;
14559 }
14560 selection.end.column -= suffix_len as u32;
14561 }
14562 break;
14563 }
14564 }
14565 }
14566 }
14567
14568 drop(snapshot);
14569 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14570
14571 let selections = this.selections.all::<Point>(cx);
14572 let selections_on_single_row = selections.windows(2).all(|selections| {
14573 selections[0].start.row == selections[1].start.row
14574 && selections[0].end.row == selections[1].end.row
14575 && selections[0].start.row == selections[0].end.row
14576 });
14577 let selections_selecting = selections
14578 .iter()
14579 .any(|selection| selection.start != selection.end);
14580 let advance_downwards = action.advance_downwards
14581 && selections_on_single_row
14582 && !selections_selecting
14583 && !matches!(this.mode, EditorMode::SingleLine { .. });
14584
14585 if advance_downwards {
14586 let snapshot = this.buffer.read(cx).snapshot(cx);
14587
14588 this.change_selections(Default::default(), window, cx, |s| {
14589 s.move_cursors_with(|display_snapshot, display_point, _| {
14590 let mut point = display_point.to_point(display_snapshot);
14591 point.row += 1;
14592 point = snapshot.clip_point(point, Bias::Left);
14593 let display_point = point.to_display_point(display_snapshot);
14594 let goal = SelectionGoal::HorizontalPosition(
14595 display_snapshot
14596 .x_for_display_point(display_point, text_layout_details)
14597 .into(),
14598 );
14599 (display_point, goal)
14600 })
14601 });
14602 }
14603 });
14604 }
14605
14606 pub fn select_enclosing_symbol(
14607 &mut self,
14608 _: &SelectEnclosingSymbol,
14609 window: &mut Window,
14610 cx: &mut Context<Self>,
14611 ) {
14612 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14613
14614 let buffer = self.buffer.read(cx).snapshot(cx);
14615 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14616
14617 fn update_selection(
14618 selection: &Selection<usize>,
14619 buffer_snap: &MultiBufferSnapshot,
14620 ) -> Option<Selection<usize>> {
14621 let cursor = selection.head();
14622 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14623 for symbol in symbols.iter().rev() {
14624 let start = symbol.range.start.to_offset(buffer_snap);
14625 let end = symbol.range.end.to_offset(buffer_snap);
14626 let new_range = start..end;
14627 if start < selection.start || end > selection.end {
14628 return Some(Selection {
14629 id: selection.id,
14630 start: new_range.start,
14631 end: new_range.end,
14632 goal: SelectionGoal::None,
14633 reversed: selection.reversed,
14634 });
14635 }
14636 }
14637 None
14638 }
14639
14640 let mut selected_larger_symbol = false;
14641 let new_selections = old_selections
14642 .iter()
14643 .map(|selection| match update_selection(selection, &buffer) {
14644 Some(new_selection) => {
14645 if new_selection.range() != selection.range() {
14646 selected_larger_symbol = true;
14647 }
14648 new_selection
14649 }
14650 None => selection.clone(),
14651 })
14652 .collect::<Vec<_>>();
14653
14654 if selected_larger_symbol {
14655 self.change_selections(Default::default(), window, cx, |s| {
14656 s.select(new_selections);
14657 });
14658 }
14659 }
14660
14661 pub fn select_larger_syntax_node(
14662 &mut self,
14663 _: &SelectLargerSyntaxNode,
14664 window: &mut Window,
14665 cx: &mut Context<Self>,
14666 ) {
14667 let Some(visible_row_count) = self.visible_row_count() else {
14668 return;
14669 };
14670 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14671 if old_selections.is_empty() {
14672 return;
14673 }
14674
14675 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14676
14677 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14678 let buffer = self.buffer.read(cx).snapshot(cx);
14679
14680 let mut selected_larger_node = false;
14681 let mut new_selections = old_selections
14682 .iter()
14683 .map(|selection| {
14684 let old_range = selection.start..selection.end;
14685
14686 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14687 // manually select word at selection
14688 if ["string_content", "inline"].contains(&node.kind()) {
14689 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14690 // ignore if word is already selected
14691 if !word_range.is_empty() && old_range != word_range {
14692 let (last_word_range, _) =
14693 buffer.surrounding_word(old_range.end, false);
14694 // only select word if start and end point belongs to same word
14695 if word_range == last_word_range {
14696 selected_larger_node = true;
14697 return Selection {
14698 id: selection.id,
14699 start: word_range.start,
14700 end: word_range.end,
14701 goal: SelectionGoal::None,
14702 reversed: selection.reversed,
14703 };
14704 }
14705 }
14706 }
14707 }
14708
14709 let mut new_range = old_range.clone();
14710 while let Some((_node, containing_range)) =
14711 buffer.syntax_ancestor(new_range.clone())
14712 {
14713 new_range = match containing_range {
14714 MultiOrSingleBufferOffsetRange::Single(_) => break,
14715 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14716 };
14717 if !display_map.intersects_fold(new_range.start)
14718 && !display_map.intersects_fold(new_range.end)
14719 {
14720 break;
14721 }
14722 }
14723
14724 selected_larger_node |= new_range != old_range;
14725 Selection {
14726 id: selection.id,
14727 start: new_range.start,
14728 end: new_range.end,
14729 goal: SelectionGoal::None,
14730 reversed: selection.reversed,
14731 }
14732 })
14733 .collect::<Vec<_>>();
14734
14735 if !selected_larger_node {
14736 return; // don't put this call in the history
14737 }
14738
14739 // scroll based on transformation done to the last selection created by the user
14740 let (last_old, last_new) = old_selections
14741 .last()
14742 .zip(new_selections.last().cloned())
14743 .expect("old_selections isn't empty");
14744
14745 // revert selection
14746 let is_selection_reversed = {
14747 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14748 new_selections.last_mut().expect("checked above").reversed =
14749 should_newest_selection_be_reversed;
14750 should_newest_selection_be_reversed
14751 };
14752
14753 if selected_larger_node {
14754 self.select_syntax_node_history.disable_clearing = true;
14755 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14756 s.select(new_selections.clone());
14757 });
14758 self.select_syntax_node_history.disable_clearing = false;
14759 }
14760
14761 let start_row = last_new.start.to_display_point(&display_map).row().0;
14762 let end_row = last_new.end.to_display_point(&display_map).row().0;
14763 let selection_height = end_row - start_row + 1;
14764 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14765
14766 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14767 let scroll_behavior = if fits_on_the_screen {
14768 self.request_autoscroll(Autoscroll::fit(), cx);
14769 SelectSyntaxNodeScrollBehavior::FitSelection
14770 } else if is_selection_reversed {
14771 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14772 SelectSyntaxNodeScrollBehavior::CursorTop
14773 } else {
14774 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14775 SelectSyntaxNodeScrollBehavior::CursorBottom
14776 };
14777
14778 self.select_syntax_node_history.push((
14779 old_selections,
14780 scroll_behavior,
14781 is_selection_reversed,
14782 ));
14783 }
14784
14785 pub fn select_smaller_syntax_node(
14786 &mut self,
14787 _: &SelectSmallerSyntaxNode,
14788 window: &mut Window,
14789 cx: &mut Context<Self>,
14790 ) {
14791 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14792
14793 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14794 self.select_syntax_node_history.pop()
14795 {
14796 if let Some(selection) = selections.last_mut() {
14797 selection.reversed = is_selection_reversed;
14798 }
14799
14800 self.select_syntax_node_history.disable_clearing = true;
14801 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14802 s.select(selections.to_vec());
14803 });
14804 self.select_syntax_node_history.disable_clearing = false;
14805
14806 match scroll_behavior {
14807 SelectSyntaxNodeScrollBehavior::CursorTop => {
14808 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14809 }
14810 SelectSyntaxNodeScrollBehavior::FitSelection => {
14811 self.request_autoscroll(Autoscroll::fit(), cx);
14812 }
14813 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14814 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14815 }
14816 }
14817 }
14818 }
14819
14820 pub fn unwrap_syntax_node(
14821 &mut self,
14822 _: &UnwrapSyntaxNode,
14823 window: &mut Window,
14824 cx: &mut Context<Self>,
14825 ) {
14826 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14827
14828 let buffer = self.buffer.read(cx).snapshot(cx);
14829 let selections = self
14830 .selections
14831 .all::<usize>(cx)
14832 .into_iter()
14833 // subtracting the offset requires sorting
14834 .sorted_by_key(|i| i.start);
14835
14836 let full_edits = selections
14837 .into_iter()
14838 .filter_map(|selection| {
14839 // Only requires two branches once if-let-chains stabilize (#53667)
14840 let child = if !selection.is_empty() {
14841 selection.range()
14842 } else if let Some((_, ancestor_range)) =
14843 buffer.syntax_ancestor(selection.start..selection.end)
14844 {
14845 match ancestor_range {
14846 MultiOrSingleBufferOffsetRange::Single(range) => range,
14847 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14848 }
14849 } else {
14850 selection.range()
14851 };
14852
14853 let mut parent = child.clone();
14854 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
14855 parent = match ancestor_range {
14856 MultiOrSingleBufferOffsetRange::Single(range) => range,
14857 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14858 };
14859 if parent.start < child.start || parent.end > child.end {
14860 break;
14861 }
14862 }
14863
14864 if parent == child {
14865 return None;
14866 }
14867 let text = buffer.text_for_range(child.clone()).collect::<String>();
14868 Some((selection.id, parent, text))
14869 })
14870 .collect::<Vec<_>>();
14871
14872 self.transact(window, cx, |this, window, cx| {
14873 this.buffer.update(cx, |buffer, cx| {
14874 buffer.edit(
14875 full_edits
14876 .iter()
14877 .map(|(_, p, t)| (p.clone(), t.clone()))
14878 .collect::<Vec<_>>(),
14879 None,
14880 cx,
14881 );
14882 });
14883 this.change_selections(Default::default(), window, cx, |s| {
14884 let mut offset = 0;
14885 let mut selections = vec![];
14886 for (id, parent, text) in full_edits {
14887 let start = parent.start - offset;
14888 offset += parent.len() - text.len();
14889 selections.push(Selection {
14890 id: id,
14891 start,
14892 end: start + text.len(),
14893 reversed: false,
14894 goal: Default::default(),
14895 });
14896 }
14897 s.select(selections);
14898 });
14899 });
14900 }
14901
14902 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14903 if !EditorSettings::get_global(cx).gutter.runnables {
14904 self.clear_tasks();
14905 return Task::ready(());
14906 }
14907 let project = self.project().map(Entity::downgrade);
14908 let task_sources = self.lsp_task_sources(cx);
14909 let multi_buffer = self.buffer.downgrade();
14910 cx.spawn_in(window, async move |editor, cx| {
14911 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14912 let Some(project) = project.and_then(|p| p.upgrade()) else {
14913 return;
14914 };
14915 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14916 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14917 }) else {
14918 return;
14919 };
14920
14921 let hide_runnables = project
14922 .update(cx, |project, cx| {
14923 // Do not display any test indicators in non-dev server remote projects.
14924 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14925 })
14926 .unwrap_or(true);
14927 if hide_runnables {
14928 return;
14929 }
14930 let new_rows =
14931 cx.background_spawn({
14932 let snapshot = display_snapshot.clone();
14933 async move {
14934 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14935 }
14936 })
14937 .await;
14938 let Ok(lsp_tasks) =
14939 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14940 else {
14941 return;
14942 };
14943 let lsp_tasks = lsp_tasks.await;
14944
14945 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14946 lsp_tasks
14947 .into_iter()
14948 .flat_map(|(kind, tasks)| {
14949 tasks.into_iter().filter_map(move |(location, task)| {
14950 Some((kind.clone(), location?, task))
14951 })
14952 })
14953 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14954 let buffer = location.target.buffer;
14955 let buffer_snapshot = buffer.read(cx).snapshot();
14956 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14957 |(excerpt_id, snapshot, _)| {
14958 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14959 display_snapshot
14960 .buffer_snapshot
14961 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14962 } else {
14963 None
14964 }
14965 },
14966 );
14967 if let Some(offset) = offset {
14968 let task_buffer_range =
14969 location.target.range.to_point(&buffer_snapshot);
14970 let context_buffer_range =
14971 task_buffer_range.to_offset(&buffer_snapshot);
14972 let context_range = BufferOffset(context_buffer_range.start)
14973 ..BufferOffset(context_buffer_range.end);
14974
14975 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14976 .or_insert_with(|| RunnableTasks {
14977 templates: Vec::new(),
14978 offset,
14979 column: task_buffer_range.start.column,
14980 extra_variables: HashMap::default(),
14981 context_range,
14982 })
14983 .templates
14984 .push((kind, task.original_task().clone()));
14985 }
14986
14987 acc
14988 })
14989 }) else {
14990 return;
14991 };
14992
14993 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14994 buffer.language_settings(cx).tasks.prefer_lsp
14995 }) else {
14996 return;
14997 };
14998
14999 let rows = Self::runnable_rows(
15000 project,
15001 display_snapshot,
15002 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15003 new_rows,
15004 cx.clone(),
15005 )
15006 .await;
15007 editor
15008 .update(cx, |editor, _| {
15009 editor.clear_tasks();
15010 for (key, mut value) in rows {
15011 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15012 value.templates.extend(lsp_tasks.templates);
15013 }
15014
15015 editor.insert_tasks(key, value);
15016 }
15017 for (key, value) in lsp_tasks_by_rows {
15018 editor.insert_tasks(key, value);
15019 }
15020 })
15021 .ok();
15022 })
15023 }
15024 fn fetch_runnable_ranges(
15025 snapshot: &DisplaySnapshot,
15026 range: Range<Anchor>,
15027 ) -> Vec<language::RunnableRange> {
15028 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15029 }
15030
15031 fn runnable_rows(
15032 project: Entity<Project>,
15033 snapshot: DisplaySnapshot,
15034 prefer_lsp: bool,
15035 runnable_ranges: Vec<RunnableRange>,
15036 cx: AsyncWindowContext,
15037 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15038 cx.spawn(async move |cx| {
15039 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15040 for mut runnable in runnable_ranges {
15041 let Some(tasks) = cx
15042 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15043 .ok()
15044 else {
15045 continue;
15046 };
15047 let mut tasks = tasks.await;
15048
15049 if prefer_lsp {
15050 tasks.retain(|(task_kind, _)| {
15051 !matches!(task_kind, TaskSourceKind::Language { .. })
15052 });
15053 }
15054 if tasks.is_empty() {
15055 continue;
15056 }
15057
15058 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15059 let Some(row) = snapshot
15060 .buffer_snapshot
15061 .buffer_line_for_row(MultiBufferRow(point.row))
15062 .map(|(_, range)| range.start.row)
15063 else {
15064 continue;
15065 };
15066
15067 let context_range =
15068 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15069 runnable_rows.push((
15070 (runnable.buffer_id, row),
15071 RunnableTasks {
15072 templates: tasks,
15073 offset: snapshot
15074 .buffer_snapshot
15075 .anchor_before(runnable.run_range.start),
15076 context_range,
15077 column: point.column,
15078 extra_variables: runnable.extra_captures,
15079 },
15080 ));
15081 }
15082 runnable_rows
15083 })
15084 }
15085
15086 fn templates_with_tags(
15087 project: &Entity<Project>,
15088 runnable: &mut Runnable,
15089 cx: &mut App,
15090 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15091 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15092 let (worktree_id, file) = project
15093 .buffer_for_id(runnable.buffer, cx)
15094 .and_then(|buffer| buffer.read(cx).file())
15095 .map(|file| (file.worktree_id(cx), file.clone()))
15096 .unzip();
15097
15098 (
15099 project.task_store().read(cx).task_inventory().cloned(),
15100 worktree_id,
15101 file,
15102 )
15103 });
15104
15105 let tags = mem::take(&mut runnable.tags);
15106 let language = runnable.language.clone();
15107 cx.spawn(async move |cx| {
15108 let mut templates_with_tags = Vec::new();
15109 if let Some(inventory) = inventory {
15110 for RunnableTag(tag) in tags {
15111 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15112 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15113 }) else {
15114 return templates_with_tags;
15115 };
15116 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15117 move |(_, template)| {
15118 template.tags.iter().any(|source_tag| source_tag == &tag)
15119 },
15120 ));
15121 }
15122 }
15123 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15124
15125 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15126 // Strongest source wins; if we have worktree tag binding, prefer that to
15127 // global and language bindings;
15128 // if we have a global binding, prefer that to language binding.
15129 let first_mismatch = templates_with_tags
15130 .iter()
15131 .position(|(tag_source, _)| tag_source != leading_tag_source);
15132 if let Some(index) = first_mismatch {
15133 templates_with_tags.truncate(index);
15134 }
15135 }
15136
15137 templates_with_tags
15138 })
15139 }
15140
15141 pub fn move_to_enclosing_bracket(
15142 &mut self,
15143 _: &MoveToEnclosingBracket,
15144 window: &mut Window,
15145 cx: &mut Context<Self>,
15146 ) {
15147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15148 self.change_selections(Default::default(), window, cx, |s| {
15149 s.move_offsets_with(|snapshot, selection| {
15150 let Some(enclosing_bracket_ranges) =
15151 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15152 else {
15153 return;
15154 };
15155
15156 let mut best_length = usize::MAX;
15157 let mut best_inside = false;
15158 let mut best_in_bracket_range = false;
15159 let mut best_destination = None;
15160 for (open, close) in enclosing_bracket_ranges {
15161 let close = close.to_inclusive();
15162 let length = close.end() - open.start;
15163 let inside = selection.start >= open.end && selection.end <= *close.start();
15164 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15165 || close.contains(&selection.head());
15166
15167 // If best is next to a bracket and current isn't, skip
15168 if !in_bracket_range && best_in_bracket_range {
15169 continue;
15170 }
15171
15172 // Prefer smaller lengths unless best is inside and current isn't
15173 if length > best_length && (best_inside || !inside) {
15174 continue;
15175 }
15176
15177 best_length = length;
15178 best_inside = inside;
15179 best_in_bracket_range = in_bracket_range;
15180 best_destination = Some(
15181 if close.contains(&selection.start) && close.contains(&selection.end) {
15182 if inside { open.end } else { open.start }
15183 } else if inside {
15184 *close.start()
15185 } else {
15186 *close.end()
15187 },
15188 );
15189 }
15190
15191 if let Some(destination) = best_destination {
15192 selection.collapse_to(destination, SelectionGoal::None);
15193 }
15194 })
15195 });
15196 }
15197
15198 pub fn undo_selection(
15199 &mut self,
15200 _: &UndoSelection,
15201 window: &mut Window,
15202 cx: &mut Context<Self>,
15203 ) {
15204 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15205 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15206 self.selection_history.mode = SelectionHistoryMode::Undoing;
15207 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15208 this.end_selection(window, cx);
15209 this.change_selections(
15210 SelectionEffects::scroll(Autoscroll::newest()),
15211 window,
15212 cx,
15213 |s| s.select_anchors(entry.selections.to_vec()),
15214 );
15215 });
15216 self.selection_history.mode = SelectionHistoryMode::Normal;
15217
15218 self.select_next_state = entry.select_next_state;
15219 self.select_prev_state = entry.select_prev_state;
15220 self.add_selections_state = entry.add_selections_state;
15221 }
15222 }
15223
15224 pub fn redo_selection(
15225 &mut self,
15226 _: &RedoSelection,
15227 window: &mut Window,
15228 cx: &mut Context<Self>,
15229 ) {
15230 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15231 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15232 self.selection_history.mode = SelectionHistoryMode::Redoing;
15233 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15234 this.end_selection(window, cx);
15235 this.change_selections(
15236 SelectionEffects::scroll(Autoscroll::newest()),
15237 window,
15238 cx,
15239 |s| s.select_anchors(entry.selections.to_vec()),
15240 );
15241 });
15242 self.selection_history.mode = SelectionHistoryMode::Normal;
15243
15244 self.select_next_state = entry.select_next_state;
15245 self.select_prev_state = entry.select_prev_state;
15246 self.add_selections_state = entry.add_selections_state;
15247 }
15248 }
15249
15250 pub fn expand_excerpts(
15251 &mut self,
15252 action: &ExpandExcerpts,
15253 _: &mut Window,
15254 cx: &mut Context<Self>,
15255 ) {
15256 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15257 }
15258
15259 pub fn expand_excerpts_down(
15260 &mut self,
15261 action: &ExpandExcerptsDown,
15262 _: &mut Window,
15263 cx: &mut Context<Self>,
15264 ) {
15265 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15266 }
15267
15268 pub fn expand_excerpts_up(
15269 &mut self,
15270 action: &ExpandExcerptsUp,
15271 _: &mut Window,
15272 cx: &mut Context<Self>,
15273 ) {
15274 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15275 }
15276
15277 pub fn expand_excerpts_for_direction(
15278 &mut self,
15279 lines: u32,
15280 direction: ExpandExcerptDirection,
15281
15282 cx: &mut Context<Self>,
15283 ) {
15284 let selections = self.selections.disjoint_anchors();
15285
15286 let lines = if lines == 0 {
15287 EditorSettings::get_global(cx).expand_excerpt_lines
15288 } else {
15289 lines
15290 };
15291
15292 self.buffer.update(cx, |buffer, cx| {
15293 let snapshot = buffer.snapshot(cx);
15294 let mut excerpt_ids = selections
15295 .iter()
15296 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15297 .collect::<Vec<_>>();
15298 excerpt_ids.sort();
15299 excerpt_ids.dedup();
15300 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15301 })
15302 }
15303
15304 pub fn expand_excerpt(
15305 &mut self,
15306 excerpt: ExcerptId,
15307 direction: ExpandExcerptDirection,
15308 window: &mut Window,
15309 cx: &mut Context<Self>,
15310 ) {
15311 let current_scroll_position = self.scroll_position(cx);
15312 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15313 let mut should_scroll_up = false;
15314
15315 if direction == ExpandExcerptDirection::Down {
15316 let multi_buffer = self.buffer.read(cx);
15317 let snapshot = multi_buffer.snapshot(cx);
15318 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15319 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15320 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15321 {
15322 let buffer_snapshot = buffer.read(cx).snapshot();
15323 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15324 let last_row = buffer_snapshot.max_point().row;
15325 let lines_below = last_row.saturating_sub(excerpt_end_row);
15326 should_scroll_up = lines_below >= lines_to_expand;
15327 }
15328 }
15329
15330 self.buffer.update(cx, |buffer, cx| {
15331 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15332 });
15333
15334 if should_scroll_up {
15335 let new_scroll_position =
15336 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15337 self.set_scroll_position(new_scroll_position, window, cx);
15338 }
15339 }
15340
15341 pub fn go_to_singleton_buffer_point(
15342 &mut self,
15343 point: Point,
15344 window: &mut Window,
15345 cx: &mut Context<Self>,
15346 ) {
15347 self.go_to_singleton_buffer_range(point..point, window, cx);
15348 }
15349
15350 pub fn go_to_singleton_buffer_range(
15351 &mut self,
15352 range: Range<Point>,
15353 window: &mut Window,
15354 cx: &mut Context<Self>,
15355 ) {
15356 let multibuffer = self.buffer().read(cx);
15357 let Some(buffer) = multibuffer.as_singleton() else {
15358 return;
15359 };
15360 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15361 return;
15362 };
15363 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15364 return;
15365 };
15366 self.change_selections(
15367 SelectionEffects::default().nav_history(true),
15368 window,
15369 cx,
15370 |s| s.select_anchor_ranges([start..end]),
15371 );
15372 }
15373
15374 pub fn go_to_diagnostic(
15375 &mut self,
15376 action: &GoToDiagnostic,
15377 window: &mut Window,
15378 cx: &mut Context<Self>,
15379 ) {
15380 if !self.diagnostics_enabled() {
15381 return;
15382 }
15383 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15384 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15385 }
15386
15387 pub fn go_to_prev_diagnostic(
15388 &mut self,
15389 action: &GoToPreviousDiagnostic,
15390 window: &mut Window,
15391 cx: &mut Context<Self>,
15392 ) {
15393 if !self.diagnostics_enabled() {
15394 return;
15395 }
15396 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15397 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15398 }
15399
15400 pub fn go_to_diagnostic_impl(
15401 &mut self,
15402 direction: Direction,
15403 severity: GoToDiagnosticSeverityFilter,
15404 window: &mut Window,
15405 cx: &mut Context<Self>,
15406 ) {
15407 let buffer = self.buffer.read(cx).snapshot(cx);
15408 let selection = self.selections.newest::<usize>(cx);
15409
15410 let mut active_group_id = None;
15411 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15412 && active_group.active_range.start.to_offset(&buffer) == selection.start
15413 {
15414 active_group_id = Some(active_group.group_id);
15415 }
15416
15417 fn filtered(
15418 snapshot: EditorSnapshot,
15419 severity: GoToDiagnosticSeverityFilter,
15420 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15421 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15422 diagnostics
15423 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15424 .filter(|entry| entry.range.start != entry.range.end)
15425 .filter(|entry| !entry.diagnostic.is_unnecessary)
15426 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15427 }
15428
15429 let snapshot = self.snapshot(window, cx);
15430 let before = filtered(
15431 snapshot.clone(),
15432 severity,
15433 buffer
15434 .diagnostics_in_range(0..selection.start)
15435 .filter(|entry| entry.range.start <= selection.start),
15436 );
15437 let after = filtered(
15438 snapshot,
15439 severity,
15440 buffer
15441 .diagnostics_in_range(selection.start..buffer.len())
15442 .filter(|entry| entry.range.start >= selection.start),
15443 );
15444
15445 let mut found: Option<DiagnosticEntry<usize>> = None;
15446 if direction == Direction::Prev {
15447 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15448 {
15449 for diagnostic in prev_diagnostics.into_iter().rev() {
15450 if diagnostic.range.start != selection.start
15451 || active_group_id
15452 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15453 {
15454 found = Some(diagnostic);
15455 break 'outer;
15456 }
15457 }
15458 }
15459 } else {
15460 for diagnostic in after.chain(before) {
15461 if diagnostic.range.start != selection.start
15462 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15463 {
15464 found = Some(diagnostic);
15465 break;
15466 }
15467 }
15468 }
15469 let Some(next_diagnostic) = found else {
15470 return;
15471 };
15472
15473 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15474 return;
15475 };
15476 self.change_selections(Default::default(), window, cx, |s| {
15477 s.select_ranges(vec![
15478 next_diagnostic.range.start..next_diagnostic.range.start,
15479 ])
15480 });
15481 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15482 self.refresh_edit_prediction(false, true, window, cx);
15483 }
15484
15485 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15486 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15487 let snapshot = self.snapshot(window, cx);
15488 let selection = self.selections.newest::<Point>(cx);
15489 self.go_to_hunk_before_or_after_position(
15490 &snapshot,
15491 selection.head(),
15492 Direction::Next,
15493 window,
15494 cx,
15495 );
15496 }
15497
15498 pub fn go_to_hunk_before_or_after_position(
15499 &mut self,
15500 snapshot: &EditorSnapshot,
15501 position: Point,
15502 direction: Direction,
15503 window: &mut Window,
15504 cx: &mut Context<Editor>,
15505 ) {
15506 let row = if direction == Direction::Next {
15507 self.hunk_after_position(snapshot, position)
15508 .map(|hunk| hunk.row_range.start)
15509 } else {
15510 self.hunk_before_position(snapshot, position)
15511 };
15512
15513 if let Some(row) = row {
15514 let destination = Point::new(row.0, 0);
15515 let autoscroll = Autoscroll::center();
15516
15517 self.unfold_ranges(&[destination..destination], false, false, cx);
15518 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15519 s.select_ranges([destination..destination]);
15520 });
15521 }
15522 }
15523
15524 fn hunk_after_position(
15525 &mut self,
15526 snapshot: &EditorSnapshot,
15527 position: Point,
15528 ) -> Option<MultiBufferDiffHunk> {
15529 snapshot
15530 .buffer_snapshot
15531 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15532 .find(|hunk| hunk.row_range.start.0 > position.row)
15533 .or_else(|| {
15534 snapshot
15535 .buffer_snapshot
15536 .diff_hunks_in_range(Point::zero()..position)
15537 .find(|hunk| hunk.row_range.end.0 < position.row)
15538 })
15539 }
15540
15541 fn go_to_prev_hunk(
15542 &mut self,
15543 _: &GoToPreviousHunk,
15544 window: &mut Window,
15545 cx: &mut Context<Self>,
15546 ) {
15547 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15548 let snapshot = self.snapshot(window, cx);
15549 let selection = self.selections.newest::<Point>(cx);
15550 self.go_to_hunk_before_or_after_position(
15551 &snapshot,
15552 selection.head(),
15553 Direction::Prev,
15554 window,
15555 cx,
15556 );
15557 }
15558
15559 fn hunk_before_position(
15560 &mut self,
15561 snapshot: &EditorSnapshot,
15562 position: Point,
15563 ) -> Option<MultiBufferRow> {
15564 snapshot
15565 .buffer_snapshot
15566 .diff_hunk_before(position)
15567 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15568 }
15569
15570 fn go_to_next_change(
15571 &mut self,
15572 _: &GoToNextChange,
15573 window: &mut Window,
15574 cx: &mut Context<Self>,
15575 ) {
15576 if let Some(selections) = self
15577 .change_list
15578 .next_change(1, Direction::Next)
15579 .map(|s| s.to_vec())
15580 {
15581 self.change_selections(Default::default(), window, cx, |s| {
15582 let map = s.display_map();
15583 s.select_display_ranges(selections.iter().map(|a| {
15584 let point = a.to_display_point(&map);
15585 point..point
15586 }))
15587 })
15588 }
15589 }
15590
15591 fn go_to_previous_change(
15592 &mut self,
15593 _: &GoToPreviousChange,
15594 window: &mut Window,
15595 cx: &mut Context<Self>,
15596 ) {
15597 if let Some(selections) = self
15598 .change_list
15599 .next_change(1, Direction::Prev)
15600 .map(|s| s.to_vec())
15601 {
15602 self.change_selections(Default::default(), window, cx, |s| {
15603 let map = s.display_map();
15604 s.select_display_ranges(selections.iter().map(|a| {
15605 let point = a.to_display_point(&map);
15606 point..point
15607 }))
15608 })
15609 }
15610 }
15611
15612 fn go_to_line<T: 'static>(
15613 &mut self,
15614 position: Anchor,
15615 highlight_color: Option<Hsla>,
15616 window: &mut Window,
15617 cx: &mut Context<Self>,
15618 ) {
15619 let snapshot = self.snapshot(window, cx).display_snapshot;
15620 let position = position.to_point(&snapshot.buffer_snapshot);
15621 let start = snapshot
15622 .buffer_snapshot
15623 .clip_point(Point::new(position.row, 0), Bias::Left);
15624 let end = start + Point::new(1, 0);
15625 let start = snapshot.buffer_snapshot.anchor_before(start);
15626 let end = snapshot.buffer_snapshot.anchor_before(end);
15627
15628 self.highlight_rows::<T>(
15629 start..end,
15630 highlight_color
15631 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15632 Default::default(),
15633 cx,
15634 );
15635
15636 if self.buffer.read(cx).is_singleton() {
15637 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15638 }
15639 }
15640
15641 pub fn go_to_definition(
15642 &mut self,
15643 _: &GoToDefinition,
15644 window: &mut Window,
15645 cx: &mut Context<Self>,
15646 ) -> Task<Result<Navigated>> {
15647 let definition =
15648 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15649 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15650 cx.spawn_in(window, async move |editor, cx| {
15651 if definition.await? == Navigated::Yes {
15652 return Ok(Navigated::Yes);
15653 }
15654 match fallback_strategy {
15655 GoToDefinitionFallback::None => Ok(Navigated::No),
15656 GoToDefinitionFallback::FindAllReferences => {
15657 match editor.update_in(cx, |editor, window, cx| {
15658 editor.find_all_references(&FindAllReferences, window, cx)
15659 })? {
15660 Some(references) => references.await,
15661 None => Ok(Navigated::No),
15662 }
15663 }
15664 }
15665 })
15666 }
15667
15668 pub fn go_to_declaration(
15669 &mut self,
15670 _: &GoToDeclaration,
15671 window: &mut Window,
15672 cx: &mut Context<Self>,
15673 ) -> Task<Result<Navigated>> {
15674 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15675 }
15676
15677 pub fn go_to_declaration_split(
15678 &mut self,
15679 _: &GoToDeclaration,
15680 window: &mut Window,
15681 cx: &mut Context<Self>,
15682 ) -> Task<Result<Navigated>> {
15683 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15684 }
15685
15686 pub fn go_to_implementation(
15687 &mut self,
15688 _: &GoToImplementation,
15689 window: &mut Window,
15690 cx: &mut Context<Self>,
15691 ) -> Task<Result<Navigated>> {
15692 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15693 }
15694
15695 pub fn go_to_implementation_split(
15696 &mut self,
15697 _: &GoToImplementationSplit,
15698 window: &mut Window,
15699 cx: &mut Context<Self>,
15700 ) -> Task<Result<Navigated>> {
15701 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15702 }
15703
15704 pub fn go_to_type_definition(
15705 &mut self,
15706 _: &GoToTypeDefinition,
15707 window: &mut Window,
15708 cx: &mut Context<Self>,
15709 ) -> Task<Result<Navigated>> {
15710 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15711 }
15712
15713 pub fn go_to_definition_split(
15714 &mut self,
15715 _: &GoToDefinitionSplit,
15716 window: &mut Window,
15717 cx: &mut Context<Self>,
15718 ) -> Task<Result<Navigated>> {
15719 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15720 }
15721
15722 pub fn go_to_type_definition_split(
15723 &mut self,
15724 _: &GoToTypeDefinitionSplit,
15725 window: &mut Window,
15726 cx: &mut Context<Self>,
15727 ) -> Task<Result<Navigated>> {
15728 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15729 }
15730
15731 fn go_to_definition_of_kind(
15732 &mut self,
15733 kind: GotoDefinitionKind,
15734 split: bool,
15735 window: &mut Window,
15736 cx: &mut Context<Self>,
15737 ) -> Task<Result<Navigated>> {
15738 let Some(provider) = self.semantics_provider.clone() else {
15739 return Task::ready(Ok(Navigated::No));
15740 };
15741 let head = self.selections.newest::<usize>(cx).head();
15742 let buffer = self.buffer.read(cx);
15743 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15744 return Task::ready(Ok(Navigated::No));
15745 };
15746 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15747 return Task::ready(Ok(Navigated::No));
15748 };
15749
15750 cx.spawn_in(window, async move |editor, cx| {
15751 let definitions = definitions.await?;
15752 let navigated = editor
15753 .update_in(cx, |editor, window, cx| {
15754 editor.navigate_to_hover_links(
15755 Some(kind),
15756 definitions
15757 .into_iter()
15758 .filter(|location| {
15759 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15760 })
15761 .map(HoverLink::Text)
15762 .collect::<Vec<_>>(),
15763 split,
15764 window,
15765 cx,
15766 )
15767 })?
15768 .await?;
15769 anyhow::Ok(navigated)
15770 })
15771 }
15772
15773 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15774 let selection = self.selections.newest_anchor();
15775 let head = selection.head();
15776 let tail = selection.tail();
15777
15778 let Some((buffer, start_position)) =
15779 self.buffer.read(cx).text_anchor_for_position(head, cx)
15780 else {
15781 return;
15782 };
15783
15784 let end_position = if head != tail {
15785 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15786 return;
15787 };
15788 Some(pos)
15789 } else {
15790 None
15791 };
15792
15793 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15794 let url = if let Some(end_pos) = end_position {
15795 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15796 } else {
15797 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15798 };
15799
15800 if let Some(url) = url {
15801 editor.update(cx, |_, cx| {
15802 cx.open_url(&url);
15803 })
15804 } else {
15805 Ok(())
15806 }
15807 });
15808
15809 url_finder.detach();
15810 }
15811
15812 pub fn open_selected_filename(
15813 &mut self,
15814 _: &OpenSelectedFilename,
15815 window: &mut Window,
15816 cx: &mut Context<Self>,
15817 ) {
15818 let Some(workspace) = self.workspace() else {
15819 return;
15820 };
15821
15822 let position = self.selections.newest_anchor().head();
15823
15824 let Some((buffer, buffer_position)) =
15825 self.buffer.read(cx).text_anchor_for_position(position, cx)
15826 else {
15827 return;
15828 };
15829
15830 let project = self.project.clone();
15831
15832 cx.spawn_in(window, async move |_, cx| {
15833 let result = find_file(&buffer, project, buffer_position, cx).await;
15834
15835 if let Some((_, path)) = result {
15836 workspace
15837 .update_in(cx, |workspace, window, cx| {
15838 workspace.open_resolved_path(path, window, cx)
15839 })?
15840 .await?;
15841 }
15842 anyhow::Ok(())
15843 })
15844 .detach();
15845 }
15846
15847 pub(crate) fn navigate_to_hover_links(
15848 &mut self,
15849 kind: Option<GotoDefinitionKind>,
15850 definitions: Vec<HoverLink>,
15851 split: bool,
15852 window: &mut Window,
15853 cx: &mut Context<Editor>,
15854 ) -> Task<Result<Navigated>> {
15855 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15856 let mut first_url_or_file = None;
15857 let definitions: Vec<_> = definitions
15858 .into_iter()
15859 .filter_map(|def| match def {
15860 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15861 HoverLink::InlayHint(lsp_location, server_id) => {
15862 let computation =
15863 self.compute_target_location(lsp_location, server_id, window, cx);
15864 Some(cx.background_spawn(computation))
15865 }
15866 HoverLink::Url(url) => {
15867 first_url_or_file = Some(Either::Left(url));
15868 None
15869 }
15870 HoverLink::File(path) => {
15871 first_url_or_file = Some(Either::Right(path));
15872 None
15873 }
15874 })
15875 .collect();
15876
15877 let workspace = self.workspace();
15878
15879 cx.spawn_in(window, async move |editor, acx| {
15880 let mut locations: Vec<Location> = future::join_all(definitions)
15881 .await
15882 .into_iter()
15883 .filter_map(|location| location.transpose())
15884 .collect::<Result<_>>()
15885 .context("location tasks")?;
15886
15887 if locations.len() > 1 {
15888 let Some(workspace) = workspace else {
15889 return Ok(Navigated::No);
15890 };
15891
15892 let tab_kind = match kind {
15893 Some(GotoDefinitionKind::Implementation) => "Implementations",
15894 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
15895 Some(GotoDefinitionKind::Declaration) => "Declarations",
15896 Some(GotoDefinitionKind::Type) => "Types",
15897 };
15898 let title = editor
15899 .update_in(acx, |_, _, cx| {
15900 let target = locations
15901 .iter()
15902 .map(|location| {
15903 location
15904 .buffer
15905 .read(cx)
15906 .text_for_range(location.range.clone())
15907 .collect::<String>()
15908 })
15909 .filter(|text| !text.contains('\n'))
15910 .unique()
15911 .take(3)
15912 .join(", ");
15913 if target.is_empty() {
15914 tab_kind.to_owned()
15915 } else {
15916 format!("{tab_kind} for {target}")
15917 }
15918 })
15919 .context("buffer title")?;
15920
15921 let opened = workspace
15922 .update_in(acx, |workspace, window, cx| {
15923 Self::open_locations_in_multibuffer(
15924 workspace,
15925 locations,
15926 title,
15927 split,
15928 MultibufferSelectionMode::First,
15929 window,
15930 cx,
15931 )
15932 })
15933 .is_ok();
15934
15935 anyhow::Ok(Navigated::from_bool(opened))
15936 } else if locations.is_empty() {
15937 // If there is one definition, just open it directly
15938 match first_url_or_file {
15939 Some(Either::Left(url)) => {
15940 acx.update(|_, cx| cx.open_url(&url))?;
15941 Ok(Navigated::Yes)
15942 }
15943 Some(Either::Right(path)) => {
15944 let Some(workspace) = workspace else {
15945 return Ok(Navigated::No);
15946 };
15947
15948 workspace
15949 .update_in(acx, |workspace, window, cx| {
15950 workspace.open_resolved_path(path, window, cx)
15951 })?
15952 .await?;
15953 Ok(Navigated::Yes)
15954 }
15955 None => Ok(Navigated::No),
15956 }
15957 } else {
15958 let Some(workspace) = workspace else {
15959 return Ok(Navigated::No);
15960 };
15961
15962 let target = locations.pop().unwrap();
15963 editor.update_in(acx, |editor, window, cx| {
15964 let pane = workspace.read(cx).active_pane().clone();
15965
15966 let range = target.range.to_point(target.buffer.read(cx));
15967 let range = editor.range_for_match(&range);
15968 let range = collapse_multiline_range(range);
15969
15970 if !split
15971 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15972 {
15973 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15974 } else {
15975 window.defer(cx, move |window, cx| {
15976 let target_editor: Entity<Self> =
15977 workspace.update(cx, |workspace, cx| {
15978 let pane = if split {
15979 workspace.adjacent_pane(window, cx)
15980 } else {
15981 workspace.active_pane().clone()
15982 };
15983
15984 workspace.open_project_item(
15985 pane,
15986 target.buffer.clone(),
15987 true,
15988 true,
15989 window,
15990 cx,
15991 )
15992 });
15993 target_editor.update(cx, |target_editor, cx| {
15994 // When selecting a definition in a different buffer, disable the nav history
15995 // to avoid creating a history entry at the previous cursor location.
15996 pane.update(cx, |pane, _| pane.disable_history());
15997 target_editor.go_to_singleton_buffer_range(range, window, cx);
15998 pane.update(cx, |pane, _| pane.enable_history());
15999 });
16000 });
16001 }
16002 Navigated::Yes
16003 })
16004 }
16005 })
16006 }
16007
16008 fn compute_target_location(
16009 &self,
16010 lsp_location: lsp::Location,
16011 server_id: LanguageServerId,
16012 window: &mut Window,
16013 cx: &mut Context<Self>,
16014 ) -> Task<anyhow::Result<Option<Location>>> {
16015 let Some(project) = self.project.clone() else {
16016 return Task::ready(Ok(None));
16017 };
16018
16019 cx.spawn_in(window, async move |editor, cx| {
16020 let location_task = editor.update(cx, |_, cx| {
16021 project.update(cx, |project, cx| {
16022 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16023 })
16024 })?;
16025 let location = Some({
16026 let target_buffer_handle = location_task.await.context("open local buffer")?;
16027 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16028 let target_start = target_buffer
16029 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16030 let target_end = target_buffer
16031 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16032 target_buffer.anchor_after(target_start)
16033 ..target_buffer.anchor_before(target_end)
16034 })?;
16035 Location {
16036 buffer: target_buffer_handle,
16037 range,
16038 }
16039 });
16040 Ok(location)
16041 })
16042 }
16043
16044 pub fn find_all_references(
16045 &mut self,
16046 _: &FindAllReferences,
16047 window: &mut Window,
16048 cx: &mut Context<Self>,
16049 ) -> Option<Task<Result<Navigated>>> {
16050 let selection = self.selections.newest::<usize>(cx);
16051 let multi_buffer = self.buffer.read(cx);
16052 let head = selection.head();
16053
16054 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16055 let head_anchor = multi_buffer_snapshot.anchor_at(
16056 head,
16057 if head < selection.tail() {
16058 Bias::Right
16059 } else {
16060 Bias::Left
16061 },
16062 );
16063
16064 match self
16065 .find_all_references_task_sources
16066 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16067 {
16068 Ok(_) => {
16069 log::info!(
16070 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16071 );
16072 return None;
16073 }
16074 Err(i) => {
16075 self.find_all_references_task_sources.insert(i, head_anchor);
16076 }
16077 }
16078
16079 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16080 let workspace = self.workspace()?;
16081 let project = workspace.read(cx).project().clone();
16082 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16083 Some(cx.spawn_in(window, async move |editor, cx| {
16084 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16085 if let Ok(i) = editor
16086 .find_all_references_task_sources
16087 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16088 {
16089 editor.find_all_references_task_sources.remove(i);
16090 }
16091 });
16092
16093 let locations = references.await?;
16094 if locations.is_empty() {
16095 return anyhow::Ok(Navigated::No);
16096 }
16097
16098 workspace.update_in(cx, |workspace, window, cx| {
16099 let target = locations
16100 .iter()
16101 .map(|location| {
16102 location
16103 .buffer
16104 .read(cx)
16105 .text_for_range(location.range.clone())
16106 .collect::<String>()
16107 })
16108 .filter(|text| !text.contains('\n'))
16109 .unique()
16110 .take(3)
16111 .join(", ");
16112 let title = if target.is_empty() {
16113 "References".to_owned()
16114 } else {
16115 format!("References to {target}")
16116 };
16117 Self::open_locations_in_multibuffer(
16118 workspace,
16119 locations,
16120 title,
16121 false,
16122 MultibufferSelectionMode::First,
16123 window,
16124 cx,
16125 );
16126 Navigated::Yes
16127 })
16128 }))
16129 }
16130
16131 /// Opens a multibuffer with the given project locations in it
16132 pub fn open_locations_in_multibuffer(
16133 workspace: &mut Workspace,
16134 mut locations: Vec<Location>,
16135 title: String,
16136 split: bool,
16137 multibuffer_selection_mode: MultibufferSelectionMode,
16138 window: &mut Window,
16139 cx: &mut Context<Workspace>,
16140 ) {
16141 if locations.is_empty() {
16142 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16143 return;
16144 }
16145
16146 // If there are multiple definitions, open them in a multibuffer
16147 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16148 let mut locations = locations.into_iter().peekable();
16149 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16150 let capability = workspace.project().read(cx).capability();
16151
16152 let excerpt_buffer = cx.new(|cx| {
16153 let mut multibuffer = MultiBuffer::new(capability);
16154 while let Some(location) = locations.next() {
16155 let buffer = location.buffer.read(cx);
16156 let mut ranges_for_buffer = Vec::new();
16157 let range = location.range.to_point(buffer);
16158 ranges_for_buffer.push(range.clone());
16159
16160 while let Some(next_location) = locations.peek() {
16161 if next_location.buffer == location.buffer {
16162 ranges_for_buffer.push(next_location.range.to_point(buffer));
16163 locations.next();
16164 } else {
16165 break;
16166 }
16167 }
16168
16169 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16170 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16171 PathKey::for_buffer(&location.buffer, cx),
16172 location.buffer.clone(),
16173 ranges_for_buffer,
16174 DEFAULT_MULTIBUFFER_CONTEXT,
16175 cx,
16176 );
16177 ranges.extend(new_ranges)
16178 }
16179
16180 multibuffer.with_title(title)
16181 });
16182
16183 let editor = cx.new(|cx| {
16184 Editor::for_multibuffer(
16185 excerpt_buffer,
16186 Some(workspace.project().clone()),
16187 window,
16188 cx,
16189 )
16190 });
16191 editor.update(cx, |editor, cx| {
16192 match multibuffer_selection_mode {
16193 MultibufferSelectionMode::First => {
16194 if let Some(first_range) = ranges.first() {
16195 editor.change_selections(
16196 SelectionEffects::no_scroll(),
16197 window,
16198 cx,
16199 |selections| {
16200 selections.clear_disjoint();
16201 selections
16202 .select_anchor_ranges(std::iter::once(first_range.clone()));
16203 },
16204 );
16205 }
16206 editor.highlight_background::<Self>(
16207 &ranges,
16208 |theme| theme.colors().editor_highlighted_line_background,
16209 cx,
16210 );
16211 }
16212 MultibufferSelectionMode::All => {
16213 editor.change_selections(
16214 SelectionEffects::no_scroll(),
16215 window,
16216 cx,
16217 |selections| {
16218 selections.clear_disjoint();
16219 selections.select_anchor_ranges(ranges);
16220 },
16221 );
16222 }
16223 }
16224 editor.register_buffers_with_language_servers(cx);
16225 });
16226
16227 let item = Box::new(editor);
16228 let item_id = item.item_id();
16229
16230 if split {
16231 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16232 } else {
16233 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16234 let (preview_item_id, preview_item_idx) =
16235 workspace.active_pane().read_with(cx, |pane, _| {
16236 (pane.preview_item_id(), pane.preview_item_idx())
16237 });
16238
16239 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16240
16241 if let Some(preview_item_id) = preview_item_id {
16242 workspace.active_pane().update(cx, |pane, cx| {
16243 pane.remove_item(preview_item_id, false, false, window, cx);
16244 });
16245 }
16246 } else {
16247 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16248 }
16249 }
16250 workspace.active_pane().update(cx, |pane, cx| {
16251 pane.set_preview_item_id(Some(item_id), cx);
16252 });
16253 }
16254
16255 pub fn rename(
16256 &mut self,
16257 _: &Rename,
16258 window: &mut Window,
16259 cx: &mut Context<Self>,
16260 ) -> Option<Task<Result<()>>> {
16261 use language::ToOffset as _;
16262
16263 let provider = self.semantics_provider.clone()?;
16264 let selection = self.selections.newest_anchor().clone();
16265 let (cursor_buffer, cursor_buffer_position) = self
16266 .buffer
16267 .read(cx)
16268 .text_anchor_for_position(selection.head(), cx)?;
16269 let (tail_buffer, cursor_buffer_position_end) = self
16270 .buffer
16271 .read(cx)
16272 .text_anchor_for_position(selection.tail(), cx)?;
16273 if tail_buffer != cursor_buffer {
16274 return None;
16275 }
16276
16277 let snapshot = cursor_buffer.read(cx).snapshot();
16278 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16279 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16280 let prepare_rename = provider
16281 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16282 .unwrap_or_else(|| Task::ready(Ok(None)));
16283 drop(snapshot);
16284
16285 Some(cx.spawn_in(window, async move |this, cx| {
16286 let rename_range = if let Some(range) = prepare_rename.await? {
16287 Some(range)
16288 } else {
16289 this.update(cx, |this, cx| {
16290 let buffer = this.buffer.read(cx).snapshot(cx);
16291 let mut buffer_highlights = this
16292 .document_highlights_for_position(selection.head(), &buffer)
16293 .filter(|highlight| {
16294 highlight.start.excerpt_id == selection.head().excerpt_id
16295 && highlight.end.excerpt_id == selection.head().excerpt_id
16296 });
16297 buffer_highlights
16298 .next()
16299 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16300 })?
16301 };
16302 if let Some(rename_range) = rename_range {
16303 this.update_in(cx, |this, window, cx| {
16304 let snapshot = cursor_buffer.read(cx).snapshot();
16305 let rename_buffer_range = rename_range.to_offset(&snapshot);
16306 let cursor_offset_in_rename_range =
16307 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16308 let cursor_offset_in_rename_range_end =
16309 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16310
16311 this.take_rename(false, window, cx);
16312 let buffer = this.buffer.read(cx).read(cx);
16313 let cursor_offset = selection.head().to_offset(&buffer);
16314 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16315 let rename_end = rename_start + rename_buffer_range.len();
16316 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16317 let mut old_highlight_id = None;
16318 let old_name: Arc<str> = buffer
16319 .chunks(rename_start..rename_end, true)
16320 .map(|chunk| {
16321 if old_highlight_id.is_none() {
16322 old_highlight_id = chunk.syntax_highlight_id;
16323 }
16324 chunk.text
16325 })
16326 .collect::<String>()
16327 .into();
16328
16329 drop(buffer);
16330
16331 // Position the selection in the rename editor so that it matches the current selection.
16332 this.show_local_selections = false;
16333 let rename_editor = cx.new(|cx| {
16334 let mut editor = Editor::single_line(window, cx);
16335 editor.buffer.update(cx, |buffer, cx| {
16336 buffer.edit([(0..0, old_name.clone())], None, cx)
16337 });
16338 let rename_selection_range = match cursor_offset_in_rename_range
16339 .cmp(&cursor_offset_in_rename_range_end)
16340 {
16341 Ordering::Equal => {
16342 editor.select_all(&SelectAll, window, cx);
16343 return editor;
16344 }
16345 Ordering::Less => {
16346 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16347 }
16348 Ordering::Greater => {
16349 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16350 }
16351 };
16352 if rename_selection_range.end > old_name.len() {
16353 editor.select_all(&SelectAll, window, cx);
16354 } else {
16355 editor.change_selections(Default::default(), window, cx, |s| {
16356 s.select_ranges([rename_selection_range]);
16357 });
16358 }
16359 editor
16360 });
16361 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16362 if e == &EditorEvent::Focused {
16363 cx.emit(EditorEvent::FocusedIn)
16364 }
16365 })
16366 .detach();
16367
16368 let write_highlights =
16369 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16370 let read_highlights =
16371 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16372 let ranges = write_highlights
16373 .iter()
16374 .flat_map(|(_, ranges)| ranges.iter())
16375 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16376 .cloned()
16377 .collect();
16378
16379 this.highlight_text::<Rename>(
16380 ranges,
16381 HighlightStyle {
16382 fade_out: Some(0.6),
16383 ..Default::default()
16384 },
16385 cx,
16386 );
16387 let rename_focus_handle = rename_editor.focus_handle(cx);
16388 window.focus(&rename_focus_handle);
16389 let block_id = this.insert_blocks(
16390 [BlockProperties {
16391 style: BlockStyle::Flex,
16392 placement: BlockPlacement::Below(range.start),
16393 height: Some(1),
16394 render: Arc::new({
16395 let rename_editor = rename_editor.clone();
16396 move |cx: &mut BlockContext| {
16397 let mut text_style = cx.editor_style.text.clone();
16398 if let Some(highlight_style) = old_highlight_id
16399 .and_then(|h| h.style(&cx.editor_style.syntax))
16400 {
16401 text_style = text_style.highlight(highlight_style);
16402 }
16403 div()
16404 .block_mouse_except_scroll()
16405 .pl(cx.anchor_x)
16406 .child(EditorElement::new(
16407 &rename_editor,
16408 EditorStyle {
16409 background: cx.theme().system().transparent,
16410 local_player: cx.editor_style.local_player,
16411 text: text_style,
16412 scrollbar_width: cx.editor_style.scrollbar_width,
16413 syntax: cx.editor_style.syntax.clone(),
16414 status: cx.editor_style.status.clone(),
16415 inlay_hints_style: HighlightStyle {
16416 font_weight: Some(FontWeight::BOLD),
16417 ..make_inlay_hints_style(cx.app)
16418 },
16419 edit_prediction_styles: make_suggestion_styles(
16420 cx.app,
16421 ),
16422 ..EditorStyle::default()
16423 },
16424 ))
16425 .into_any_element()
16426 }
16427 }),
16428 priority: 0,
16429 }],
16430 Some(Autoscroll::fit()),
16431 cx,
16432 )[0];
16433 this.pending_rename = Some(RenameState {
16434 range,
16435 old_name,
16436 editor: rename_editor,
16437 block_id,
16438 });
16439 })?;
16440 }
16441
16442 Ok(())
16443 }))
16444 }
16445
16446 pub fn confirm_rename(
16447 &mut self,
16448 _: &ConfirmRename,
16449 window: &mut Window,
16450 cx: &mut Context<Self>,
16451 ) -> Option<Task<Result<()>>> {
16452 let rename = self.take_rename(false, window, cx)?;
16453 let workspace = self.workspace()?.downgrade();
16454 let (buffer, start) = self
16455 .buffer
16456 .read(cx)
16457 .text_anchor_for_position(rename.range.start, cx)?;
16458 let (end_buffer, _) = self
16459 .buffer
16460 .read(cx)
16461 .text_anchor_for_position(rename.range.end, cx)?;
16462 if buffer != end_buffer {
16463 return None;
16464 }
16465
16466 let old_name = rename.old_name;
16467 let new_name = rename.editor.read(cx).text(cx);
16468
16469 let rename = self.semantics_provider.as_ref()?.perform_rename(
16470 &buffer,
16471 start,
16472 new_name.clone(),
16473 cx,
16474 )?;
16475
16476 Some(cx.spawn_in(window, async move |editor, cx| {
16477 let project_transaction = rename.await?;
16478 Self::open_project_transaction(
16479 &editor,
16480 workspace,
16481 project_transaction,
16482 format!("Rename: {} → {}", old_name, new_name),
16483 cx,
16484 )
16485 .await?;
16486
16487 editor.update(cx, |editor, cx| {
16488 editor.refresh_document_highlights(cx);
16489 })?;
16490 Ok(())
16491 }))
16492 }
16493
16494 fn take_rename(
16495 &mut self,
16496 moving_cursor: bool,
16497 window: &mut Window,
16498 cx: &mut Context<Self>,
16499 ) -> Option<RenameState> {
16500 let rename = self.pending_rename.take()?;
16501 if rename.editor.focus_handle(cx).is_focused(window) {
16502 window.focus(&self.focus_handle);
16503 }
16504
16505 self.remove_blocks(
16506 [rename.block_id].into_iter().collect(),
16507 Some(Autoscroll::fit()),
16508 cx,
16509 );
16510 self.clear_highlights::<Rename>(cx);
16511 self.show_local_selections = true;
16512
16513 if moving_cursor {
16514 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16515 editor.selections.newest::<usize>(cx).head()
16516 });
16517
16518 // Update the selection to match the position of the selection inside
16519 // the rename editor.
16520 let snapshot = self.buffer.read(cx).read(cx);
16521 let rename_range = rename.range.to_offset(&snapshot);
16522 let cursor_in_editor = snapshot
16523 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16524 .min(rename_range.end);
16525 drop(snapshot);
16526
16527 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16528 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16529 });
16530 } else {
16531 self.refresh_document_highlights(cx);
16532 }
16533
16534 Some(rename)
16535 }
16536
16537 pub fn pending_rename(&self) -> Option<&RenameState> {
16538 self.pending_rename.as_ref()
16539 }
16540
16541 fn format(
16542 &mut self,
16543 _: &Format,
16544 window: &mut Window,
16545 cx: &mut Context<Self>,
16546 ) -> Option<Task<Result<()>>> {
16547 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16548
16549 let project = match &self.project {
16550 Some(project) => project.clone(),
16551 None => return None,
16552 };
16553
16554 Some(self.perform_format(
16555 project,
16556 FormatTrigger::Manual,
16557 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16558 window,
16559 cx,
16560 ))
16561 }
16562
16563 fn format_selections(
16564 &mut self,
16565 _: &FormatSelections,
16566 window: &mut Window,
16567 cx: &mut Context<Self>,
16568 ) -> Option<Task<Result<()>>> {
16569 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16570
16571 let project = match &self.project {
16572 Some(project) => project.clone(),
16573 None => return None,
16574 };
16575
16576 let ranges = self
16577 .selections
16578 .all_adjusted(cx)
16579 .into_iter()
16580 .map(|selection| selection.range())
16581 .collect_vec();
16582
16583 Some(self.perform_format(
16584 project,
16585 FormatTrigger::Manual,
16586 FormatTarget::Ranges(ranges),
16587 window,
16588 cx,
16589 ))
16590 }
16591
16592 fn perform_format(
16593 &mut self,
16594 project: Entity<Project>,
16595 trigger: FormatTrigger,
16596 target: FormatTarget,
16597 window: &mut Window,
16598 cx: &mut Context<Self>,
16599 ) -> Task<Result<()>> {
16600 let buffer = self.buffer.clone();
16601 let (buffers, target) = match target {
16602 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16603 FormatTarget::Ranges(selection_ranges) => {
16604 let multi_buffer = buffer.read(cx);
16605 let snapshot = multi_buffer.read(cx);
16606 let mut buffers = HashSet::default();
16607 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16608 BTreeMap::new();
16609 for selection_range in selection_ranges {
16610 for (buffer, buffer_range, _) in
16611 snapshot.range_to_buffer_ranges(selection_range)
16612 {
16613 let buffer_id = buffer.remote_id();
16614 let start = buffer.anchor_before(buffer_range.start);
16615 let end = buffer.anchor_after(buffer_range.end);
16616 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16617 buffer_id_to_ranges
16618 .entry(buffer_id)
16619 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16620 .or_insert_with(|| vec![start..end]);
16621 }
16622 }
16623 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16624 }
16625 };
16626
16627 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16628 let selections_prev = transaction_id_prev
16629 .and_then(|transaction_id_prev| {
16630 // default to selections as they were after the last edit, if we have them,
16631 // instead of how they are now.
16632 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16633 // will take you back to where you made the last edit, instead of staying where you scrolled
16634 self.selection_history
16635 .transaction(transaction_id_prev)
16636 .map(|t| t.0.clone())
16637 })
16638 .unwrap_or_else(|| {
16639 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16640 self.selections.disjoint_anchors()
16641 });
16642
16643 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16644 let format = project.update(cx, |project, cx| {
16645 project.format(buffers, target, true, trigger, cx)
16646 });
16647
16648 cx.spawn_in(window, async move |editor, cx| {
16649 let transaction = futures::select_biased! {
16650 transaction = format.log_err().fuse() => transaction,
16651 () = timeout => {
16652 log::warn!("timed out waiting for formatting");
16653 None
16654 }
16655 };
16656
16657 buffer
16658 .update(cx, |buffer, cx| {
16659 if let Some(transaction) = transaction
16660 && !buffer.is_singleton()
16661 {
16662 buffer.push_transaction(&transaction.0, cx);
16663 }
16664 cx.notify();
16665 })
16666 .ok();
16667
16668 if let Some(transaction_id_now) =
16669 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16670 {
16671 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16672 if has_new_transaction {
16673 _ = editor.update(cx, |editor, _| {
16674 editor
16675 .selection_history
16676 .insert_transaction(transaction_id_now, selections_prev);
16677 });
16678 }
16679 }
16680
16681 Ok(())
16682 })
16683 }
16684
16685 fn organize_imports(
16686 &mut self,
16687 _: &OrganizeImports,
16688 window: &mut Window,
16689 cx: &mut Context<Self>,
16690 ) -> Option<Task<Result<()>>> {
16691 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16692 let project = match &self.project {
16693 Some(project) => project.clone(),
16694 None => return None,
16695 };
16696 Some(self.perform_code_action_kind(
16697 project,
16698 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16699 window,
16700 cx,
16701 ))
16702 }
16703
16704 fn perform_code_action_kind(
16705 &mut self,
16706 project: Entity<Project>,
16707 kind: CodeActionKind,
16708 window: &mut Window,
16709 cx: &mut Context<Self>,
16710 ) -> Task<Result<()>> {
16711 let buffer = self.buffer.clone();
16712 let buffers = buffer.read(cx).all_buffers();
16713 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16714 let apply_action = project.update(cx, |project, cx| {
16715 project.apply_code_action_kind(buffers, kind, true, cx)
16716 });
16717 cx.spawn_in(window, async move |_, cx| {
16718 let transaction = futures::select_biased! {
16719 () = timeout => {
16720 log::warn!("timed out waiting for executing code action");
16721 None
16722 }
16723 transaction = apply_action.log_err().fuse() => transaction,
16724 };
16725 buffer
16726 .update(cx, |buffer, cx| {
16727 // check if we need this
16728 if let Some(transaction) = transaction
16729 && !buffer.is_singleton()
16730 {
16731 buffer.push_transaction(&transaction.0, cx);
16732 }
16733 cx.notify();
16734 })
16735 .ok();
16736 Ok(())
16737 })
16738 }
16739
16740 pub fn restart_language_server(
16741 &mut self,
16742 _: &RestartLanguageServer,
16743 _: &mut Window,
16744 cx: &mut Context<Self>,
16745 ) {
16746 if let Some(project) = self.project.clone() {
16747 self.buffer.update(cx, |multi_buffer, cx| {
16748 project.update(cx, |project, cx| {
16749 project.restart_language_servers_for_buffers(
16750 multi_buffer.all_buffers().into_iter().collect(),
16751 HashSet::default(),
16752 cx,
16753 );
16754 });
16755 })
16756 }
16757 }
16758
16759 pub fn stop_language_server(
16760 &mut self,
16761 _: &StopLanguageServer,
16762 _: &mut Window,
16763 cx: &mut Context<Self>,
16764 ) {
16765 if let Some(project) = self.project.clone() {
16766 self.buffer.update(cx, |multi_buffer, cx| {
16767 project.update(cx, |project, cx| {
16768 project.stop_language_servers_for_buffers(
16769 multi_buffer.all_buffers().into_iter().collect(),
16770 HashSet::default(),
16771 cx,
16772 );
16773 cx.emit(project::Event::RefreshInlayHints);
16774 });
16775 });
16776 }
16777 }
16778
16779 fn cancel_language_server_work(
16780 workspace: &mut Workspace,
16781 _: &actions::CancelLanguageServerWork,
16782 _: &mut Window,
16783 cx: &mut Context<Workspace>,
16784 ) {
16785 let project = workspace.project();
16786 let buffers = workspace
16787 .active_item(cx)
16788 .and_then(|item| item.act_as::<Editor>(cx))
16789 .map_or(HashSet::default(), |editor| {
16790 editor.read(cx).buffer.read(cx).all_buffers()
16791 });
16792 project.update(cx, |project, cx| {
16793 project.cancel_language_server_work_for_buffers(buffers, cx);
16794 });
16795 }
16796
16797 fn show_character_palette(
16798 &mut self,
16799 _: &ShowCharacterPalette,
16800 window: &mut Window,
16801 _: &mut Context<Self>,
16802 ) {
16803 window.show_character_palette();
16804 }
16805
16806 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16807 if !self.diagnostics_enabled() {
16808 return;
16809 }
16810
16811 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16812 let buffer = self.buffer.read(cx).snapshot(cx);
16813 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16814 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16815 let is_valid = buffer
16816 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16817 .any(|entry| {
16818 entry.diagnostic.is_primary
16819 && !entry.range.is_empty()
16820 && entry.range.start == primary_range_start
16821 && entry.diagnostic.message == active_diagnostics.active_message
16822 });
16823
16824 if !is_valid {
16825 self.dismiss_diagnostics(cx);
16826 }
16827 }
16828 }
16829
16830 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16831 match &self.active_diagnostics {
16832 ActiveDiagnostic::Group(group) => Some(group),
16833 _ => None,
16834 }
16835 }
16836
16837 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16838 if !self.diagnostics_enabled() {
16839 return;
16840 }
16841 self.dismiss_diagnostics(cx);
16842 self.active_diagnostics = ActiveDiagnostic::All;
16843 }
16844
16845 fn activate_diagnostics(
16846 &mut self,
16847 buffer_id: BufferId,
16848 diagnostic: DiagnosticEntry<usize>,
16849 window: &mut Window,
16850 cx: &mut Context<Self>,
16851 ) {
16852 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16853 return;
16854 }
16855 self.dismiss_diagnostics(cx);
16856 let snapshot = self.snapshot(window, cx);
16857 let buffer = self.buffer.read(cx).snapshot(cx);
16858 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16859 return;
16860 };
16861
16862 let diagnostic_group = buffer
16863 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16864 .collect::<Vec<_>>();
16865
16866 let blocks =
16867 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16868
16869 let blocks = self.display_map.update(cx, |display_map, cx| {
16870 display_map.insert_blocks(blocks, cx).into_iter().collect()
16871 });
16872 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16873 active_range: buffer.anchor_before(diagnostic.range.start)
16874 ..buffer.anchor_after(diagnostic.range.end),
16875 active_message: diagnostic.diagnostic.message.clone(),
16876 group_id: diagnostic.diagnostic.group_id,
16877 blocks,
16878 });
16879 cx.notify();
16880 }
16881
16882 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16883 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16884 return;
16885 };
16886
16887 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16888 if let ActiveDiagnostic::Group(group) = prev {
16889 self.display_map.update(cx, |display_map, cx| {
16890 display_map.remove_blocks(group.blocks, cx);
16891 });
16892 cx.notify();
16893 }
16894 }
16895
16896 /// Disable inline diagnostics rendering for this editor.
16897 pub fn disable_inline_diagnostics(&mut self) {
16898 self.inline_diagnostics_enabled = false;
16899 self.inline_diagnostics_update = Task::ready(());
16900 self.inline_diagnostics.clear();
16901 }
16902
16903 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16904 self.diagnostics_enabled = false;
16905 self.dismiss_diagnostics(cx);
16906 self.inline_diagnostics_update = Task::ready(());
16907 self.inline_diagnostics.clear();
16908 }
16909
16910 pub fn diagnostics_enabled(&self) -> bool {
16911 self.diagnostics_enabled && self.mode.is_full()
16912 }
16913
16914 pub fn inline_diagnostics_enabled(&self) -> bool {
16915 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16916 }
16917
16918 pub fn show_inline_diagnostics(&self) -> bool {
16919 self.show_inline_diagnostics
16920 }
16921
16922 pub fn toggle_inline_diagnostics(
16923 &mut self,
16924 _: &ToggleInlineDiagnostics,
16925 window: &mut Window,
16926 cx: &mut Context<Editor>,
16927 ) {
16928 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16929 self.refresh_inline_diagnostics(false, window, cx);
16930 }
16931
16932 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16933 self.diagnostics_max_severity = severity;
16934 self.display_map.update(cx, |display_map, _| {
16935 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16936 });
16937 }
16938
16939 pub fn toggle_diagnostics(
16940 &mut self,
16941 _: &ToggleDiagnostics,
16942 window: &mut Window,
16943 cx: &mut Context<Editor>,
16944 ) {
16945 if !self.diagnostics_enabled() {
16946 return;
16947 }
16948
16949 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16950 EditorSettings::get_global(cx)
16951 .diagnostics_max_severity
16952 .filter(|severity| severity != &DiagnosticSeverity::Off)
16953 .unwrap_or(DiagnosticSeverity::Hint)
16954 } else {
16955 DiagnosticSeverity::Off
16956 };
16957 self.set_max_diagnostics_severity(new_severity, cx);
16958 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16959 self.active_diagnostics = ActiveDiagnostic::None;
16960 self.inline_diagnostics_update = Task::ready(());
16961 self.inline_diagnostics.clear();
16962 } else {
16963 self.refresh_inline_diagnostics(false, window, cx);
16964 }
16965
16966 cx.notify();
16967 }
16968
16969 pub fn toggle_minimap(
16970 &mut self,
16971 _: &ToggleMinimap,
16972 window: &mut Window,
16973 cx: &mut Context<Editor>,
16974 ) {
16975 if self.supports_minimap(cx) {
16976 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16977 }
16978 }
16979
16980 fn refresh_inline_diagnostics(
16981 &mut self,
16982 debounce: bool,
16983 window: &mut Window,
16984 cx: &mut Context<Self>,
16985 ) {
16986 let max_severity = ProjectSettings::get_global(cx)
16987 .diagnostics
16988 .inline
16989 .max_severity
16990 .unwrap_or(self.diagnostics_max_severity);
16991
16992 if !self.inline_diagnostics_enabled()
16993 || !self.show_inline_diagnostics
16994 || max_severity == DiagnosticSeverity::Off
16995 {
16996 self.inline_diagnostics_update = Task::ready(());
16997 self.inline_diagnostics.clear();
16998 return;
16999 }
17000
17001 let debounce_ms = ProjectSettings::get_global(cx)
17002 .diagnostics
17003 .inline
17004 .update_debounce_ms;
17005 let debounce = if debounce && debounce_ms > 0 {
17006 Some(Duration::from_millis(debounce_ms))
17007 } else {
17008 None
17009 };
17010 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17011 if let Some(debounce) = debounce {
17012 cx.background_executor().timer(debounce).await;
17013 }
17014 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17015 editor
17016 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17017 .ok()
17018 }) else {
17019 return;
17020 };
17021
17022 let new_inline_diagnostics = cx
17023 .background_spawn(async move {
17024 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17025 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17026 let message = diagnostic_entry
17027 .diagnostic
17028 .message
17029 .split_once('\n')
17030 .map(|(line, _)| line)
17031 .map(SharedString::new)
17032 .unwrap_or_else(|| {
17033 SharedString::from(diagnostic_entry.diagnostic.message)
17034 });
17035 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17036 let (Ok(i) | Err(i)) = inline_diagnostics
17037 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17038 inline_diagnostics.insert(
17039 i,
17040 (
17041 start_anchor,
17042 InlineDiagnostic {
17043 message,
17044 group_id: diagnostic_entry.diagnostic.group_id,
17045 start: diagnostic_entry.range.start.to_point(&snapshot),
17046 is_primary: diagnostic_entry.diagnostic.is_primary,
17047 severity: diagnostic_entry.diagnostic.severity,
17048 },
17049 ),
17050 );
17051 }
17052 inline_diagnostics
17053 })
17054 .await;
17055
17056 editor
17057 .update(cx, |editor, cx| {
17058 editor.inline_diagnostics = new_inline_diagnostics;
17059 cx.notify();
17060 })
17061 .ok();
17062 });
17063 }
17064
17065 fn pull_diagnostics(
17066 &mut self,
17067 buffer_id: Option<BufferId>,
17068 window: &Window,
17069 cx: &mut Context<Self>,
17070 ) -> Option<()> {
17071 if !self.mode().is_full() {
17072 return None;
17073 }
17074 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17075 .diagnostics
17076 .lsp_pull_diagnostics;
17077 if !pull_diagnostics_settings.enabled {
17078 return None;
17079 }
17080 let project = self.project()?.downgrade();
17081 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17082 let mut buffers = self.buffer.read(cx).all_buffers();
17083 if let Some(buffer_id) = buffer_id {
17084 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17085 }
17086
17087 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17088 cx.background_executor().timer(debounce).await;
17089
17090 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17091 buffers
17092 .into_iter()
17093 .filter_map(|buffer| {
17094 project
17095 .update(cx, |project, cx| {
17096 project.lsp_store().update(cx, |lsp_store, cx| {
17097 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17098 })
17099 })
17100 .ok()
17101 })
17102 .collect::<FuturesUnordered<_>>()
17103 }) else {
17104 return;
17105 };
17106
17107 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17108 match pull_task {
17109 Ok(()) => {
17110 if editor
17111 .update_in(cx, |editor, window, cx| {
17112 editor.update_diagnostics_state(window, cx);
17113 })
17114 .is_err()
17115 {
17116 return;
17117 }
17118 }
17119 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17120 }
17121 }
17122 });
17123
17124 Some(())
17125 }
17126
17127 pub fn set_selections_from_remote(
17128 &mut self,
17129 selections: Vec<Selection<Anchor>>,
17130 pending_selection: Option<Selection<Anchor>>,
17131 window: &mut Window,
17132 cx: &mut Context<Self>,
17133 ) {
17134 let old_cursor_position = self.selections.newest_anchor().head();
17135 self.selections.change_with(cx, |s| {
17136 s.select_anchors(selections);
17137 if let Some(pending_selection) = pending_selection {
17138 s.set_pending(pending_selection, SelectMode::Character);
17139 } else {
17140 s.clear_pending();
17141 }
17142 });
17143 self.selections_did_change(
17144 false,
17145 &old_cursor_position,
17146 SelectionEffects::default(),
17147 window,
17148 cx,
17149 );
17150 }
17151
17152 pub fn transact(
17153 &mut self,
17154 window: &mut Window,
17155 cx: &mut Context<Self>,
17156 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17157 ) -> Option<TransactionId> {
17158 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17159 this.start_transaction_at(Instant::now(), window, cx);
17160 update(this, window, cx);
17161 this.end_transaction_at(Instant::now(), cx)
17162 })
17163 }
17164
17165 pub fn start_transaction_at(
17166 &mut self,
17167 now: Instant,
17168 window: &mut Window,
17169 cx: &mut Context<Self>,
17170 ) -> Option<TransactionId> {
17171 self.end_selection(window, cx);
17172 if let Some(tx_id) = self
17173 .buffer
17174 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17175 {
17176 self.selection_history
17177 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17178 cx.emit(EditorEvent::TransactionBegun {
17179 transaction_id: tx_id,
17180 });
17181 Some(tx_id)
17182 } else {
17183 None
17184 }
17185 }
17186
17187 pub fn end_transaction_at(
17188 &mut self,
17189 now: Instant,
17190 cx: &mut Context<Self>,
17191 ) -> Option<TransactionId> {
17192 if let Some(transaction_id) = self
17193 .buffer
17194 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17195 {
17196 if let Some((_, end_selections)) =
17197 self.selection_history.transaction_mut(transaction_id)
17198 {
17199 *end_selections = Some(self.selections.disjoint_anchors());
17200 } else {
17201 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17202 }
17203
17204 cx.emit(EditorEvent::Edited { transaction_id });
17205 Some(transaction_id)
17206 } else {
17207 None
17208 }
17209 }
17210
17211 pub fn modify_transaction_selection_history(
17212 &mut self,
17213 transaction_id: TransactionId,
17214 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17215 ) -> bool {
17216 self.selection_history
17217 .transaction_mut(transaction_id)
17218 .map(modify)
17219 .is_some()
17220 }
17221
17222 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17223 if self.selection_mark_mode {
17224 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17225 s.move_with(|_, sel| {
17226 sel.collapse_to(sel.head(), SelectionGoal::None);
17227 });
17228 })
17229 }
17230 self.selection_mark_mode = true;
17231 cx.notify();
17232 }
17233
17234 pub fn swap_selection_ends(
17235 &mut self,
17236 _: &actions::SwapSelectionEnds,
17237 window: &mut Window,
17238 cx: &mut Context<Self>,
17239 ) {
17240 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17241 s.move_with(|_, sel| {
17242 if sel.start != sel.end {
17243 sel.reversed = !sel.reversed
17244 }
17245 });
17246 });
17247 self.request_autoscroll(Autoscroll::newest(), cx);
17248 cx.notify();
17249 }
17250
17251 pub fn toggle_focus(
17252 workspace: &mut Workspace,
17253 _: &actions::ToggleFocus,
17254 window: &mut Window,
17255 cx: &mut Context<Workspace>,
17256 ) {
17257 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17258 return;
17259 };
17260 workspace.activate_item(&item, true, true, window, cx);
17261 }
17262
17263 pub fn toggle_fold(
17264 &mut self,
17265 _: &actions::ToggleFold,
17266 window: &mut Window,
17267 cx: &mut Context<Self>,
17268 ) {
17269 if self.is_singleton(cx) {
17270 let selection = self.selections.newest::<Point>(cx);
17271
17272 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17273 let range = if selection.is_empty() {
17274 let point = selection.head().to_display_point(&display_map);
17275 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17276 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17277 .to_point(&display_map);
17278 start..end
17279 } else {
17280 selection.range()
17281 };
17282 if display_map.folds_in_range(range).next().is_some() {
17283 self.unfold_lines(&Default::default(), window, cx)
17284 } else {
17285 self.fold(&Default::default(), window, cx)
17286 }
17287 } else {
17288 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17289 let buffer_ids: HashSet<_> = self
17290 .selections
17291 .disjoint_anchor_ranges()
17292 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17293 .collect();
17294
17295 let should_unfold = buffer_ids
17296 .iter()
17297 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17298
17299 for buffer_id in buffer_ids {
17300 if should_unfold {
17301 self.unfold_buffer(buffer_id, cx);
17302 } else {
17303 self.fold_buffer(buffer_id, cx);
17304 }
17305 }
17306 }
17307 }
17308
17309 pub fn toggle_fold_recursive(
17310 &mut self,
17311 _: &actions::ToggleFoldRecursive,
17312 window: &mut Window,
17313 cx: &mut Context<Self>,
17314 ) {
17315 let selection = self.selections.newest::<Point>(cx);
17316
17317 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17318 let range = if selection.is_empty() {
17319 let point = selection.head().to_display_point(&display_map);
17320 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17321 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17322 .to_point(&display_map);
17323 start..end
17324 } else {
17325 selection.range()
17326 };
17327 if display_map.folds_in_range(range).next().is_some() {
17328 self.unfold_recursive(&Default::default(), window, cx)
17329 } else {
17330 self.fold_recursive(&Default::default(), window, cx)
17331 }
17332 }
17333
17334 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17335 if self.is_singleton(cx) {
17336 let mut to_fold = Vec::new();
17337 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17338 let selections = self.selections.all_adjusted(cx);
17339
17340 for selection in selections {
17341 let range = selection.range().sorted();
17342 let buffer_start_row = range.start.row;
17343
17344 if range.start.row != range.end.row {
17345 let mut found = false;
17346 let mut row = range.start.row;
17347 while row <= range.end.row {
17348 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17349 {
17350 found = true;
17351 row = crease.range().end.row + 1;
17352 to_fold.push(crease);
17353 } else {
17354 row += 1
17355 }
17356 }
17357 if found {
17358 continue;
17359 }
17360 }
17361
17362 for row in (0..=range.start.row).rev() {
17363 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17364 && crease.range().end.row >= buffer_start_row
17365 {
17366 to_fold.push(crease);
17367 if row <= range.start.row {
17368 break;
17369 }
17370 }
17371 }
17372 }
17373
17374 self.fold_creases(to_fold, true, window, cx);
17375 } else {
17376 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17377 let buffer_ids = self
17378 .selections
17379 .disjoint_anchor_ranges()
17380 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17381 .collect::<HashSet<_>>();
17382 for buffer_id in buffer_ids {
17383 self.fold_buffer(buffer_id, cx);
17384 }
17385 }
17386 }
17387
17388 pub fn toggle_fold_all(
17389 &mut self,
17390 _: &actions::ToggleFoldAll,
17391 window: &mut Window,
17392 cx: &mut Context<Self>,
17393 ) {
17394 if self.buffer.read(cx).is_singleton() {
17395 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17396 let has_folds = display_map
17397 .folds_in_range(0..display_map.buffer_snapshot.len())
17398 .next()
17399 .is_some();
17400
17401 if has_folds {
17402 self.unfold_all(&actions::UnfoldAll, window, cx);
17403 } else {
17404 self.fold_all(&actions::FoldAll, window, cx);
17405 }
17406 } else {
17407 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17408 let should_unfold = buffer_ids
17409 .iter()
17410 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17411
17412 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17413 editor
17414 .update_in(cx, |editor, _, cx| {
17415 for buffer_id in buffer_ids {
17416 if should_unfold {
17417 editor.unfold_buffer(buffer_id, cx);
17418 } else {
17419 editor.fold_buffer(buffer_id, cx);
17420 }
17421 }
17422 })
17423 .ok();
17424 });
17425 }
17426 }
17427
17428 fn fold_at_level(
17429 &mut self,
17430 fold_at: &FoldAtLevel,
17431 window: &mut Window,
17432 cx: &mut Context<Self>,
17433 ) {
17434 if !self.buffer.read(cx).is_singleton() {
17435 return;
17436 }
17437
17438 let fold_at_level = fold_at.0;
17439 let snapshot = self.buffer.read(cx).snapshot(cx);
17440 let mut to_fold = Vec::new();
17441 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17442
17443 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17444 while start_row < end_row {
17445 match self
17446 .snapshot(window, cx)
17447 .crease_for_buffer_row(MultiBufferRow(start_row))
17448 {
17449 Some(crease) => {
17450 let nested_start_row = crease.range().start.row + 1;
17451 let nested_end_row = crease.range().end.row;
17452
17453 if current_level < fold_at_level {
17454 stack.push((nested_start_row, nested_end_row, current_level + 1));
17455 } else if current_level == fold_at_level {
17456 to_fold.push(crease);
17457 }
17458
17459 start_row = nested_end_row + 1;
17460 }
17461 None => start_row += 1,
17462 }
17463 }
17464 }
17465
17466 self.fold_creases(to_fold, true, window, cx);
17467 }
17468
17469 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17470 if self.buffer.read(cx).is_singleton() {
17471 let mut fold_ranges = Vec::new();
17472 let snapshot = self.buffer.read(cx).snapshot(cx);
17473
17474 for row in 0..snapshot.max_row().0 {
17475 if let Some(foldable_range) = self
17476 .snapshot(window, cx)
17477 .crease_for_buffer_row(MultiBufferRow(row))
17478 {
17479 fold_ranges.push(foldable_range);
17480 }
17481 }
17482
17483 self.fold_creases(fold_ranges, true, window, cx);
17484 } else {
17485 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17486 editor
17487 .update_in(cx, |editor, _, cx| {
17488 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17489 editor.fold_buffer(buffer_id, cx);
17490 }
17491 })
17492 .ok();
17493 });
17494 }
17495 }
17496
17497 pub fn fold_function_bodies(
17498 &mut self,
17499 _: &actions::FoldFunctionBodies,
17500 window: &mut Window,
17501 cx: &mut Context<Self>,
17502 ) {
17503 let snapshot = self.buffer.read(cx).snapshot(cx);
17504
17505 let ranges = snapshot
17506 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17507 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17508 .collect::<Vec<_>>();
17509
17510 let creases = ranges
17511 .into_iter()
17512 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17513 .collect();
17514
17515 self.fold_creases(creases, true, window, cx);
17516 }
17517
17518 pub fn fold_recursive(
17519 &mut self,
17520 _: &actions::FoldRecursive,
17521 window: &mut Window,
17522 cx: &mut Context<Self>,
17523 ) {
17524 let mut to_fold = Vec::new();
17525 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17526 let selections = self.selections.all_adjusted(cx);
17527
17528 for selection in selections {
17529 let range = selection.range().sorted();
17530 let buffer_start_row = range.start.row;
17531
17532 if range.start.row != range.end.row {
17533 let mut found = false;
17534 for row in range.start.row..=range.end.row {
17535 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17536 found = true;
17537 to_fold.push(crease);
17538 }
17539 }
17540 if found {
17541 continue;
17542 }
17543 }
17544
17545 for row in (0..=range.start.row).rev() {
17546 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17547 if crease.range().end.row >= buffer_start_row {
17548 to_fold.push(crease);
17549 } else {
17550 break;
17551 }
17552 }
17553 }
17554 }
17555
17556 self.fold_creases(to_fold, true, window, cx);
17557 }
17558
17559 pub fn fold_at(
17560 &mut self,
17561 buffer_row: MultiBufferRow,
17562 window: &mut Window,
17563 cx: &mut Context<Self>,
17564 ) {
17565 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17566
17567 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17568 let autoscroll = self
17569 .selections
17570 .all::<Point>(cx)
17571 .iter()
17572 .any(|selection| crease.range().overlaps(&selection.range()));
17573
17574 self.fold_creases(vec![crease], autoscroll, window, cx);
17575 }
17576 }
17577
17578 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17579 if self.is_singleton(cx) {
17580 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17581 let buffer = &display_map.buffer_snapshot;
17582 let selections = self.selections.all::<Point>(cx);
17583 let ranges = selections
17584 .iter()
17585 .map(|s| {
17586 let range = s.display_range(&display_map).sorted();
17587 let mut start = range.start.to_point(&display_map);
17588 let mut end = range.end.to_point(&display_map);
17589 start.column = 0;
17590 end.column = buffer.line_len(MultiBufferRow(end.row));
17591 start..end
17592 })
17593 .collect::<Vec<_>>();
17594
17595 self.unfold_ranges(&ranges, true, true, cx);
17596 } else {
17597 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17598 let buffer_ids = self
17599 .selections
17600 .disjoint_anchor_ranges()
17601 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17602 .collect::<HashSet<_>>();
17603 for buffer_id in buffer_ids {
17604 self.unfold_buffer(buffer_id, cx);
17605 }
17606 }
17607 }
17608
17609 pub fn unfold_recursive(
17610 &mut self,
17611 _: &UnfoldRecursive,
17612 _window: &mut Window,
17613 cx: &mut Context<Self>,
17614 ) {
17615 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17616 let selections = self.selections.all::<Point>(cx);
17617 let ranges = selections
17618 .iter()
17619 .map(|s| {
17620 let mut range = s.display_range(&display_map).sorted();
17621 *range.start.column_mut() = 0;
17622 *range.end.column_mut() = display_map.line_len(range.end.row());
17623 let start = range.start.to_point(&display_map);
17624 let end = range.end.to_point(&display_map);
17625 start..end
17626 })
17627 .collect::<Vec<_>>();
17628
17629 self.unfold_ranges(&ranges, true, true, cx);
17630 }
17631
17632 pub fn unfold_at(
17633 &mut self,
17634 buffer_row: MultiBufferRow,
17635 _window: &mut Window,
17636 cx: &mut Context<Self>,
17637 ) {
17638 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17639
17640 let intersection_range = Point::new(buffer_row.0, 0)
17641 ..Point::new(
17642 buffer_row.0,
17643 display_map.buffer_snapshot.line_len(buffer_row),
17644 );
17645
17646 let autoscroll = self
17647 .selections
17648 .all::<Point>(cx)
17649 .iter()
17650 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17651
17652 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17653 }
17654
17655 pub fn unfold_all(
17656 &mut self,
17657 _: &actions::UnfoldAll,
17658 _window: &mut Window,
17659 cx: &mut Context<Self>,
17660 ) {
17661 if self.buffer.read(cx).is_singleton() {
17662 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17663 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17664 } else {
17665 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17666 editor
17667 .update(cx, |editor, cx| {
17668 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17669 editor.unfold_buffer(buffer_id, cx);
17670 }
17671 })
17672 .ok();
17673 });
17674 }
17675 }
17676
17677 pub fn fold_selected_ranges(
17678 &mut self,
17679 _: &FoldSelectedRanges,
17680 window: &mut Window,
17681 cx: &mut Context<Self>,
17682 ) {
17683 let selections = self.selections.all_adjusted(cx);
17684 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17685 let ranges = selections
17686 .into_iter()
17687 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17688 .collect::<Vec<_>>();
17689 self.fold_creases(ranges, true, window, cx);
17690 }
17691
17692 pub fn fold_ranges<T: ToOffset + Clone>(
17693 &mut self,
17694 ranges: Vec<Range<T>>,
17695 auto_scroll: bool,
17696 window: &mut Window,
17697 cx: &mut Context<Self>,
17698 ) {
17699 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17700 let ranges = ranges
17701 .into_iter()
17702 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17703 .collect::<Vec<_>>();
17704 self.fold_creases(ranges, auto_scroll, window, cx);
17705 }
17706
17707 pub fn fold_creases<T: ToOffset + Clone>(
17708 &mut self,
17709 creases: Vec<Crease<T>>,
17710 auto_scroll: bool,
17711 _window: &mut Window,
17712 cx: &mut Context<Self>,
17713 ) {
17714 if creases.is_empty() {
17715 return;
17716 }
17717
17718 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17719
17720 if auto_scroll {
17721 self.request_autoscroll(Autoscroll::fit(), cx);
17722 }
17723
17724 cx.notify();
17725
17726 self.scrollbar_marker_state.dirty = true;
17727 self.folds_did_change(cx);
17728 }
17729
17730 /// Removes any folds whose ranges intersect any of the given ranges.
17731 pub fn unfold_ranges<T: ToOffset + Clone>(
17732 &mut self,
17733 ranges: &[Range<T>],
17734 inclusive: bool,
17735 auto_scroll: bool,
17736 cx: &mut Context<Self>,
17737 ) {
17738 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17739 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17740 });
17741 self.folds_did_change(cx);
17742 }
17743
17744 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17745 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17746 return;
17747 }
17748 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17749 self.display_map.update(cx, |display_map, cx| {
17750 display_map.fold_buffers([buffer_id], cx)
17751 });
17752 cx.emit(EditorEvent::BufferFoldToggled {
17753 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17754 folded: true,
17755 });
17756 cx.notify();
17757 }
17758
17759 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17760 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17761 return;
17762 }
17763 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17764 self.display_map.update(cx, |display_map, cx| {
17765 display_map.unfold_buffers([buffer_id], cx);
17766 });
17767 cx.emit(EditorEvent::BufferFoldToggled {
17768 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17769 folded: false,
17770 });
17771 cx.notify();
17772 }
17773
17774 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17775 self.display_map.read(cx).is_buffer_folded(buffer)
17776 }
17777
17778 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17779 self.display_map.read(cx).folded_buffers()
17780 }
17781
17782 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17783 self.display_map.update(cx, |display_map, cx| {
17784 display_map.disable_header_for_buffer(buffer_id, cx);
17785 });
17786 cx.notify();
17787 }
17788
17789 /// Removes any folds with the given ranges.
17790 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17791 &mut self,
17792 ranges: &[Range<T>],
17793 type_id: TypeId,
17794 auto_scroll: bool,
17795 cx: &mut Context<Self>,
17796 ) {
17797 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17798 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17799 });
17800 self.folds_did_change(cx);
17801 }
17802
17803 fn remove_folds_with<T: ToOffset + Clone>(
17804 &mut self,
17805 ranges: &[Range<T>],
17806 auto_scroll: bool,
17807 cx: &mut Context<Self>,
17808 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17809 ) {
17810 if ranges.is_empty() {
17811 return;
17812 }
17813
17814 let mut buffers_affected = HashSet::default();
17815 let multi_buffer = self.buffer().read(cx);
17816 for range in ranges {
17817 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17818 buffers_affected.insert(buffer.read(cx).remote_id());
17819 };
17820 }
17821
17822 self.display_map.update(cx, update);
17823
17824 if auto_scroll {
17825 self.request_autoscroll(Autoscroll::fit(), cx);
17826 }
17827
17828 cx.notify();
17829 self.scrollbar_marker_state.dirty = true;
17830 self.active_indent_guides_state.dirty = true;
17831 }
17832
17833 pub fn update_renderer_widths(
17834 &mut self,
17835 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17836 cx: &mut Context<Self>,
17837 ) -> bool {
17838 self.display_map
17839 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17840 }
17841
17842 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17843 self.display_map.read(cx).fold_placeholder.clone()
17844 }
17845
17846 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17847 self.buffer.update(cx, |buffer, cx| {
17848 buffer.set_all_diff_hunks_expanded(cx);
17849 });
17850 }
17851
17852 pub fn expand_all_diff_hunks(
17853 &mut self,
17854 _: &ExpandAllDiffHunks,
17855 _window: &mut Window,
17856 cx: &mut Context<Self>,
17857 ) {
17858 self.buffer.update(cx, |buffer, cx| {
17859 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17860 });
17861 }
17862
17863 pub fn toggle_selected_diff_hunks(
17864 &mut self,
17865 _: &ToggleSelectedDiffHunks,
17866 _window: &mut Window,
17867 cx: &mut Context<Self>,
17868 ) {
17869 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17870 self.toggle_diff_hunks_in_ranges(ranges, cx);
17871 }
17872
17873 pub fn diff_hunks_in_ranges<'a>(
17874 &'a self,
17875 ranges: &'a [Range<Anchor>],
17876 buffer: &'a MultiBufferSnapshot,
17877 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17878 ranges.iter().flat_map(move |range| {
17879 let end_excerpt_id = range.end.excerpt_id;
17880 let range = range.to_point(buffer);
17881 let mut peek_end = range.end;
17882 if range.end.row < buffer.max_row().0 {
17883 peek_end = Point::new(range.end.row + 1, 0);
17884 }
17885 buffer
17886 .diff_hunks_in_range(range.start..peek_end)
17887 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17888 })
17889 }
17890
17891 pub fn has_stageable_diff_hunks_in_ranges(
17892 &self,
17893 ranges: &[Range<Anchor>],
17894 snapshot: &MultiBufferSnapshot,
17895 ) -> bool {
17896 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
17897 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17898 }
17899
17900 pub fn toggle_staged_selected_diff_hunks(
17901 &mut self,
17902 _: &::git::ToggleStaged,
17903 _: &mut Window,
17904 cx: &mut Context<Self>,
17905 ) {
17906 let snapshot = self.buffer.read(cx).snapshot(cx);
17907 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17908 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17909 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17910 }
17911
17912 pub fn set_render_diff_hunk_controls(
17913 &mut self,
17914 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17915 cx: &mut Context<Self>,
17916 ) {
17917 self.render_diff_hunk_controls = render_diff_hunk_controls;
17918 cx.notify();
17919 }
17920
17921 pub fn stage_and_next(
17922 &mut self,
17923 _: &::git::StageAndNext,
17924 window: &mut Window,
17925 cx: &mut Context<Self>,
17926 ) {
17927 self.do_stage_or_unstage_and_next(true, window, cx);
17928 }
17929
17930 pub fn unstage_and_next(
17931 &mut self,
17932 _: &::git::UnstageAndNext,
17933 window: &mut Window,
17934 cx: &mut Context<Self>,
17935 ) {
17936 self.do_stage_or_unstage_and_next(false, window, cx);
17937 }
17938
17939 pub fn stage_or_unstage_diff_hunks(
17940 &mut self,
17941 stage: bool,
17942 ranges: Vec<Range<Anchor>>,
17943 cx: &mut Context<Self>,
17944 ) {
17945 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17946 cx.spawn(async move |this, cx| {
17947 task.await?;
17948 this.update(cx, |this, cx| {
17949 let snapshot = this.buffer.read(cx).snapshot(cx);
17950 let chunk_by = this
17951 .diff_hunks_in_ranges(&ranges, &snapshot)
17952 .chunk_by(|hunk| hunk.buffer_id);
17953 for (buffer_id, hunks) in &chunk_by {
17954 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17955 }
17956 })
17957 })
17958 .detach_and_log_err(cx);
17959 }
17960
17961 fn save_buffers_for_ranges_if_needed(
17962 &mut self,
17963 ranges: &[Range<Anchor>],
17964 cx: &mut Context<Editor>,
17965 ) -> Task<Result<()>> {
17966 let multibuffer = self.buffer.read(cx);
17967 let snapshot = multibuffer.read(cx);
17968 let buffer_ids: HashSet<_> = ranges
17969 .iter()
17970 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17971 .collect();
17972 drop(snapshot);
17973
17974 let mut buffers = HashSet::default();
17975 for buffer_id in buffer_ids {
17976 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17977 let buffer = buffer_entity.read(cx);
17978 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17979 {
17980 buffers.insert(buffer_entity);
17981 }
17982 }
17983 }
17984
17985 if let Some(project) = &self.project {
17986 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17987 } else {
17988 Task::ready(Ok(()))
17989 }
17990 }
17991
17992 fn do_stage_or_unstage_and_next(
17993 &mut self,
17994 stage: bool,
17995 window: &mut Window,
17996 cx: &mut Context<Self>,
17997 ) {
17998 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17999
18000 if ranges.iter().any(|range| range.start != range.end) {
18001 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18002 return;
18003 }
18004
18005 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18006 let snapshot = self.snapshot(window, cx);
18007 let position = self.selections.newest::<Point>(cx).head();
18008 let mut row = snapshot
18009 .buffer_snapshot
18010 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18011 .find(|hunk| hunk.row_range.start.0 > position.row)
18012 .map(|hunk| hunk.row_range.start);
18013
18014 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18015 // Outside of the project diff editor, wrap around to the beginning.
18016 if !all_diff_hunks_expanded {
18017 row = row.or_else(|| {
18018 snapshot
18019 .buffer_snapshot
18020 .diff_hunks_in_range(Point::zero()..position)
18021 .find(|hunk| hunk.row_range.end.0 < position.row)
18022 .map(|hunk| hunk.row_range.start)
18023 });
18024 }
18025
18026 if let Some(row) = row {
18027 let destination = Point::new(row.0, 0);
18028 let autoscroll = Autoscroll::center();
18029
18030 self.unfold_ranges(&[destination..destination], false, false, cx);
18031 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18032 s.select_ranges([destination..destination]);
18033 });
18034 }
18035 }
18036
18037 fn do_stage_or_unstage(
18038 &self,
18039 stage: bool,
18040 buffer_id: BufferId,
18041 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18042 cx: &mut App,
18043 ) -> Option<()> {
18044 let project = self.project()?;
18045 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18046 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18047 let buffer_snapshot = buffer.read(cx).snapshot();
18048 let file_exists = buffer_snapshot
18049 .file()
18050 .is_some_and(|file| file.disk_state().exists());
18051 diff.update(cx, |diff, cx| {
18052 diff.stage_or_unstage_hunks(
18053 stage,
18054 &hunks
18055 .map(|hunk| buffer_diff::DiffHunk {
18056 buffer_range: hunk.buffer_range,
18057 diff_base_byte_range: hunk.diff_base_byte_range,
18058 secondary_status: hunk.secondary_status,
18059 range: Point::zero()..Point::zero(), // unused
18060 })
18061 .collect::<Vec<_>>(),
18062 &buffer_snapshot,
18063 file_exists,
18064 cx,
18065 )
18066 });
18067 None
18068 }
18069
18070 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18071 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18072 self.buffer
18073 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18074 }
18075
18076 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18077 self.buffer.update(cx, |buffer, cx| {
18078 let ranges = vec![Anchor::min()..Anchor::max()];
18079 if !buffer.all_diff_hunks_expanded()
18080 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18081 {
18082 buffer.collapse_diff_hunks(ranges, cx);
18083 true
18084 } else {
18085 false
18086 }
18087 })
18088 }
18089
18090 fn toggle_diff_hunks_in_ranges(
18091 &mut self,
18092 ranges: Vec<Range<Anchor>>,
18093 cx: &mut Context<Editor>,
18094 ) {
18095 self.buffer.update(cx, |buffer, cx| {
18096 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18097 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18098 })
18099 }
18100
18101 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18102 self.buffer.update(cx, |buffer, cx| {
18103 let snapshot = buffer.snapshot(cx);
18104 let excerpt_id = range.end.excerpt_id;
18105 let point_range = range.to_point(&snapshot);
18106 let expand = !buffer.single_hunk_is_expanded(range, cx);
18107 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18108 })
18109 }
18110
18111 pub(crate) fn apply_all_diff_hunks(
18112 &mut self,
18113 _: &ApplyAllDiffHunks,
18114 window: &mut Window,
18115 cx: &mut Context<Self>,
18116 ) {
18117 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18118
18119 let buffers = self.buffer.read(cx).all_buffers();
18120 for branch_buffer in buffers {
18121 branch_buffer.update(cx, |branch_buffer, cx| {
18122 branch_buffer.merge_into_base(Vec::new(), cx);
18123 });
18124 }
18125
18126 if let Some(project) = self.project.clone() {
18127 self.save(
18128 SaveOptions {
18129 format: true,
18130 autosave: false,
18131 },
18132 project,
18133 window,
18134 cx,
18135 )
18136 .detach_and_log_err(cx);
18137 }
18138 }
18139
18140 pub(crate) fn apply_selected_diff_hunks(
18141 &mut self,
18142 _: &ApplyDiffHunk,
18143 window: &mut Window,
18144 cx: &mut Context<Self>,
18145 ) {
18146 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18147 let snapshot = self.snapshot(window, cx);
18148 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18149 let mut ranges_by_buffer = HashMap::default();
18150 self.transact(window, cx, |editor, _window, cx| {
18151 for hunk in hunks {
18152 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18153 ranges_by_buffer
18154 .entry(buffer.clone())
18155 .or_insert_with(Vec::new)
18156 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18157 }
18158 }
18159
18160 for (buffer, ranges) in ranges_by_buffer {
18161 buffer.update(cx, |buffer, cx| {
18162 buffer.merge_into_base(ranges, cx);
18163 });
18164 }
18165 });
18166
18167 if let Some(project) = self.project.clone() {
18168 self.save(
18169 SaveOptions {
18170 format: true,
18171 autosave: false,
18172 },
18173 project,
18174 window,
18175 cx,
18176 )
18177 .detach_and_log_err(cx);
18178 }
18179 }
18180
18181 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18182 if hovered != self.gutter_hovered {
18183 self.gutter_hovered = hovered;
18184 cx.notify();
18185 }
18186 }
18187
18188 pub fn insert_blocks(
18189 &mut self,
18190 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18191 autoscroll: Option<Autoscroll>,
18192 cx: &mut Context<Self>,
18193 ) -> Vec<CustomBlockId> {
18194 let blocks = self
18195 .display_map
18196 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18197 if let Some(autoscroll) = autoscroll {
18198 self.request_autoscroll(autoscroll, cx);
18199 }
18200 cx.notify();
18201 blocks
18202 }
18203
18204 pub fn resize_blocks(
18205 &mut self,
18206 heights: HashMap<CustomBlockId, u32>,
18207 autoscroll: Option<Autoscroll>,
18208 cx: &mut Context<Self>,
18209 ) {
18210 self.display_map
18211 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18212 if let Some(autoscroll) = autoscroll {
18213 self.request_autoscroll(autoscroll, cx);
18214 }
18215 cx.notify();
18216 }
18217
18218 pub fn replace_blocks(
18219 &mut self,
18220 renderers: HashMap<CustomBlockId, RenderBlock>,
18221 autoscroll: Option<Autoscroll>,
18222 cx: &mut Context<Self>,
18223 ) {
18224 self.display_map
18225 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18226 if let Some(autoscroll) = autoscroll {
18227 self.request_autoscroll(autoscroll, cx);
18228 }
18229 cx.notify();
18230 }
18231
18232 pub fn remove_blocks(
18233 &mut self,
18234 block_ids: HashSet<CustomBlockId>,
18235 autoscroll: Option<Autoscroll>,
18236 cx: &mut Context<Self>,
18237 ) {
18238 self.display_map.update(cx, |display_map, cx| {
18239 display_map.remove_blocks(block_ids, cx)
18240 });
18241 if let Some(autoscroll) = autoscroll {
18242 self.request_autoscroll(autoscroll, cx);
18243 }
18244 cx.notify();
18245 }
18246
18247 pub fn row_for_block(
18248 &self,
18249 block_id: CustomBlockId,
18250 cx: &mut Context<Self>,
18251 ) -> Option<DisplayRow> {
18252 self.display_map
18253 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18254 }
18255
18256 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18257 self.focused_block = Some(focused_block);
18258 }
18259
18260 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18261 self.focused_block.take()
18262 }
18263
18264 pub fn insert_creases(
18265 &mut self,
18266 creases: impl IntoIterator<Item = Crease<Anchor>>,
18267 cx: &mut Context<Self>,
18268 ) -> Vec<CreaseId> {
18269 self.display_map
18270 .update(cx, |map, cx| map.insert_creases(creases, cx))
18271 }
18272
18273 pub fn remove_creases(
18274 &mut self,
18275 ids: impl IntoIterator<Item = CreaseId>,
18276 cx: &mut Context<Self>,
18277 ) -> Vec<(CreaseId, Range<Anchor>)> {
18278 self.display_map
18279 .update(cx, |map, cx| map.remove_creases(ids, cx))
18280 }
18281
18282 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18283 self.display_map
18284 .update(cx, |map, cx| map.snapshot(cx))
18285 .longest_row()
18286 }
18287
18288 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18289 self.display_map
18290 .update(cx, |map, cx| map.snapshot(cx))
18291 .max_point()
18292 }
18293
18294 pub fn text(&self, cx: &App) -> String {
18295 self.buffer.read(cx).read(cx).text()
18296 }
18297
18298 pub fn is_empty(&self, cx: &App) -> bool {
18299 self.buffer.read(cx).read(cx).is_empty()
18300 }
18301
18302 pub fn text_option(&self, cx: &App) -> Option<String> {
18303 let text = self.text(cx);
18304 let text = text.trim();
18305
18306 if text.is_empty() {
18307 return None;
18308 }
18309
18310 Some(text.to_string())
18311 }
18312
18313 pub fn set_text(
18314 &mut self,
18315 text: impl Into<Arc<str>>,
18316 window: &mut Window,
18317 cx: &mut Context<Self>,
18318 ) {
18319 self.transact(window, cx, |this, _, cx| {
18320 this.buffer
18321 .read(cx)
18322 .as_singleton()
18323 .expect("you can only call set_text on editors for singleton buffers")
18324 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18325 });
18326 }
18327
18328 pub fn display_text(&self, cx: &mut App) -> String {
18329 self.display_map
18330 .update(cx, |map, cx| map.snapshot(cx))
18331 .text()
18332 }
18333
18334 fn create_minimap(
18335 &self,
18336 minimap_settings: MinimapSettings,
18337 window: &mut Window,
18338 cx: &mut Context<Self>,
18339 ) -> Option<Entity<Self>> {
18340 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18341 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18342 }
18343
18344 fn initialize_new_minimap(
18345 &self,
18346 minimap_settings: MinimapSettings,
18347 window: &mut Window,
18348 cx: &mut Context<Self>,
18349 ) -> Entity<Self> {
18350 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18351
18352 let mut minimap = Editor::new_internal(
18353 EditorMode::Minimap {
18354 parent: cx.weak_entity(),
18355 },
18356 self.buffer.clone(),
18357 None,
18358 Some(self.display_map.clone()),
18359 window,
18360 cx,
18361 );
18362 minimap.scroll_manager.clone_state(&self.scroll_manager);
18363 minimap.set_text_style_refinement(TextStyleRefinement {
18364 font_size: Some(MINIMAP_FONT_SIZE),
18365 font_weight: Some(MINIMAP_FONT_WEIGHT),
18366 ..Default::default()
18367 });
18368 minimap.update_minimap_configuration(minimap_settings, cx);
18369 cx.new(|_| minimap)
18370 }
18371
18372 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18373 let current_line_highlight = minimap_settings
18374 .current_line_highlight
18375 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18376 self.set_current_line_highlight(Some(current_line_highlight));
18377 }
18378
18379 pub fn minimap(&self) -> Option<&Entity<Self>> {
18380 self.minimap
18381 .as_ref()
18382 .filter(|_| self.minimap_visibility.visible())
18383 }
18384
18385 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18386 let mut wrap_guides = smallvec![];
18387
18388 if self.show_wrap_guides == Some(false) {
18389 return wrap_guides;
18390 }
18391
18392 let settings = self.buffer.read(cx).language_settings(cx);
18393 if settings.show_wrap_guides {
18394 match self.soft_wrap_mode(cx) {
18395 SoftWrap::Column(soft_wrap) => {
18396 wrap_guides.push((soft_wrap as usize, true));
18397 }
18398 SoftWrap::Bounded(soft_wrap) => {
18399 wrap_guides.push((soft_wrap as usize, true));
18400 }
18401 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18402 }
18403 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18404 }
18405
18406 wrap_guides
18407 }
18408
18409 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18410 let settings = self.buffer.read(cx).language_settings(cx);
18411 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18412 match mode {
18413 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18414 SoftWrap::None
18415 }
18416 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18417 language_settings::SoftWrap::PreferredLineLength => {
18418 SoftWrap::Column(settings.preferred_line_length)
18419 }
18420 language_settings::SoftWrap::Bounded => {
18421 SoftWrap::Bounded(settings.preferred_line_length)
18422 }
18423 }
18424 }
18425
18426 pub fn set_soft_wrap_mode(
18427 &mut self,
18428 mode: language_settings::SoftWrap,
18429
18430 cx: &mut Context<Self>,
18431 ) {
18432 self.soft_wrap_mode_override = Some(mode);
18433 cx.notify();
18434 }
18435
18436 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18437 self.hard_wrap = hard_wrap;
18438 cx.notify();
18439 }
18440
18441 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18442 self.text_style_refinement = Some(style);
18443 }
18444
18445 /// called by the Element so we know what style we were most recently rendered with.
18446 pub(crate) fn set_style(
18447 &mut self,
18448 style: EditorStyle,
18449 window: &mut Window,
18450 cx: &mut Context<Self>,
18451 ) {
18452 // We intentionally do not inform the display map about the minimap style
18453 // so that wrapping is not recalculated and stays consistent for the editor
18454 // and its linked minimap.
18455 if !self.mode.is_minimap() {
18456 let rem_size = window.rem_size();
18457 self.display_map.update(cx, |map, cx| {
18458 map.set_font(
18459 style.text.font(),
18460 style.text.font_size.to_pixels(rem_size),
18461 cx,
18462 )
18463 });
18464 }
18465 self.style = Some(style);
18466 }
18467
18468 pub fn style(&self) -> Option<&EditorStyle> {
18469 self.style.as_ref()
18470 }
18471
18472 // Called by the element. This method is not designed to be called outside of the editor
18473 // element's layout code because it does not notify when rewrapping is computed synchronously.
18474 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18475 self.display_map
18476 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18477 }
18478
18479 pub fn set_soft_wrap(&mut self) {
18480 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18481 }
18482
18483 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18484 if self.soft_wrap_mode_override.is_some() {
18485 self.soft_wrap_mode_override.take();
18486 } else {
18487 let soft_wrap = match self.soft_wrap_mode(cx) {
18488 SoftWrap::GitDiff => return,
18489 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18490 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18491 language_settings::SoftWrap::None
18492 }
18493 };
18494 self.soft_wrap_mode_override = Some(soft_wrap);
18495 }
18496 cx.notify();
18497 }
18498
18499 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18500 let Some(workspace) = self.workspace() else {
18501 return;
18502 };
18503 let fs = workspace.read(cx).app_state().fs.clone();
18504 let current_show = TabBarSettings::get_global(cx).show;
18505 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18506 setting.show = Some(!current_show);
18507 });
18508 }
18509
18510 pub fn toggle_indent_guides(
18511 &mut self,
18512 _: &ToggleIndentGuides,
18513 _: &mut Window,
18514 cx: &mut Context<Self>,
18515 ) {
18516 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18517 self.buffer
18518 .read(cx)
18519 .language_settings(cx)
18520 .indent_guides
18521 .enabled
18522 });
18523 self.show_indent_guides = Some(!currently_enabled);
18524 cx.notify();
18525 }
18526
18527 fn should_show_indent_guides(&self) -> Option<bool> {
18528 self.show_indent_guides
18529 }
18530
18531 pub fn toggle_line_numbers(
18532 &mut self,
18533 _: &ToggleLineNumbers,
18534 _: &mut Window,
18535 cx: &mut Context<Self>,
18536 ) {
18537 let mut editor_settings = EditorSettings::get_global(cx).clone();
18538 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18539 EditorSettings::override_global(editor_settings, cx);
18540 }
18541
18542 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18543 if let Some(show_line_numbers) = self.show_line_numbers {
18544 return show_line_numbers;
18545 }
18546 EditorSettings::get_global(cx).gutter.line_numbers
18547 }
18548
18549 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18550 self.use_relative_line_numbers
18551 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18552 }
18553
18554 pub fn toggle_relative_line_numbers(
18555 &mut self,
18556 _: &ToggleRelativeLineNumbers,
18557 _: &mut Window,
18558 cx: &mut Context<Self>,
18559 ) {
18560 let is_relative = self.should_use_relative_line_numbers(cx);
18561 self.set_relative_line_number(Some(!is_relative), cx)
18562 }
18563
18564 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18565 self.use_relative_line_numbers = is_relative;
18566 cx.notify();
18567 }
18568
18569 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18570 self.show_gutter = show_gutter;
18571 cx.notify();
18572 }
18573
18574 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18575 self.show_scrollbars = ScrollbarAxes {
18576 horizontal: show,
18577 vertical: show,
18578 };
18579 cx.notify();
18580 }
18581
18582 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18583 self.show_scrollbars.vertical = show;
18584 cx.notify();
18585 }
18586
18587 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18588 self.show_scrollbars.horizontal = show;
18589 cx.notify();
18590 }
18591
18592 pub fn set_minimap_visibility(
18593 &mut self,
18594 minimap_visibility: MinimapVisibility,
18595 window: &mut Window,
18596 cx: &mut Context<Self>,
18597 ) {
18598 if self.minimap_visibility != minimap_visibility {
18599 if minimap_visibility.visible() && self.minimap.is_none() {
18600 let minimap_settings = EditorSettings::get_global(cx).minimap;
18601 self.minimap =
18602 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18603 }
18604 self.minimap_visibility = minimap_visibility;
18605 cx.notify();
18606 }
18607 }
18608
18609 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18610 self.set_show_scrollbars(false, cx);
18611 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18612 }
18613
18614 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18615 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18616 }
18617
18618 /// Normally the text in full mode and auto height editors is padded on the
18619 /// left side by roughly half a character width for improved hit testing.
18620 ///
18621 /// Use this method to disable this for cases where this is not wanted (e.g.
18622 /// if you want to align the editor text with some other text above or below)
18623 /// or if you want to add this padding to single-line editors.
18624 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18625 self.offset_content = offset_content;
18626 cx.notify();
18627 }
18628
18629 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18630 self.show_line_numbers = Some(show_line_numbers);
18631 cx.notify();
18632 }
18633
18634 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18635 self.disable_expand_excerpt_buttons = true;
18636 cx.notify();
18637 }
18638
18639 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18640 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18641 cx.notify();
18642 }
18643
18644 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18645 self.show_code_actions = Some(show_code_actions);
18646 cx.notify();
18647 }
18648
18649 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18650 self.show_runnables = Some(show_runnables);
18651 cx.notify();
18652 }
18653
18654 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18655 self.show_breakpoints = Some(show_breakpoints);
18656 cx.notify();
18657 }
18658
18659 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18660 if self.display_map.read(cx).masked != masked {
18661 self.display_map.update(cx, |map, _| map.masked = masked);
18662 }
18663 cx.notify()
18664 }
18665
18666 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18667 self.show_wrap_guides = Some(show_wrap_guides);
18668 cx.notify();
18669 }
18670
18671 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18672 self.show_indent_guides = Some(show_indent_guides);
18673 cx.notify();
18674 }
18675
18676 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18677 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18678 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
18679 && let Some(dir) = file.abs_path(cx).parent()
18680 {
18681 return Some(dir.to_owned());
18682 }
18683
18684 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18685 return Some(project_path.path.to_path_buf());
18686 }
18687 }
18688
18689 None
18690 }
18691
18692 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18693 self.active_excerpt(cx)?
18694 .1
18695 .read(cx)
18696 .file()
18697 .and_then(|f| f.as_local())
18698 }
18699
18700 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18701 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18702 let buffer = buffer.read(cx);
18703 if let Some(project_path) = buffer.project_path(cx) {
18704 let project = self.project()?.read(cx);
18705 project.absolute_path(&project_path, cx)
18706 } else {
18707 buffer
18708 .file()
18709 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18710 }
18711 })
18712 }
18713
18714 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18715 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18716 let project_path = buffer.read(cx).project_path(cx)?;
18717 let project = self.project()?.read(cx);
18718 let entry = project.entry_for_path(&project_path, cx)?;
18719 let path = entry.path.to_path_buf();
18720 Some(path)
18721 })
18722 }
18723
18724 pub fn reveal_in_finder(
18725 &mut self,
18726 _: &RevealInFileManager,
18727 _window: &mut Window,
18728 cx: &mut Context<Self>,
18729 ) {
18730 if let Some(target) = self.target_file(cx) {
18731 cx.reveal_path(&target.abs_path(cx));
18732 }
18733 }
18734
18735 pub fn copy_path(
18736 &mut self,
18737 _: &zed_actions::workspace::CopyPath,
18738 _window: &mut Window,
18739 cx: &mut Context<Self>,
18740 ) {
18741 if let Some(path) = self.target_file_abs_path(cx)
18742 && let Some(path) = path.to_str()
18743 {
18744 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18745 }
18746 }
18747
18748 pub fn copy_relative_path(
18749 &mut self,
18750 _: &zed_actions::workspace::CopyRelativePath,
18751 _window: &mut Window,
18752 cx: &mut Context<Self>,
18753 ) {
18754 if let Some(path) = self.target_file_path(cx)
18755 && let Some(path) = path.to_str()
18756 {
18757 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18758 }
18759 }
18760
18761 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18762 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18763 buffer.read(cx).project_path(cx)
18764 } else {
18765 None
18766 }
18767 }
18768
18769 // Returns true if the editor handled a go-to-line request
18770 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18771 maybe!({
18772 let breakpoint_store = self.breakpoint_store.as_ref()?;
18773
18774 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18775 else {
18776 self.clear_row_highlights::<ActiveDebugLine>();
18777 return None;
18778 };
18779
18780 let position = active_stack_frame.position;
18781 let buffer_id = position.buffer_id?;
18782 let snapshot = self
18783 .project
18784 .as_ref()?
18785 .read(cx)
18786 .buffer_for_id(buffer_id, cx)?
18787 .read(cx)
18788 .snapshot();
18789
18790 let mut handled = false;
18791 for (id, ExcerptRange { context, .. }) in
18792 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18793 {
18794 if context.start.cmp(&position, &snapshot).is_ge()
18795 || context.end.cmp(&position, &snapshot).is_lt()
18796 {
18797 continue;
18798 }
18799 let snapshot = self.buffer.read(cx).snapshot(cx);
18800 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18801
18802 handled = true;
18803 self.clear_row_highlights::<ActiveDebugLine>();
18804
18805 self.go_to_line::<ActiveDebugLine>(
18806 multibuffer_anchor,
18807 Some(cx.theme().colors().editor_debugger_active_line_background),
18808 window,
18809 cx,
18810 );
18811
18812 cx.notify();
18813 }
18814
18815 handled.then_some(())
18816 })
18817 .is_some()
18818 }
18819
18820 pub fn copy_file_name_without_extension(
18821 &mut self,
18822 _: &CopyFileNameWithoutExtension,
18823 _: &mut Window,
18824 cx: &mut Context<Self>,
18825 ) {
18826 if let Some(file) = self.target_file(cx)
18827 && let Some(file_stem) = file.path().file_stem()
18828 && let Some(name) = file_stem.to_str()
18829 {
18830 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18831 }
18832 }
18833
18834 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18835 if let Some(file) = self.target_file(cx)
18836 && let Some(file_name) = file.path().file_name()
18837 && let Some(name) = file_name.to_str()
18838 {
18839 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18840 }
18841 }
18842
18843 pub fn toggle_git_blame(
18844 &mut self,
18845 _: &::git::Blame,
18846 window: &mut Window,
18847 cx: &mut Context<Self>,
18848 ) {
18849 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18850
18851 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18852 self.start_git_blame(true, window, cx);
18853 }
18854
18855 cx.notify();
18856 }
18857
18858 pub fn toggle_git_blame_inline(
18859 &mut self,
18860 _: &ToggleGitBlameInline,
18861 window: &mut Window,
18862 cx: &mut Context<Self>,
18863 ) {
18864 self.toggle_git_blame_inline_internal(true, window, cx);
18865 cx.notify();
18866 }
18867
18868 pub fn open_git_blame_commit(
18869 &mut self,
18870 _: &OpenGitBlameCommit,
18871 window: &mut Window,
18872 cx: &mut Context<Self>,
18873 ) {
18874 self.open_git_blame_commit_internal(window, cx);
18875 }
18876
18877 fn open_git_blame_commit_internal(
18878 &mut self,
18879 window: &mut Window,
18880 cx: &mut Context<Self>,
18881 ) -> Option<()> {
18882 let blame = self.blame.as_ref()?;
18883 let snapshot = self.snapshot(window, cx);
18884 let cursor = self.selections.newest::<Point>(cx).head();
18885 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18886 let blame_entry = blame
18887 .update(cx, |blame, cx| {
18888 blame
18889 .blame_for_rows(
18890 &[RowInfo {
18891 buffer_id: Some(buffer.remote_id()),
18892 buffer_row: Some(point.row),
18893 ..Default::default()
18894 }],
18895 cx,
18896 )
18897 .next()
18898 })
18899 .flatten()?;
18900 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18901 let repo = blame.read(cx).repository(cx)?;
18902 let workspace = self.workspace()?.downgrade();
18903 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18904 None
18905 }
18906
18907 pub fn git_blame_inline_enabled(&self) -> bool {
18908 self.git_blame_inline_enabled
18909 }
18910
18911 pub fn toggle_selection_menu(
18912 &mut self,
18913 _: &ToggleSelectionMenu,
18914 _: &mut Window,
18915 cx: &mut Context<Self>,
18916 ) {
18917 self.show_selection_menu = self
18918 .show_selection_menu
18919 .map(|show_selections_menu| !show_selections_menu)
18920 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18921
18922 cx.notify();
18923 }
18924
18925 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18926 self.show_selection_menu
18927 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18928 }
18929
18930 fn start_git_blame(
18931 &mut self,
18932 user_triggered: bool,
18933 window: &mut Window,
18934 cx: &mut Context<Self>,
18935 ) {
18936 if let Some(project) = self.project() {
18937 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18938 return;
18939 };
18940
18941 if buffer.read(cx).file().is_none() {
18942 return;
18943 }
18944
18945 let focused = self.focus_handle(cx).contains_focused(window, cx);
18946
18947 let project = project.clone();
18948 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18949 self.blame_subscription =
18950 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18951 self.blame = Some(blame);
18952 }
18953 }
18954
18955 fn toggle_git_blame_inline_internal(
18956 &mut self,
18957 user_triggered: bool,
18958 window: &mut Window,
18959 cx: &mut Context<Self>,
18960 ) {
18961 if self.git_blame_inline_enabled {
18962 self.git_blame_inline_enabled = false;
18963 self.show_git_blame_inline = false;
18964 self.show_git_blame_inline_delay_task.take();
18965 } else {
18966 self.git_blame_inline_enabled = true;
18967 self.start_git_blame_inline(user_triggered, window, cx);
18968 }
18969
18970 cx.notify();
18971 }
18972
18973 fn start_git_blame_inline(
18974 &mut self,
18975 user_triggered: bool,
18976 window: &mut Window,
18977 cx: &mut Context<Self>,
18978 ) {
18979 self.start_git_blame(user_triggered, window, cx);
18980
18981 if ProjectSettings::get_global(cx)
18982 .git
18983 .inline_blame_delay()
18984 .is_some()
18985 {
18986 self.start_inline_blame_timer(window, cx);
18987 } else {
18988 self.show_git_blame_inline = true
18989 }
18990 }
18991
18992 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18993 self.blame.as_ref()
18994 }
18995
18996 pub fn show_git_blame_gutter(&self) -> bool {
18997 self.show_git_blame_gutter
18998 }
18999
19000 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19001 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19002 }
19003
19004 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19005 self.show_git_blame_inline
19006 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19007 && !self.newest_selection_head_on_empty_line(cx)
19008 && self.has_blame_entries(cx)
19009 }
19010
19011 fn has_blame_entries(&self, cx: &App) -> bool {
19012 self.blame()
19013 .map_or(false, |blame| blame.read(cx).has_generated_entries())
19014 }
19015
19016 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19017 let cursor_anchor = self.selections.newest_anchor().head();
19018
19019 let snapshot = self.buffer.read(cx).snapshot(cx);
19020 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19021
19022 snapshot.line_len(buffer_row) == 0
19023 }
19024
19025 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19026 let buffer_and_selection = maybe!({
19027 let selection = self.selections.newest::<Point>(cx);
19028 let selection_range = selection.range();
19029
19030 let multi_buffer = self.buffer().read(cx);
19031 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19032 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19033
19034 let (buffer, range, _) = if selection.reversed {
19035 buffer_ranges.first()
19036 } else {
19037 buffer_ranges.last()
19038 }?;
19039
19040 let selection = text::ToPoint::to_point(&range.start, buffer).row
19041 ..text::ToPoint::to_point(&range.end, buffer).row;
19042 Some((
19043 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
19044 selection,
19045 ))
19046 });
19047
19048 let Some((buffer, selection)) = buffer_and_selection else {
19049 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19050 };
19051
19052 let Some(project) = self.project() else {
19053 return Task::ready(Err(anyhow!("editor does not have project")));
19054 };
19055
19056 project.update(cx, |project, cx| {
19057 project.get_permalink_to_line(&buffer, selection, cx)
19058 })
19059 }
19060
19061 pub fn copy_permalink_to_line(
19062 &mut self,
19063 _: &CopyPermalinkToLine,
19064 window: &mut Window,
19065 cx: &mut Context<Self>,
19066 ) {
19067 let permalink_task = self.get_permalink_to_line(cx);
19068 let workspace = self.workspace();
19069
19070 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19071 Ok(permalink) => {
19072 cx.update(|_, cx| {
19073 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19074 })
19075 .ok();
19076 }
19077 Err(err) => {
19078 let message = format!("Failed to copy permalink: {err}");
19079
19080 anyhow::Result::<()>::Err(err).log_err();
19081
19082 if let Some(workspace) = workspace {
19083 workspace
19084 .update_in(cx, |workspace, _, cx| {
19085 struct CopyPermalinkToLine;
19086
19087 workspace.show_toast(
19088 Toast::new(
19089 NotificationId::unique::<CopyPermalinkToLine>(),
19090 message,
19091 ),
19092 cx,
19093 )
19094 })
19095 .ok();
19096 }
19097 }
19098 })
19099 .detach();
19100 }
19101
19102 pub fn copy_file_location(
19103 &mut self,
19104 _: &CopyFileLocation,
19105 _: &mut Window,
19106 cx: &mut Context<Self>,
19107 ) {
19108 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19109 if let Some(file) = self.target_file(cx)
19110 && let Some(path) = file.path().to_str()
19111 {
19112 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19113 }
19114 }
19115
19116 pub fn open_permalink_to_line(
19117 &mut self,
19118 _: &OpenPermalinkToLine,
19119 window: &mut Window,
19120 cx: &mut Context<Self>,
19121 ) {
19122 let permalink_task = self.get_permalink_to_line(cx);
19123 let workspace = self.workspace();
19124
19125 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19126 Ok(permalink) => {
19127 cx.update(|_, cx| {
19128 cx.open_url(permalink.as_ref());
19129 })
19130 .ok();
19131 }
19132 Err(err) => {
19133 let message = format!("Failed to open permalink: {err}");
19134
19135 anyhow::Result::<()>::Err(err).log_err();
19136
19137 if let Some(workspace) = workspace {
19138 workspace
19139 .update(cx, |workspace, cx| {
19140 struct OpenPermalinkToLine;
19141
19142 workspace.show_toast(
19143 Toast::new(
19144 NotificationId::unique::<OpenPermalinkToLine>(),
19145 message,
19146 ),
19147 cx,
19148 )
19149 })
19150 .ok();
19151 }
19152 }
19153 })
19154 .detach();
19155 }
19156
19157 pub fn insert_uuid_v4(
19158 &mut self,
19159 _: &InsertUuidV4,
19160 window: &mut Window,
19161 cx: &mut Context<Self>,
19162 ) {
19163 self.insert_uuid(UuidVersion::V4, window, cx);
19164 }
19165
19166 pub fn insert_uuid_v7(
19167 &mut self,
19168 _: &InsertUuidV7,
19169 window: &mut Window,
19170 cx: &mut Context<Self>,
19171 ) {
19172 self.insert_uuid(UuidVersion::V7, window, cx);
19173 }
19174
19175 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19176 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19177 self.transact(window, cx, |this, window, cx| {
19178 let edits = this
19179 .selections
19180 .all::<Point>(cx)
19181 .into_iter()
19182 .map(|selection| {
19183 let uuid = match version {
19184 UuidVersion::V4 => uuid::Uuid::new_v4(),
19185 UuidVersion::V7 => uuid::Uuid::now_v7(),
19186 };
19187
19188 (selection.range(), uuid.to_string())
19189 });
19190 this.edit(edits, cx);
19191 this.refresh_edit_prediction(true, false, window, cx);
19192 });
19193 }
19194
19195 pub fn open_selections_in_multibuffer(
19196 &mut self,
19197 _: &OpenSelectionsInMultibuffer,
19198 window: &mut Window,
19199 cx: &mut Context<Self>,
19200 ) {
19201 let multibuffer = self.buffer.read(cx);
19202
19203 let Some(buffer) = multibuffer.as_singleton() else {
19204 return;
19205 };
19206
19207 let Some(workspace) = self.workspace() else {
19208 return;
19209 };
19210
19211 let title = multibuffer.title(cx).to_string();
19212
19213 let locations = self
19214 .selections
19215 .all_anchors(cx)
19216 .into_iter()
19217 .map(|selection| Location {
19218 buffer: buffer.clone(),
19219 range: selection.start.text_anchor..selection.end.text_anchor,
19220 })
19221 .collect::<Vec<_>>();
19222
19223 cx.spawn_in(window, async move |_, cx| {
19224 workspace.update_in(cx, |workspace, window, cx| {
19225 Self::open_locations_in_multibuffer(
19226 workspace,
19227 locations,
19228 format!("Selections for '{title}'"),
19229 false,
19230 MultibufferSelectionMode::All,
19231 window,
19232 cx,
19233 );
19234 })
19235 })
19236 .detach();
19237 }
19238
19239 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19240 /// last highlight added will be used.
19241 ///
19242 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19243 pub fn highlight_rows<T: 'static>(
19244 &mut self,
19245 range: Range<Anchor>,
19246 color: Hsla,
19247 options: RowHighlightOptions,
19248 cx: &mut Context<Self>,
19249 ) {
19250 let snapshot = self.buffer().read(cx).snapshot(cx);
19251 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19252 let ix = row_highlights.binary_search_by(|highlight| {
19253 Ordering::Equal
19254 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19255 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19256 });
19257
19258 if let Err(mut ix) = ix {
19259 let index = post_inc(&mut self.highlight_order);
19260
19261 // If this range intersects with the preceding highlight, then merge it with
19262 // the preceding highlight. Otherwise insert a new highlight.
19263 let mut merged = false;
19264 if ix > 0 {
19265 let prev_highlight = &mut row_highlights[ix - 1];
19266 if prev_highlight
19267 .range
19268 .end
19269 .cmp(&range.start, &snapshot)
19270 .is_ge()
19271 {
19272 ix -= 1;
19273 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19274 prev_highlight.range.end = range.end;
19275 }
19276 merged = true;
19277 prev_highlight.index = index;
19278 prev_highlight.color = color;
19279 prev_highlight.options = options;
19280 }
19281 }
19282
19283 if !merged {
19284 row_highlights.insert(
19285 ix,
19286 RowHighlight {
19287 range: range.clone(),
19288 index,
19289 color,
19290 options,
19291 type_id: TypeId::of::<T>(),
19292 },
19293 );
19294 }
19295
19296 // If any of the following highlights intersect with this one, merge them.
19297 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19298 let highlight = &row_highlights[ix];
19299 if next_highlight
19300 .range
19301 .start
19302 .cmp(&highlight.range.end, &snapshot)
19303 .is_le()
19304 {
19305 if next_highlight
19306 .range
19307 .end
19308 .cmp(&highlight.range.end, &snapshot)
19309 .is_gt()
19310 {
19311 row_highlights[ix].range.end = next_highlight.range.end;
19312 }
19313 row_highlights.remove(ix + 1);
19314 } else {
19315 break;
19316 }
19317 }
19318 }
19319 }
19320
19321 /// Remove any highlighted row ranges of the given type that intersect the
19322 /// given ranges.
19323 pub fn remove_highlighted_rows<T: 'static>(
19324 &mut self,
19325 ranges_to_remove: Vec<Range<Anchor>>,
19326 cx: &mut Context<Self>,
19327 ) {
19328 let snapshot = self.buffer().read(cx).snapshot(cx);
19329 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19330 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19331 row_highlights.retain(|highlight| {
19332 while let Some(range_to_remove) = ranges_to_remove.peek() {
19333 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19334 Ordering::Less | Ordering::Equal => {
19335 ranges_to_remove.next();
19336 }
19337 Ordering::Greater => {
19338 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19339 Ordering::Less | Ordering::Equal => {
19340 return false;
19341 }
19342 Ordering::Greater => break,
19343 }
19344 }
19345 }
19346 }
19347
19348 true
19349 })
19350 }
19351
19352 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19353 pub fn clear_row_highlights<T: 'static>(&mut self) {
19354 self.highlighted_rows.remove(&TypeId::of::<T>());
19355 }
19356
19357 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19358 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19359 self.highlighted_rows
19360 .get(&TypeId::of::<T>())
19361 .map_or(&[] as &[_], |vec| vec.as_slice())
19362 .iter()
19363 .map(|highlight| (highlight.range.clone(), highlight.color))
19364 }
19365
19366 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19367 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19368 /// Allows to ignore certain kinds of highlights.
19369 pub fn highlighted_display_rows(
19370 &self,
19371 window: &mut Window,
19372 cx: &mut App,
19373 ) -> BTreeMap<DisplayRow, LineHighlight> {
19374 let snapshot = self.snapshot(window, cx);
19375 let mut used_highlight_orders = HashMap::default();
19376 self.highlighted_rows
19377 .iter()
19378 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19379 .fold(
19380 BTreeMap::<DisplayRow, LineHighlight>::new(),
19381 |mut unique_rows, highlight| {
19382 let start = highlight.range.start.to_display_point(&snapshot);
19383 let end = highlight.range.end.to_display_point(&snapshot);
19384 let start_row = start.row().0;
19385 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19386 && end.column() == 0
19387 {
19388 end.row().0.saturating_sub(1)
19389 } else {
19390 end.row().0
19391 };
19392 for row in start_row..=end_row {
19393 let used_index =
19394 used_highlight_orders.entry(row).or_insert(highlight.index);
19395 if highlight.index >= *used_index {
19396 *used_index = highlight.index;
19397 unique_rows.insert(
19398 DisplayRow(row),
19399 LineHighlight {
19400 include_gutter: highlight.options.include_gutter,
19401 border: None,
19402 background: highlight.color.into(),
19403 type_id: Some(highlight.type_id),
19404 },
19405 );
19406 }
19407 }
19408 unique_rows
19409 },
19410 )
19411 }
19412
19413 pub fn highlighted_display_row_for_autoscroll(
19414 &self,
19415 snapshot: &DisplaySnapshot,
19416 ) -> Option<DisplayRow> {
19417 self.highlighted_rows
19418 .values()
19419 .flat_map(|highlighted_rows| highlighted_rows.iter())
19420 .filter_map(|highlight| {
19421 if highlight.options.autoscroll {
19422 Some(highlight.range.start.to_display_point(snapshot).row())
19423 } else {
19424 None
19425 }
19426 })
19427 .min()
19428 }
19429
19430 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19431 self.highlight_background::<SearchWithinRange>(
19432 ranges,
19433 |colors| colors.colors().editor_document_highlight_read_background,
19434 cx,
19435 )
19436 }
19437
19438 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19439 self.breadcrumb_header = Some(new_header);
19440 }
19441
19442 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19443 self.clear_background_highlights::<SearchWithinRange>(cx);
19444 }
19445
19446 pub fn highlight_background<T: 'static>(
19447 &mut self,
19448 ranges: &[Range<Anchor>],
19449 color_fetcher: fn(&Theme) -> Hsla,
19450 cx: &mut Context<Self>,
19451 ) {
19452 self.background_highlights.insert(
19453 HighlightKey::Type(TypeId::of::<T>()),
19454 (color_fetcher, Arc::from(ranges)),
19455 );
19456 self.scrollbar_marker_state.dirty = true;
19457 cx.notify();
19458 }
19459
19460 pub fn highlight_background_key<T: 'static>(
19461 &mut self,
19462 key: usize,
19463 ranges: &[Range<Anchor>],
19464 color_fetcher: fn(&Theme) -> Hsla,
19465 cx: &mut Context<Self>,
19466 ) {
19467 self.background_highlights.insert(
19468 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19469 (color_fetcher, Arc::from(ranges)),
19470 );
19471 self.scrollbar_marker_state.dirty = true;
19472 cx.notify();
19473 }
19474
19475 pub fn clear_background_highlights<T: 'static>(
19476 &mut self,
19477 cx: &mut Context<Self>,
19478 ) -> Option<BackgroundHighlight> {
19479 let text_highlights = self
19480 .background_highlights
19481 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19482 if !text_highlights.1.is_empty() {
19483 self.scrollbar_marker_state.dirty = true;
19484 cx.notify();
19485 }
19486 Some(text_highlights)
19487 }
19488
19489 pub fn highlight_gutter<T: 'static>(
19490 &mut self,
19491 ranges: impl Into<Vec<Range<Anchor>>>,
19492 color_fetcher: fn(&App) -> Hsla,
19493 cx: &mut Context<Self>,
19494 ) {
19495 self.gutter_highlights
19496 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19497 cx.notify();
19498 }
19499
19500 pub fn clear_gutter_highlights<T: 'static>(
19501 &mut self,
19502 cx: &mut Context<Self>,
19503 ) -> Option<GutterHighlight> {
19504 cx.notify();
19505 self.gutter_highlights.remove(&TypeId::of::<T>())
19506 }
19507
19508 pub fn insert_gutter_highlight<T: 'static>(
19509 &mut self,
19510 range: Range<Anchor>,
19511 color_fetcher: fn(&App) -> Hsla,
19512 cx: &mut Context<Self>,
19513 ) {
19514 let snapshot = self.buffer().read(cx).snapshot(cx);
19515 let mut highlights = self
19516 .gutter_highlights
19517 .remove(&TypeId::of::<T>())
19518 .map(|(_, highlights)| highlights)
19519 .unwrap_or_default();
19520 let ix = highlights.binary_search_by(|highlight| {
19521 Ordering::Equal
19522 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19523 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19524 });
19525 if let Err(ix) = ix {
19526 highlights.insert(ix, range);
19527 }
19528 self.gutter_highlights
19529 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19530 }
19531
19532 pub fn remove_gutter_highlights<T: 'static>(
19533 &mut self,
19534 ranges_to_remove: Vec<Range<Anchor>>,
19535 cx: &mut Context<Self>,
19536 ) {
19537 let snapshot = self.buffer().read(cx).snapshot(cx);
19538 let Some((color_fetcher, mut gutter_highlights)) =
19539 self.gutter_highlights.remove(&TypeId::of::<T>())
19540 else {
19541 return;
19542 };
19543 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19544 gutter_highlights.retain(|highlight| {
19545 while let Some(range_to_remove) = ranges_to_remove.peek() {
19546 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19547 Ordering::Less | Ordering::Equal => {
19548 ranges_to_remove.next();
19549 }
19550 Ordering::Greater => {
19551 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19552 Ordering::Less | Ordering::Equal => {
19553 return false;
19554 }
19555 Ordering::Greater => break,
19556 }
19557 }
19558 }
19559 }
19560
19561 true
19562 });
19563 self.gutter_highlights
19564 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19565 }
19566
19567 #[cfg(feature = "test-support")]
19568 pub fn all_text_highlights(
19569 &self,
19570 window: &mut Window,
19571 cx: &mut Context<Self>,
19572 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19573 let snapshot = self.snapshot(window, cx);
19574 self.display_map.update(cx, |display_map, _| {
19575 display_map
19576 .all_text_highlights()
19577 .map(|highlight| {
19578 let (style, ranges) = highlight.as_ref();
19579 (
19580 *style,
19581 ranges
19582 .iter()
19583 .map(|range| range.clone().to_display_points(&snapshot))
19584 .collect(),
19585 )
19586 })
19587 .collect()
19588 })
19589 }
19590
19591 #[cfg(feature = "test-support")]
19592 pub fn all_text_background_highlights(
19593 &self,
19594 window: &mut Window,
19595 cx: &mut Context<Self>,
19596 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19597 let snapshot = self.snapshot(window, cx);
19598 let buffer = &snapshot.buffer_snapshot;
19599 let start = buffer.anchor_before(0);
19600 let end = buffer.anchor_after(buffer.len());
19601 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19602 }
19603
19604 #[cfg(feature = "test-support")]
19605 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19606 let snapshot = self.buffer().read(cx).snapshot(cx);
19607
19608 let highlights = self
19609 .background_highlights
19610 .get(&HighlightKey::Type(TypeId::of::<
19611 items::BufferSearchHighlights,
19612 >()));
19613
19614 if let Some((_color, ranges)) = highlights {
19615 ranges
19616 .iter()
19617 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19618 .collect_vec()
19619 } else {
19620 vec![]
19621 }
19622 }
19623
19624 fn document_highlights_for_position<'a>(
19625 &'a self,
19626 position: Anchor,
19627 buffer: &'a MultiBufferSnapshot,
19628 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19629 let read_highlights = self
19630 .background_highlights
19631 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19632 .map(|h| &h.1);
19633 let write_highlights = self
19634 .background_highlights
19635 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19636 .map(|h| &h.1);
19637 let left_position = position.bias_left(buffer);
19638 let right_position = position.bias_right(buffer);
19639 read_highlights
19640 .into_iter()
19641 .chain(write_highlights)
19642 .flat_map(move |ranges| {
19643 let start_ix = match ranges.binary_search_by(|probe| {
19644 let cmp = probe.end.cmp(&left_position, buffer);
19645 if cmp.is_ge() {
19646 Ordering::Greater
19647 } else {
19648 Ordering::Less
19649 }
19650 }) {
19651 Ok(i) | Err(i) => i,
19652 };
19653
19654 ranges[start_ix..]
19655 .iter()
19656 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19657 })
19658 }
19659
19660 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19661 self.background_highlights
19662 .get(&HighlightKey::Type(TypeId::of::<T>()))
19663 .map_or(false, |(_, highlights)| !highlights.is_empty())
19664 }
19665
19666 pub fn background_highlights_in_range(
19667 &self,
19668 search_range: Range<Anchor>,
19669 display_snapshot: &DisplaySnapshot,
19670 theme: &Theme,
19671 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19672 let mut results = Vec::new();
19673 for (color_fetcher, ranges) in self.background_highlights.values() {
19674 let color = color_fetcher(theme);
19675 let start_ix = match ranges.binary_search_by(|probe| {
19676 let cmp = probe
19677 .end
19678 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19679 if cmp.is_gt() {
19680 Ordering::Greater
19681 } else {
19682 Ordering::Less
19683 }
19684 }) {
19685 Ok(i) | Err(i) => i,
19686 };
19687 for range in &ranges[start_ix..] {
19688 if range
19689 .start
19690 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19691 .is_ge()
19692 {
19693 break;
19694 }
19695
19696 let start = range.start.to_display_point(display_snapshot);
19697 let end = range.end.to_display_point(display_snapshot);
19698 results.push((start..end, color))
19699 }
19700 }
19701 results
19702 }
19703
19704 pub fn background_highlight_row_ranges<T: 'static>(
19705 &self,
19706 search_range: Range<Anchor>,
19707 display_snapshot: &DisplaySnapshot,
19708 count: usize,
19709 ) -> Vec<RangeInclusive<DisplayPoint>> {
19710 let mut results = Vec::new();
19711 let Some((_, ranges)) = self
19712 .background_highlights
19713 .get(&HighlightKey::Type(TypeId::of::<T>()))
19714 else {
19715 return vec![];
19716 };
19717
19718 let start_ix = match ranges.binary_search_by(|probe| {
19719 let cmp = probe
19720 .end
19721 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19722 if cmp.is_gt() {
19723 Ordering::Greater
19724 } else {
19725 Ordering::Less
19726 }
19727 }) {
19728 Ok(i) | Err(i) => i,
19729 };
19730 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19731 if let (Some(start_display), Some(end_display)) = (start, end) {
19732 results.push(
19733 start_display.to_display_point(display_snapshot)
19734 ..=end_display.to_display_point(display_snapshot),
19735 );
19736 }
19737 };
19738 let mut start_row: Option<Point> = None;
19739 let mut end_row: Option<Point> = None;
19740 if ranges.len() > count {
19741 return Vec::new();
19742 }
19743 for range in &ranges[start_ix..] {
19744 if range
19745 .start
19746 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19747 .is_ge()
19748 {
19749 break;
19750 }
19751 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19752 if let Some(current_row) = &end_row
19753 && end.row == current_row.row
19754 {
19755 continue;
19756 }
19757 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19758 if start_row.is_none() {
19759 assert_eq!(end_row, None);
19760 start_row = Some(start);
19761 end_row = Some(end);
19762 continue;
19763 }
19764 if let Some(current_end) = end_row.as_mut() {
19765 if start.row > current_end.row + 1 {
19766 push_region(start_row, end_row);
19767 start_row = Some(start);
19768 end_row = Some(end);
19769 } else {
19770 // Merge two hunks.
19771 *current_end = end;
19772 }
19773 } else {
19774 unreachable!();
19775 }
19776 }
19777 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19778 push_region(start_row, end_row);
19779 results
19780 }
19781
19782 pub fn gutter_highlights_in_range(
19783 &self,
19784 search_range: Range<Anchor>,
19785 display_snapshot: &DisplaySnapshot,
19786 cx: &App,
19787 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19788 let mut results = Vec::new();
19789 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19790 let color = color_fetcher(cx);
19791 let start_ix = match ranges.binary_search_by(|probe| {
19792 let cmp = probe
19793 .end
19794 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19795 if cmp.is_gt() {
19796 Ordering::Greater
19797 } else {
19798 Ordering::Less
19799 }
19800 }) {
19801 Ok(i) | Err(i) => i,
19802 };
19803 for range in &ranges[start_ix..] {
19804 if range
19805 .start
19806 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19807 .is_ge()
19808 {
19809 break;
19810 }
19811
19812 let start = range.start.to_display_point(display_snapshot);
19813 let end = range.end.to_display_point(display_snapshot);
19814 results.push((start..end, color))
19815 }
19816 }
19817 results
19818 }
19819
19820 /// Get the text ranges corresponding to the redaction query
19821 pub fn redacted_ranges(
19822 &self,
19823 search_range: Range<Anchor>,
19824 display_snapshot: &DisplaySnapshot,
19825 cx: &App,
19826 ) -> Vec<Range<DisplayPoint>> {
19827 display_snapshot
19828 .buffer_snapshot
19829 .redacted_ranges(search_range, |file| {
19830 if let Some(file) = file {
19831 file.is_private()
19832 && EditorSettings::get(
19833 Some(SettingsLocation {
19834 worktree_id: file.worktree_id(cx),
19835 path: file.path().as_ref(),
19836 }),
19837 cx,
19838 )
19839 .redact_private_values
19840 } else {
19841 false
19842 }
19843 })
19844 .map(|range| {
19845 range.start.to_display_point(display_snapshot)
19846 ..range.end.to_display_point(display_snapshot)
19847 })
19848 .collect()
19849 }
19850
19851 pub fn highlight_text_key<T: 'static>(
19852 &mut self,
19853 key: usize,
19854 ranges: Vec<Range<Anchor>>,
19855 style: HighlightStyle,
19856 cx: &mut Context<Self>,
19857 ) {
19858 self.display_map.update(cx, |map, _| {
19859 map.highlight_text(
19860 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19861 ranges,
19862 style,
19863 );
19864 });
19865 cx.notify();
19866 }
19867
19868 pub fn highlight_text<T: 'static>(
19869 &mut self,
19870 ranges: Vec<Range<Anchor>>,
19871 style: HighlightStyle,
19872 cx: &mut Context<Self>,
19873 ) {
19874 self.display_map.update(cx, |map, _| {
19875 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19876 });
19877 cx.notify();
19878 }
19879
19880 pub(crate) fn highlight_inlays<T: 'static>(
19881 &mut self,
19882 highlights: Vec<InlayHighlight>,
19883 style: HighlightStyle,
19884 cx: &mut Context<Self>,
19885 ) {
19886 self.display_map.update(cx, |map, _| {
19887 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19888 });
19889 cx.notify();
19890 }
19891
19892 pub fn text_highlights<'a, T: 'static>(
19893 &'a self,
19894 cx: &'a App,
19895 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19896 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19897 }
19898
19899 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19900 let cleared = self
19901 .display_map
19902 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19903 if cleared {
19904 cx.notify();
19905 }
19906 }
19907
19908 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19909 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19910 && self.focus_handle.is_focused(window)
19911 }
19912
19913 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19914 self.show_cursor_when_unfocused = is_enabled;
19915 cx.notify();
19916 }
19917
19918 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19919 cx.notify();
19920 }
19921
19922 fn on_debug_session_event(
19923 &mut self,
19924 _session: Entity<Session>,
19925 event: &SessionEvent,
19926 cx: &mut Context<Self>,
19927 ) {
19928 match event {
19929 SessionEvent::InvalidateInlineValue => {
19930 self.refresh_inline_values(cx);
19931 }
19932 _ => {}
19933 }
19934 }
19935
19936 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19937 let Some(project) = self.project.clone() else {
19938 return;
19939 };
19940
19941 if !self.inline_value_cache.enabled {
19942 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19943 self.splice_inlays(&inlays, Vec::new(), cx);
19944 return;
19945 }
19946
19947 let current_execution_position = self
19948 .highlighted_rows
19949 .get(&TypeId::of::<ActiveDebugLine>())
19950 .and_then(|lines| lines.last().map(|line| line.range.end));
19951
19952 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19953 let inline_values = editor
19954 .update(cx, |editor, cx| {
19955 let Some(current_execution_position) = current_execution_position else {
19956 return Some(Task::ready(Ok(Vec::new())));
19957 };
19958
19959 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19960 let snapshot = buffer.snapshot(cx);
19961
19962 let excerpt = snapshot.excerpt_containing(
19963 current_execution_position..current_execution_position,
19964 )?;
19965
19966 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19967 })?;
19968
19969 let range =
19970 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19971
19972 project.inline_values(buffer, range, cx)
19973 })
19974 .ok()
19975 .flatten()?
19976 .await
19977 .context("refreshing debugger inlays")
19978 .log_err()?;
19979
19980 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19981
19982 for (buffer_id, inline_value) in inline_values
19983 .into_iter()
19984 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19985 {
19986 buffer_inline_values
19987 .entry(buffer_id)
19988 .or_default()
19989 .push(inline_value);
19990 }
19991
19992 editor
19993 .update(cx, |editor, cx| {
19994 let snapshot = editor.buffer.read(cx).snapshot(cx);
19995 let mut new_inlays = Vec::default();
19996
19997 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19998 let buffer_id = buffer_snapshot.remote_id();
19999 buffer_inline_values
20000 .get(&buffer_id)
20001 .into_iter()
20002 .flatten()
20003 .for_each(|hint| {
20004 let inlay = Inlay::debugger(
20005 post_inc(&mut editor.next_inlay_id),
20006 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20007 hint.text(),
20008 );
20009 if !inlay.text.chars().contains(&'\n') {
20010 new_inlays.push(inlay);
20011 }
20012 });
20013 }
20014
20015 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20016 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20017
20018 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20019 })
20020 .ok()?;
20021 Some(())
20022 });
20023 }
20024
20025 fn on_buffer_event(
20026 &mut self,
20027 multibuffer: &Entity<MultiBuffer>,
20028 event: &multi_buffer::Event,
20029 window: &mut Window,
20030 cx: &mut Context<Self>,
20031 ) {
20032 match event {
20033 multi_buffer::Event::Edited {
20034 singleton_buffer_edited,
20035 edited_buffer,
20036 } => {
20037 self.scrollbar_marker_state.dirty = true;
20038 self.active_indent_guides_state.dirty = true;
20039 self.refresh_active_diagnostics(cx);
20040 self.refresh_code_actions(window, cx);
20041 self.refresh_selected_text_highlights(true, window, cx);
20042 self.refresh_single_line_folds(window, cx);
20043 refresh_matching_bracket_highlights(self, window, cx);
20044 if self.has_active_edit_prediction() {
20045 self.update_visible_edit_prediction(window, cx);
20046 }
20047 if let Some(project) = self.project.as_ref()
20048 && let Some(edited_buffer) = edited_buffer
20049 {
20050 project.update(cx, |project, cx| {
20051 self.registered_buffers
20052 .entry(edited_buffer.read(cx).remote_id())
20053 .or_insert_with(|| {
20054 project.register_buffer_with_language_servers(edited_buffer, cx)
20055 });
20056 });
20057 }
20058 cx.emit(EditorEvent::BufferEdited);
20059 cx.emit(SearchEvent::MatchesInvalidated);
20060
20061 if let Some(buffer) = edited_buffer {
20062 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20063 }
20064
20065 if *singleton_buffer_edited {
20066 if let Some(buffer) = edited_buffer
20067 && buffer.read(cx).file().is_none()
20068 {
20069 cx.emit(EditorEvent::TitleChanged);
20070 }
20071 if let Some(project) = &self.project {
20072 #[allow(clippy::mutable_key_type)]
20073 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20074 multibuffer
20075 .all_buffers()
20076 .into_iter()
20077 .filter_map(|buffer| {
20078 buffer.update(cx, |buffer, cx| {
20079 let language = buffer.language()?;
20080 let should_discard = project.update(cx, |project, cx| {
20081 project.is_local()
20082 && !project.has_language_servers_for(buffer, cx)
20083 });
20084 should_discard.not().then_some(language.clone())
20085 })
20086 })
20087 .collect::<HashSet<_>>()
20088 });
20089 if !languages_affected.is_empty() {
20090 self.refresh_inlay_hints(
20091 InlayHintRefreshReason::BufferEdited(languages_affected),
20092 cx,
20093 );
20094 }
20095 }
20096 }
20097
20098 let Some(project) = &self.project else { return };
20099 let (telemetry, is_via_ssh) = {
20100 let project = project.read(cx);
20101 let telemetry = project.client().telemetry().clone();
20102 let is_via_ssh = project.is_via_ssh();
20103 (telemetry, is_via_ssh)
20104 };
20105 refresh_linked_ranges(self, window, cx);
20106 telemetry.log_edit_event("editor", is_via_ssh);
20107 }
20108 multi_buffer::Event::ExcerptsAdded {
20109 buffer,
20110 predecessor,
20111 excerpts,
20112 } => {
20113 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20114 let buffer_id = buffer.read(cx).remote_id();
20115 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20116 && let Some(project) = &self.project
20117 {
20118 update_uncommitted_diff_for_buffer(
20119 cx.entity(),
20120 project,
20121 [buffer.clone()],
20122 self.buffer.clone(),
20123 cx,
20124 )
20125 .detach();
20126 }
20127 self.update_lsp_data(false, Some(buffer_id), window, cx);
20128 cx.emit(EditorEvent::ExcerptsAdded {
20129 buffer: buffer.clone(),
20130 predecessor: *predecessor,
20131 excerpts: excerpts.clone(),
20132 });
20133 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20134 }
20135 multi_buffer::Event::ExcerptsRemoved {
20136 ids,
20137 removed_buffer_ids,
20138 } => {
20139 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20140 let buffer = self.buffer.read(cx);
20141 self.registered_buffers
20142 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20143 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20144 cx.emit(EditorEvent::ExcerptsRemoved {
20145 ids: ids.clone(),
20146 removed_buffer_ids: removed_buffer_ids.clone(),
20147 });
20148 }
20149 multi_buffer::Event::ExcerptsEdited {
20150 excerpt_ids,
20151 buffer_ids,
20152 } => {
20153 self.display_map.update(cx, |map, cx| {
20154 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20155 });
20156 cx.emit(EditorEvent::ExcerptsEdited {
20157 ids: excerpt_ids.clone(),
20158 });
20159 }
20160 multi_buffer::Event::ExcerptsExpanded { ids } => {
20161 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20162 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20163 }
20164 multi_buffer::Event::Reparsed(buffer_id) => {
20165 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20166 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20167
20168 cx.emit(EditorEvent::Reparsed(*buffer_id));
20169 }
20170 multi_buffer::Event::DiffHunksToggled => {
20171 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20172 }
20173 multi_buffer::Event::LanguageChanged(buffer_id) => {
20174 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20175 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20176 cx.emit(EditorEvent::Reparsed(*buffer_id));
20177 cx.notify();
20178 }
20179 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20180 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20181 multi_buffer::Event::FileHandleChanged
20182 | multi_buffer::Event::Reloaded
20183 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20184 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20185 multi_buffer::Event::DiagnosticsUpdated => {
20186 self.update_diagnostics_state(window, cx);
20187 }
20188 _ => {}
20189 };
20190 }
20191
20192 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20193 if !self.diagnostics_enabled() {
20194 return;
20195 }
20196 self.refresh_active_diagnostics(cx);
20197 self.refresh_inline_diagnostics(true, window, cx);
20198 self.scrollbar_marker_state.dirty = true;
20199 cx.notify();
20200 }
20201
20202 pub fn start_temporary_diff_override(&mut self) {
20203 self.load_diff_task.take();
20204 self.temporary_diff_override = true;
20205 }
20206
20207 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20208 self.temporary_diff_override = false;
20209 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20210 self.buffer.update(cx, |buffer, cx| {
20211 buffer.set_all_diff_hunks_collapsed(cx);
20212 });
20213
20214 if let Some(project) = self.project.clone() {
20215 self.load_diff_task = Some(
20216 update_uncommitted_diff_for_buffer(
20217 cx.entity(),
20218 &project,
20219 self.buffer.read(cx).all_buffers(),
20220 self.buffer.clone(),
20221 cx,
20222 )
20223 .shared(),
20224 );
20225 }
20226 }
20227
20228 fn on_display_map_changed(
20229 &mut self,
20230 _: Entity<DisplayMap>,
20231 _: &mut Window,
20232 cx: &mut Context<Self>,
20233 ) {
20234 cx.notify();
20235 }
20236
20237 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20238 if self.diagnostics_enabled() {
20239 let new_severity = EditorSettings::get_global(cx)
20240 .diagnostics_max_severity
20241 .unwrap_or(DiagnosticSeverity::Hint);
20242 self.set_max_diagnostics_severity(new_severity, cx);
20243 }
20244 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20245 self.update_edit_prediction_settings(cx);
20246 self.refresh_edit_prediction(true, false, window, cx);
20247 self.refresh_inline_values(cx);
20248 self.refresh_inlay_hints(
20249 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20250 self.selections.newest_anchor().head(),
20251 &self.buffer.read(cx).snapshot(cx),
20252 cx,
20253 )),
20254 cx,
20255 );
20256
20257 let old_cursor_shape = self.cursor_shape;
20258 let old_show_breadcrumbs = self.show_breadcrumbs;
20259
20260 {
20261 let editor_settings = EditorSettings::get_global(cx);
20262 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20263 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20264 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20265 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20266 }
20267
20268 if old_cursor_shape != self.cursor_shape {
20269 cx.emit(EditorEvent::CursorShapeChanged);
20270 }
20271
20272 if old_show_breadcrumbs != self.show_breadcrumbs {
20273 cx.emit(EditorEvent::BreadcrumbsChanged);
20274 }
20275
20276 let project_settings = ProjectSettings::get_global(cx);
20277 self.serialize_dirty_buffers =
20278 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20279
20280 if self.mode.is_full() {
20281 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20282 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20283 if self.show_inline_diagnostics != show_inline_diagnostics {
20284 self.show_inline_diagnostics = show_inline_diagnostics;
20285 self.refresh_inline_diagnostics(false, window, cx);
20286 }
20287
20288 if self.git_blame_inline_enabled != inline_blame_enabled {
20289 self.toggle_git_blame_inline_internal(false, window, cx);
20290 }
20291
20292 let minimap_settings = EditorSettings::get_global(cx).minimap;
20293 if self.minimap_visibility != MinimapVisibility::Disabled {
20294 if self.minimap_visibility.settings_visibility()
20295 != minimap_settings.minimap_enabled()
20296 {
20297 self.set_minimap_visibility(
20298 MinimapVisibility::for_mode(self.mode(), cx),
20299 window,
20300 cx,
20301 );
20302 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20303 minimap_entity.update(cx, |minimap_editor, cx| {
20304 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20305 })
20306 }
20307 }
20308 }
20309
20310 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20311 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20312 }) {
20313 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20314 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20315 }
20316 self.refresh_colors(false, None, window, cx);
20317 }
20318
20319 cx.notify();
20320 }
20321
20322 pub fn set_searchable(&mut self, searchable: bool) {
20323 self.searchable = searchable;
20324 }
20325
20326 pub fn searchable(&self) -> bool {
20327 self.searchable
20328 }
20329
20330 fn open_proposed_changes_editor(
20331 &mut self,
20332 _: &OpenProposedChangesEditor,
20333 window: &mut Window,
20334 cx: &mut Context<Self>,
20335 ) {
20336 let Some(workspace) = self.workspace() else {
20337 cx.propagate();
20338 return;
20339 };
20340
20341 let selections = self.selections.all::<usize>(cx);
20342 let multi_buffer = self.buffer.read(cx);
20343 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20344 let mut new_selections_by_buffer = HashMap::default();
20345 for selection in selections {
20346 for (buffer, range, _) in
20347 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20348 {
20349 let mut range = range.to_point(buffer);
20350 range.start.column = 0;
20351 range.end.column = buffer.line_len(range.end.row);
20352 new_selections_by_buffer
20353 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20354 .or_insert(Vec::new())
20355 .push(range)
20356 }
20357 }
20358
20359 let proposed_changes_buffers = new_selections_by_buffer
20360 .into_iter()
20361 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20362 .collect::<Vec<_>>();
20363 let proposed_changes_editor = cx.new(|cx| {
20364 ProposedChangesEditor::new(
20365 "Proposed changes",
20366 proposed_changes_buffers,
20367 self.project.clone(),
20368 window,
20369 cx,
20370 )
20371 });
20372
20373 window.defer(cx, move |window, cx| {
20374 workspace.update(cx, |workspace, cx| {
20375 workspace.active_pane().update(cx, |pane, cx| {
20376 pane.add_item(
20377 Box::new(proposed_changes_editor),
20378 true,
20379 true,
20380 None,
20381 window,
20382 cx,
20383 );
20384 });
20385 });
20386 });
20387 }
20388
20389 pub fn open_excerpts_in_split(
20390 &mut self,
20391 _: &OpenExcerptsSplit,
20392 window: &mut Window,
20393 cx: &mut Context<Self>,
20394 ) {
20395 self.open_excerpts_common(None, true, window, cx)
20396 }
20397
20398 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20399 self.open_excerpts_common(None, false, window, cx)
20400 }
20401
20402 fn open_excerpts_common(
20403 &mut self,
20404 jump_data: Option<JumpData>,
20405 split: bool,
20406 window: &mut Window,
20407 cx: &mut Context<Self>,
20408 ) {
20409 let Some(workspace) = self.workspace() else {
20410 cx.propagate();
20411 return;
20412 };
20413
20414 if self.buffer.read(cx).is_singleton() {
20415 cx.propagate();
20416 return;
20417 }
20418
20419 let mut new_selections_by_buffer = HashMap::default();
20420 match &jump_data {
20421 Some(JumpData::MultiBufferPoint {
20422 excerpt_id,
20423 position,
20424 anchor,
20425 line_offset_from_top,
20426 }) => {
20427 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20428 if let Some(buffer) = multi_buffer_snapshot
20429 .buffer_id_for_excerpt(*excerpt_id)
20430 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20431 {
20432 let buffer_snapshot = buffer.read(cx).snapshot();
20433 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20434 language::ToPoint::to_point(anchor, &buffer_snapshot)
20435 } else {
20436 buffer_snapshot.clip_point(*position, Bias::Left)
20437 };
20438 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20439 new_selections_by_buffer.insert(
20440 buffer,
20441 (
20442 vec![jump_to_offset..jump_to_offset],
20443 Some(*line_offset_from_top),
20444 ),
20445 );
20446 }
20447 }
20448 Some(JumpData::MultiBufferRow {
20449 row,
20450 line_offset_from_top,
20451 }) => {
20452 let point = MultiBufferPoint::new(row.0, 0);
20453 if let Some((buffer, buffer_point, _)) =
20454 self.buffer.read(cx).point_to_buffer_point(point, cx)
20455 {
20456 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20457 new_selections_by_buffer
20458 .entry(buffer)
20459 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20460 .0
20461 .push(buffer_offset..buffer_offset)
20462 }
20463 }
20464 None => {
20465 let selections = self.selections.all::<usize>(cx);
20466 let multi_buffer = self.buffer.read(cx);
20467 for selection in selections {
20468 for (snapshot, range, _, anchor) in multi_buffer
20469 .snapshot(cx)
20470 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20471 {
20472 if let Some(anchor) = anchor {
20473 // selection is in a deleted hunk
20474 let Some(buffer_id) = anchor.buffer_id else {
20475 continue;
20476 };
20477 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20478 continue;
20479 };
20480 let offset = text::ToOffset::to_offset(
20481 &anchor.text_anchor,
20482 &buffer_handle.read(cx).snapshot(),
20483 );
20484 let range = offset..offset;
20485 new_selections_by_buffer
20486 .entry(buffer_handle)
20487 .or_insert((Vec::new(), None))
20488 .0
20489 .push(range)
20490 } else {
20491 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20492 else {
20493 continue;
20494 };
20495 new_selections_by_buffer
20496 .entry(buffer_handle)
20497 .or_insert((Vec::new(), None))
20498 .0
20499 .push(range)
20500 }
20501 }
20502 }
20503 }
20504 }
20505
20506 new_selections_by_buffer
20507 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20508
20509 if new_selections_by_buffer.is_empty() {
20510 return;
20511 }
20512
20513 // We defer the pane interaction because we ourselves are a workspace item
20514 // and activating a new item causes the pane to call a method on us reentrantly,
20515 // which panics if we're on the stack.
20516 window.defer(cx, move |window, cx| {
20517 workspace.update(cx, |workspace, cx| {
20518 let pane = if split {
20519 workspace.adjacent_pane(window, cx)
20520 } else {
20521 workspace.active_pane().clone()
20522 };
20523
20524 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20525 let editor = buffer
20526 .read(cx)
20527 .file()
20528 .is_none()
20529 .then(|| {
20530 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20531 // so `workspace.open_project_item` will never find them, always opening a new editor.
20532 // Instead, we try to activate the existing editor in the pane first.
20533 let (editor, pane_item_index) =
20534 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20535 let editor = item.downcast::<Editor>()?;
20536 let singleton_buffer =
20537 editor.read(cx).buffer().read(cx).as_singleton()?;
20538 if singleton_buffer == buffer {
20539 Some((editor, i))
20540 } else {
20541 None
20542 }
20543 })?;
20544 pane.update(cx, |pane, cx| {
20545 pane.activate_item(pane_item_index, true, true, window, cx)
20546 });
20547 Some(editor)
20548 })
20549 .flatten()
20550 .unwrap_or_else(|| {
20551 workspace.open_project_item::<Self>(
20552 pane.clone(),
20553 buffer,
20554 true,
20555 true,
20556 window,
20557 cx,
20558 )
20559 });
20560
20561 editor.update(cx, |editor, cx| {
20562 let autoscroll = match scroll_offset {
20563 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20564 None => Autoscroll::newest(),
20565 };
20566 let nav_history = editor.nav_history.take();
20567 editor.change_selections(
20568 SelectionEffects::scroll(autoscroll),
20569 window,
20570 cx,
20571 |s| {
20572 s.select_ranges(ranges);
20573 },
20574 );
20575 editor.nav_history = nav_history;
20576 });
20577 }
20578 })
20579 });
20580 }
20581
20582 // For now, don't allow opening excerpts in buffers that aren't backed by
20583 // regular project files.
20584 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20585 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20586 }
20587
20588 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20589 let snapshot = self.buffer.read(cx).read(cx);
20590 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20591 Some(
20592 ranges
20593 .iter()
20594 .map(move |range| {
20595 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20596 })
20597 .collect(),
20598 )
20599 }
20600
20601 fn selection_replacement_ranges(
20602 &self,
20603 range: Range<OffsetUtf16>,
20604 cx: &mut App,
20605 ) -> Vec<Range<OffsetUtf16>> {
20606 let selections = self.selections.all::<OffsetUtf16>(cx);
20607 let newest_selection = selections
20608 .iter()
20609 .max_by_key(|selection| selection.id)
20610 .unwrap();
20611 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20612 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20613 let snapshot = self.buffer.read(cx).read(cx);
20614 selections
20615 .into_iter()
20616 .map(|mut selection| {
20617 selection.start.0 =
20618 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20619 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20620 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20621 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20622 })
20623 .collect()
20624 }
20625
20626 fn report_editor_event(
20627 &self,
20628 reported_event: ReportEditorEvent,
20629 file_extension: Option<String>,
20630 cx: &App,
20631 ) {
20632 if cfg!(any(test, feature = "test-support")) {
20633 return;
20634 }
20635
20636 let Some(project) = &self.project else { return };
20637
20638 // If None, we are in a file without an extension
20639 let file = self
20640 .buffer
20641 .read(cx)
20642 .as_singleton()
20643 .and_then(|b| b.read(cx).file());
20644 let file_extension = file_extension.or(file
20645 .as_ref()
20646 .and_then(|file| Path::new(file.file_name(cx)).extension())
20647 .and_then(|e| e.to_str())
20648 .map(|a| a.to_string()));
20649
20650 let vim_mode = vim_enabled(cx);
20651
20652 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20653 let copilot_enabled = edit_predictions_provider
20654 == language::language_settings::EditPredictionProvider::Copilot;
20655 let copilot_enabled_for_language = self
20656 .buffer
20657 .read(cx)
20658 .language_settings(cx)
20659 .show_edit_predictions;
20660
20661 let project = project.read(cx);
20662 let event_type = reported_event.event_type();
20663
20664 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20665 telemetry::event!(
20666 event_type,
20667 type = if auto_saved {"autosave"} else {"manual"},
20668 file_extension,
20669 vim_mode,
20670 copilot_enabled,
20671 copilot_enabled_for_language,
20672 edit_predictions_provider,
20673 is_via_ssh = project.is_via_ssh(),
20674 );
20675 } else {
20676 telemetry::event!(
20677 event_type,
20678 file_extension,
20679 vim_mode,
20680 copilot_enabled,
20681 copilot_enabled_for_language,
20682 edit_predictions_provider,
20683 is_via_ssh = project.is_via_ssh(),
20684 );
20685 };
20686 }
20687
20688 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20689 /// with each line being an array of {text, highlight} objects.
20690 fn copy_highlight_json(
20691 &mut self,
20692 _: &CopyHighlightJson,
20693 window: &mut Window,
20694 cx: &mut Context<Self>,
20695 ) {
20696 #[derive(Serialize)]
20697 struct Chunk<'a> {
20698 text: String,
20699 highlight: Option<&'a str>,
20700 }
20701
20702 let snapshot = self.buffer.read(cx).snapshot(cx);
20703 let range = self
20704 .selected_text_range(false, window, cx)
20705 .and_then(|selection| {
20706 if selection.range.is_empty() {
20707 None
20708 } else {
20709 Some(selection.range)
20710 }
20711 })
20712 .unwrap_or_else(|| 0..snapshot.len());
20713
20714 let chunks = snapshot.chunks(range, true);
20715 let mut lines = Vec::new();
20716 let mut line: VecDeque<Chunk> = VecDeque::new();
20717
20718 let Some(style) = self.style.as_ref() else {
20719 return;
20720 };
20721
20722 for chunk in chunks {
20723 let highlight = chunk
20724 .syntax_highlight_id
20725 .and_then(|id| id.name(&style.syntax));
20726 let mut chunk_lines = chunk.text.split('\n').peekable();
20727 while let Some(text) = chunk_lines.next() {
20728 let mut merged_with_last_token = false;
20729 if let Some(last_token) = line.back_mut()
20730 && last_token.highlight == highlight
20731 {
20732 last_token.text.push_str(text);
20733 merged_with_last_token = true;
20734 }
20735
20736 if !merged_with_last_token {
20737 line.push_back(Chunk {
20738 text: text.into(),
20739 highlight,
20740 });
20741 }
20742
20743 if chunk_lines.peek().is_some() {
20744 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20745 line.pop_front();
20746 }
20747 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20748 line.pop_back();
20749 }
20750
20751 lines.push(mem::take(&mut line));
20752 }
20753 }
20754 }
20755
20756 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20757 return;
20758 };
20759 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20760 }
20761
20762 pub fn open_context_menu(
20763 &mut self,
20764 _: &OpenContextMenu,
20765 window: &mut Window,
20766 cx: &mut Context<Self>,
20767 ) {
20768 self.request_autoscroll(Autoscroll::newest(), cx);
20769 let position = self.selections.newest_display(cx).start;
20770 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20771 }
20772
20773 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20774 &self.inlay_hint_cache
20775 }
20776
20777 pub fn replay_insert_event(
20778 &mut self,
20779 text: &str,
20780 relative_utf16_range: Option<Range<isize>>,
20781 window: &mut Window,
20782 cx: &mut Context<Self>,
20783 ) {
20784 if !self.input_enabled {
20785 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20786 return;
20787 }
20788 if let Some(relative_utf16_range) = relative_utf16_range {
20789 let selections = self.selections.all::<OffsetUtf16>(cx);
20790 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20791 let new_ranges = selections.into_iter().map(|range| {
20792 let start = OffsetUtf16(
20793 range
20794 .head()
20795 .0
20796 .saturating_add_signed(relative_utf16_range.start),
20797 );
20798 let end = OffsetUtf16(
20799 range
20800 .head()
20801 .0
20802 .saturating_add_signed(relative_utf16_range.end),
20803 );
20804 start..end
20805 });
20806 s.select_ranges(new_ranges);
20807 });
20808 }
20809
20810 self.handle_input(text, window, cx);
20811 }
20812
20813 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20814 let Some(provider) = self.semantics_provider.as_ref() else {
20815 return false;
20816 };
20817
20818 let mut supports = false;
20819 self.buffer().update(cx, |this, cx| {
20820 this.for_each_buffer(|buffer| {
20821 supports |= provider.supports_inlay_hints(buffer, cx);
20822 });
20823 });
20824
20825 supports
20826 }
20827
20828 pub fn is_focused(&self, window: &Window) -> bool {
20829 self.focus_handle.is_focused(window)
20830 }
20831
20832 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20833 cx.emit(EditorEvent::Focused);
20834
20835 if let Some(descendant) = self
20836 .last_focused_descendant
20837 .take()
20838 .and_then(|descendant| descendant.upgrade())
20839 {
20840 window.focus(&descendant);
20841 } else {
20842 if let Some(blame) = self.blame.as_ref() {
20843 blame.update(cx, GitBlame::focus)
20844 }
20845
20846 self.blink_manager.update(cx, BlinkManager::enable);
20847 self.show_cursor_names(window, cx);
20848 self.buffer.update(cx, |buffer, cx| {
20849 buffer.finalize_last_transaction(cx);
20850 if self.leader_id.is_none() {
20851 buffer.set_active_selections(
20852 &self.selections.disjoint_anchors(),
20853 self.selections.line_mode,
20854 self.cursor_shape,
20855 cx,
20856 );
20857 }
20858 });
20859 }
20860 }
20861
20862 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20863 cx.emit(EditorEvent::FocusedIn)
20864 }
20865
20866 fn handle_focus_out(
20867 &mut self,
20868 event: FocusOutEvent,
20869 _window: &mut Window,
20870 cx: &mut Context<Self>,
20871 ) {
20872 if event.blurred != self.focus_handle {
20873 self.last_focused_descendant = Some(event.blurred);
20874 }
20875 self.selection_drag_state = SelectionDragState::None;
20876 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20877 }
20878
20879 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20880 self.blink_manager.update(cx, BlinkManager::disable);
20881 self.buffer
20882 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20883
20884 if let Some(blame) = self.blame.as_ref() {
20885 blame.update(cx, GitBlame::blur)
20886 }
20887 if !self.hover_state.focused(window, cx) {
20888 hide_hover(self, cx);
20889 }
20890 if !self
20891 .context_menu
20892 .borrow()
20893 .as_ref()
20894 .is_some_and(|context_menu| context_menu.focused(window, cx))
20895 {
20896 self.hide_context_menu(window, cx);
20897 }
20898 self.discard_edit_prediction(false, cx);
20899 cx.emit(EditorEvent::Blurred);
20900 cx.notify();
20901 }
20902
20903 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20904 let mut pending: String = window
20905 .pending_input_keystrokes()
20906 .into_iter()
20907 .flatten()
20908 .filter_map(|keystroke| {
20909 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20910 keystroke.key_char.clone()
20911 } else {
20912 None
20913 }
20914 })
20915 .collect();
20916
20917 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20918 pending = "".to_string();
20919 }
20920
20921 let existing_pending = self
20922 .text_highlights::<PendingInput>(cx)
20923 .map(|(_, ranges)| ranges.to_vec());
20924 if existing_pending.is_none() && pending.is_empty() {
20925 return;
20926 }
20927 let transaction =
20928 self.transact(window, cx, |this, window, cx| {
20929 let selections = this.selections.all::<usize>(cx);
20930 let edits = selections
20931 .iter()
20932 .map(|selection| (selection.end..selection.end, pending.clone()));
20933 this.edit(edits, cx);
20934 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20935 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20936 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20937 }));
20938 });
20939 if let Some(existing_ranges) = existing_pending {
20940 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20941 this.edit(edits, cx);
20942 }
20943 });
20944
20945 let snapshot = self.snapshot(window, cx);
20946 let ranges = self
20947 .selections
20948 .all::<usize>(cx)
20949 .into_iter()
20950 .map(|selection| {
20951 snapshot.buffer_snapshot.anchor_after(selection.end)
20952 ..snapshot
20953 .buffer_snapshot
20954 .anchor_before(selection.end + pending.len())
20955 })
20956 .collect();
20957
20958 if pending.is_empty() {
20959 self.clear_highlights::<PendingInput>(cx);
20960 } else {
20961 self.highlight_text::<PendingInput>(
20962 ranges,
20963 HighlightStyle {
20964 underline: Some(UnderlineStyle {
20965 thickness: px(1.),
20966 color: None,
20967 wavy: false,
20968 }),
20969 ..Default::default()
20970 },
20971 cx,
20972 );
20973 }
20974
20975 self.ime_transaction = self.ime_transaction.or(transaction);
20976 if let Some(transaction) = self.ime_transaction {
20977 self.buffer.update(cx, |buffer, cx| {
20978 buffer.group_until_transaction(transaction, cx);
20979 });
20980 }
20981
20982 if self.text_highlights::<PendingInput>(cx).is_none() {
20983 self.ime_transaction.take();
20984 }
20985 }
20986
20987 pub fn register_action_renderer(
20988 &mut self,
20989 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20990 ) -> Subscription {
20991 let id = self.next_editor_action_id.post_inc();
20992 self.editor_actions
20993 .borrow_mut()
20994 .insert(id, Box::new(listener));
20995
20996 let editor_actions = self.editor_actions.clone();
20997 Subscription::new(move || {
20998 editor_actions.borrow_mut().remove(&id);
20999 })
21000 }
21001
21002 pub fn register_action<A: Action>(
21003 &mut self,
21004 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21005 ) -> Subscription {
21006 let id = self.next_editor_action_id.post_inc();
21007 let listener = Arc::new(listener);
21008 self.editor_actions.borrow_mut().insert(
21009 id,
21010 Box::new(move |_, window, _| {
21011 let listener = listener.clone();
21012 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21013 let action = action.downcast_ref().unwrap();
21014 if phase == DispatchPhase::Bubble {
21015 listener(action, window, cx)
21016 }
21017 })
21018 }),
21019 );
21020
21021 let editor_actions = self.editor_actions.clone();
21022 Subscription::new(move || {
21023 editor_actions.borrow_mut().remove(&id);
21024 })
21025 }
21026
21027 pub fn file_header_size(&self) -> u32 {
21028 FILE_HEADER_HEIGHT
21029 }
21030
21031 pub fn restore(
21032 &mut self,
21033 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21034 window: &mut Window,
21035 cx: &mut Context<Self>,
21036 ) {
21037 let workspace = self.workspace();
21038 let project = self.project();
21039 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21040 let mut tasks = Vec::new();
21041 for (buffer_id, changes) in revert_changes {
21042 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21043 buffer.update(cx, |buffer, cx| {
21044 buffer.edit(
21045 changes
21046 .into_iter()
21047 .map(|(range, text)| (range, text.to_string())),
21048 None,
21049 cx,
21050 );
21051 });
21052
21053 if let Some(project) =
21054 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21055 {
21056 project.update(cx, |project, cx| {
21057 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21058 })
21059 }
21060 }
21061 }
21062 tasks
21063 });
21064 cx.spawn_in(window, async move |_, cx| {
21065 for (buffer, task) in save_tasks {
21066 let result = task.await;
21067 if result.is_err() {
21068 let Some(path) = buffer
21069 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21070 .ok()
21071 else {
21072 continue;
21073 };
21074 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21075 let Some(task) = cx
21076 .update_window_entity(workspace, |workspace, window, cx| {
21077 workspace
21078 .open_path_preview(path, None, false, false, false, window, cx)
21079 })
21080 .ok()
21081 else {
21082 continue;
21083 };
21084 task.await.log_err();
21085 }
21086 }
21087 }
21088 })
21089 .detach();
21090 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21091 selections.refresh()
21092 });
21093 }
21094
21095 pub fn to_pixel_point(
21096 &self,
21097 source: multi_buffer::Anchor,
21098 editor_snapshot: &EditorSnapshot,
21099 window: &mut Window,
21100 ) -> Option<gpui::Point<Pixels>> {
21101 let source_point = source.to_display_point(editor_snapshot);
21102 self.display_to_pixel_point(source_point, editor_snapshot, window)
21103 }
21104
21105 pub fn display_to_pixel_point(
21106 &self,
21107 source: DisplayPoint,
21108 editor_snapshot: &EditorSnapshot,
21109 window: &mut Window,
21110 ) -> Option<gpui::Point<Pixels>> {
21111 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21112 let text_layout_details = self.text_layout_details(window);
21113 let scroll_top = text_layout_details
21114 .scroll_anchor
21115 .scroll_position(editor_snapshot)
21116 .y;
21117
21118 if source.row().as_f32() < scroll_top.floor() {
21119 return None;
21120 }
21121 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21122 let source_y = line_height * (source.row().as_f32() - scroll_top);
21123 Some(gpui::Point::new(source_x, source_y))
21124 }
21125
21126 pub fn has_visible_completions_menu(&self) -> bool {
21127 !self.edit_prediction_preview_is_active()
21128 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
21129 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21130 })
21131 }
21132
21133 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21134 if self.mode.is_minimap() {
21135 return;
21136 }
21137 self.addons
21138 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21139 }
21140
21141 pub fn unregister_addon<T: Addon>(&mut self) {
21142 self.addons.remove(&std::any::TypeId::of::<T>());
21143 }
21144
21145 pub fn addon<T: Addon>(&self) -> Option<&T> {
21146 let type_id = std::any::TypeId::of::<T>();
21147 self.addons
21148 .get(&type_id)
21149 .and_then(|item| item.to_any().downcast_ref::<T>())
21150 }
21151
21152 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21153 let type_id = std::any::TypeId::of::<T>();
21154 self.addons
21155 .get_mut(&type_id)
21156 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21157 }
21158
21159 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21160 let text_layout_details = self.text_layout_details(window);
21161 let style = &text_layout_details.editor_style;
21162 let font_id = window.text_system().resolve_font(&style.text.font());
21163 let font_size = style.text.font_size.to_pixels(window.rem_size());
21164 let line_height = style.text.line_height_in_pixels(window.rem_size());
21165 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21166 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21167
21168 CharacterDimensions {
21169 em_width,
21170 em_advance,
21171 line_height,
21172 }
21173 }
21174
21175 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21176 self.load_diff_task.clone()
21177 }
21178
21179 fn read_metadata_from_db(
21180 &mut self,
21181 item_id: u64,
21182 workspace_id: WorkspaceId,
21183 window: &mut Window,
21184 cx: &mut Context<Editor>,
21185 ) {
21186 if self.is_singleton(cx)
21187 && !self.mode.is_minimap()
21188 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21189 {
21190 let buffer_snapshot = OnceCell::new();
21191
21192 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21193 && !folds.is_empty()
21194 {
21195 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21196 self.fold_ranges(
21197 folds
21198 .into_iter()
21199 .map(|(start, end)| {
21200 snapshot.clip_offset(start, Bias::Left)
21201 ..snapshot.clip_offset(end, Bias::Right)
21202 })
21203 .collect(),
21204 false,
21205 window,
21206 cx,
21207 );
21208 }
21209
21210 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21211 && !selections.is_empty()
21212 {
21213 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21214 // skip adding the initial selection to selection history
21215 self.selection_history.mode = SelectionHistoryMode::Skipping;
21216 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21217 s.select_ranges(selections.into_iter().map(|(start, end)| {
21218 snapshot.clip_offset(start, Bias::Left)
21219 ..snapshot.clip_offset(end, Bias::Right)
21220 }));
21221 });
21222 self.selection_history.mode = SelectionHistoryMode::Normal;
21223 };
21224 }
21225
21226 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21227 }
21228
21229 fn update_lsp_data(
21230 &mut self,
21231 ignore_cache: bool,
21232 for_buffer: Option<BufferId>,
21233 window: &mut Window,
21234 cx: &mut Context<'_, Self>,
21235 ) {
21236 self.pull_diagnostics(for_buffer, window, cx);
21237 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21238 }
21239}
21240
21241fn vim_enabled(cx: &App) -> bool {
21242 cx.global::<SettingsStore>()
21243 .raw_user_settings()
21244 .get("vim_mode")
21245 == Some(&serde_json::Value::Bool(true))
21246}
21247
21248fn process_completion_for_edit(
21249 completion: &Completion,
21250 intent: CompletionIntent,
21251 buffer: &Entity<Buffer>,
21252 cursor_position: &text::Anchor,
21253 cx: &mut Context<Editor>,
21254) -> CompletionEdit {
21255 let buffer = buffer.read(cx);
21256 let buffer_snapshot = buffer.snapshot();
21257 let (snippet, new_text) = if completion.is_snippet() {
21258 // Workaround for typescript language server issues so that methods don't expand within
21259 // strings and functions with type expressions. The previous point is used because the query
21260 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21261 let mut snippet_source = completion.new_text.clone();
21262 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21263 previous_point.column = previous_point.column.saturating_sub(1);
21264 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21265 && scope.prefers_label_for_snippet_in_completion()
21266 && let Some(label) = completion.label()
21267 && matches!(
21268 completion.kind(),
21269 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21270 )
21271 {
21272 snippet_source = label;
21273 }
21274 match Snippet::parse(&snippet_source).log_err() {
21275 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21276 None => (None, completion.new_text.clone()),
21277 }
21278 } else {
21279 (None, completion.new_text.clone())
21280 };
21281
21282 let mut range_to_replace = {
21283 let replace_range = &completion.replace_range;
21284 if let CompletionSource::Lsp {
21285 insert_range: Some(insert_range),
21286 ..
21287 } = &completion.source
21288 {
21289 debug_assert_eq!(
21290 insert_range.start, replace_range.start,
21291 "insert_range and replace_range should start at the same position"
21292 );
21293 debug_assert!(
21294 insert_range
21295 .start
21296 .cmp(cursor_position, &buffer_snapshot)
21297 .is_le(),
21298 "insert_range should start before or at cursor position"
21299 );
21300 debug_assert!(
21301 replace_range
21302 .start
21303 .cmp(cursor_position, &buffer_snapshot)
21304 .is_le(),
21305 "replace_range should start before or at cursor position"
21306 );
21307
21308 let should_replace = match intent {
21309 CompletionIntent::CompleteWithInsert => false,
21310 CompletionIntent::CompleteWithReplace => true,
21311 CompletionIntent::Complete | CompletionIntent::Compose => {
21312 let insert_mode =
21313 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21314 .completions
21315 .lsp_insert_mode;
21316 match insert_mode {
21317 LspInsertMode::Insert => false,
21318 LspInsertMode::Replace => true,
21319 LspInsertMode::ReplaceSubsequence => {
21320 let mut text_to_replace = buffer.chars_for_range(
21321 buffer.anchor_before(replace_range.start)
21322 ..buffer.anchor_after(replace_range.end),
21323 );
21324 let mut current_needle = text_to_replace.next();
21325 for haystack_ch in completion.label.text.chars() {
21326 if let Some(needle_ch) = current_needle
21327 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21328 {
21329 current_needle = text_to_replace.next();
21330 }
21331 }
21332 current_needle.is_none()
21333 }
21334 LspInsertMode::ReplaceSuffix => {
21335 if replace_range
21336 .end
21337 .cmp(cursor_position, &buffer_snapshot)
21338 .is_gt()
21339 {
21340 let range_after_cursor = *cursor_position..replace_range.end;
21341 let text_after_cursor = buffer
21342 .text_for_range(
21343 buffer.anchor_before(range_after_cursor.start)
21344 ..buffer.anchor_after(range_after_cursor.end),
21345 )
21346 .collect::<String>()
21347 .to_ascii_lowercase();
21348 completion
21349 .label
21350 .text
21351 .to_ascii_lowercase()
21352 .ends_with(&text_after_cursor)
21353 } else {
21354 true
21355 }
21356 }
21357 }
21358 }
21359 };
21360
21361 if should_replace {
21362 replace_range.clone()
21363 } else {
21364 insert_range.clone()
21365 }
21366 } else {
21367 replace_range.clone()
21368 }
21369 };
21370
21371 if range_to_replace
21372 .end
21373 .cmp(cursor_position, &buffer_snapshot)
21374 .is_lt()
21375 {
21376 range_to_replace.end = *cursor_position;
21377 }
21378
21379 CompletionEdit {
21380 new_text,
21381 replace_range: range_to_replace.to_offset(buffer),
21382 snippet,
21383 }
21384}
21385
21386struct CompletionEdit {
21387 new_text: String,
21388 replace_range: Range<usize>,
21389 snippet: Option<Snippet>,
21390}
21391
21392fn insert_extra_newline_brackets(
21393 buffer: &MultiBufferSnapshot,
21394 range: Range<usize>,
21395 language: &language::LanguageScope,
21396) -> bool {
21397 let leading_whitespace_len = buffer
21398 .reversed_chars_at(range.start)
21399 .take_while(|c| c.is_whitespace() && *c != '\n')
21400 .map(|c| c.len_utf8())
21401 .sum::<usize>();
21402 let trailing_whitespace_len = buffer
21403 .chars_at(range.end)
21404 .take_while(|c| c.is_whitespace() && *c != '\n')
21405 .map(|c| c.len_utf8())
21406 .sum::<usize>();
21407 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21408
21409 language.brackets().any(|(pair, enabled)| {
21410 let pair_start = pair.start.trim_end();
21411 let pair_end = pair.end.trim_start();
21412
21413 enabled
21414 && pair.newline
21415 && buffer.contains_str_at(range.end, pair_end)
21416 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21417 })
21418}
21419
21420fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21421 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21422 [(buffer, range, _)] => (*buffer, range.clone()),
21423 _ => return false,
21424 };
21425 let pair = {
21426 let mut result: Option<BracketMatch> = None;
21427
21428 for pair in buffer
21429 .all_bracket_ranges(range.clone())
21430 .filter(move |pair| {
21431 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21432 })
21433 {
21434 let len = pair.close_range.end - pair.open_range.start;
21435
21436 if let Some(existing) = &result {
21437 let existing_len = existing.close_range.end - existing.open_range.start;
21438 if len > existing_len {
21439 continue;
21440 }
21441 }
21442
21443 result = Some(pair);
21444 }
21445
21446 result
21447 };
21448 let Some(pair) = pair else {
21449 return false;
21450 };
21451 pair.newline_only
21452 && buffer
21453 .chars_for_range(pair.open_range.end..range.start)
21454 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21455 .all(|c| c.is_whitespace() && c != '\n')
21456}
21457
21458fn update_uncommitted_diff_for_buffer(
21459 editor: Entity<Editor>,
21460 project: &Entity<Project>,
21461 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21462 buffer: Entity<MultiBuffer>,
21463 cx: &mut App,
21464) -> Task<()> {
21465 let mut tasks = Vec::new();
21466 project.update(cx, |project, cx| {
21467 for buffer in buffers {
21468 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21469 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21470 }
21471 }
21472 });
21473 cx.spawn(async move |cx| {
21474 let diffs = future::join_all(tasks).await;
21475 if editor
21476 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21477 .unwrap_or(false)
21478 {
21479 return;
21480 }
21481
21482 buffer
21483 .update(cx, |buffer, cx| {
21484 for diff in diffs.into_iter().flatten() {
21485 buffer.add_diff(diff, cx);
21486 }
21487 })
21488 .ok();
21489 })
21490}
21491
21492fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21493 let tab_size = tab_size.get() as usize;
21494 let mut width = offset;
21495
21496 for ch in text.chars() {
21497 width += if ch == '\t' {
21498 tab_size - (width % tab_size)
21499 } else {
21500 1
21501 };
21502 }
21503
21504 width - offset
21505}
21506
21507#[cfg(test)]
21508mod tests {
21509 use super::*;
21510
21511 #[test]
21512 fn test_string_size_with_expanded_tabs() {
21513 let nz = |val| NonZeroU32::new(val).unwrap();
21514 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21515 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21516 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21517 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21518 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21519 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21520 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21521 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21522 }
21523}
21524
21525/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21526struct WordBreakingTokenizer<'a> {
21527 input: &'a str,
21528}
21529
21530impl<'a> WordBreakingTokenizer<'a> {
21531 fn new(input: &'a str) -> Self {
21532 Self { input }
21533 }
21534}
21535
21536fn is_char_ideographic(ch: char) -> bool {
21537 use unicode_script::Script::*;
21538 use unicode_script::UnicodeScript;
21539 matches!(ch.script(), Han | Tangut | Yi)
21540}
21541
21542fn is_grapheme_ideographic(text: &str) -> bool {
21543 text.chars().any(is_char_ideographic)
21544}
21545
21546fn is_grapheme_whitespace(text: &str) -> bool {
21547 text.chars().any(|x| x.is_whitespace())
21548}
21549
21550fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21551 text.chars().next().map_or(false, |ch| {
21552 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21553 })
21554}
21555
21556#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21557enum WordBreakToken<'a> {
21558 Word { token: &'a str, grapheme_len: usize },
21559 InlineWhitespace { token: &'a str, grapheme_len: usize },
21560 Newline,
21561}
21562
21563impl<'a> Iterator for WordBreakingTokenizer<'a> {
21564 /// Yields a span, the count of graphemes in the token, and whether it was
21565 /// whitespace. Note that it also breaks at word boundaries.
21566 type Item = WordBreakToken<'a>;
21567
21568 fn next(&mut self) -> Option<Self::Item> {
21569 use unicode_segmentation::UnicodeSegmentation;
21570 if self.input.is_empty() {
21571 return None;
21572 }
21573
21574 let mut iter = self.input.graphemes(true).peekable();
21575 let mut offset = 0;
21576 let mut grapheme_len = 0;
21577 if let Some(first_grapheme) = iter.next() {
21578 let is_newline = first_grapheme == "\n";
21579 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21580 offset += first_grapheme.len();
21581 grapheme_len += 1;
21582 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21583 if let Some(grapheme) = iter.peek().copied()
21584 && should_stay_with_preceding_ideograph(grapheme)
21585 {
21586 offset += grapheme.len();
21587 grapheme_len += 1;
21588 }
21589 } else {
21590 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21591 let mut next_word_bound = words.peek().copied();
21592 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21593 next_word_bound = words.next();
21594 }
21595 while let Some(grapheme) = iter.peek().copied() {
21596 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21597 break;
21598 };
21599 if is_grapheme_whitespace(grapheme) != is_whitespace
21600 || (grapheme == "\n") != is_newline
21601 {
21602 break;
21603 };
21604 offset += grapheme.len();
21605 grapheme_len += 1;
21606 iter.next();
21607 }
21608 }
21609 let token = &self.input[..offset];
21610 self.input = &self.input[offset..];
21611 if token == "\n" {
21612 Some(WordBreakToken::Newline)
21613 } else if is_whitespace {
21614 Some(WordBreakToken::InlineWhitespace {
21615 token,
21616 grapheme_len,
21617 })
21618 } else {
21619 Some(WordBreakToken::Word {
21620 token,
21621 grapheme_len,
21622 })
21623 }
21624 } else {
21625 None
21626 }
21627 }
21628}
21629
21630#[test]
21631fn test_word_breaking_tokenizer() {
21632 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21633 ("", &[]),
21634 (" ", &[whitespace(" ", 2)]),
21635 ("Ʒ", &[word("Ʒ", 1)]),
21636 ("Ǽ", &[word("Ǽ", 1)]),
21637 ("⋑", &[word("⋑", 1)]),
21638 ("⋑⋑", &[word("⋑⋑", 2)]),
21639 (
21640 "原理,进而",
21641 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21642 ),
21643 (
21644 "hello world",
21645 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21646 ),
21647 (
21648 "hello, world",
21649 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21650 ),
21651 (
21652 " hello world",
21653 &[
21654 whitespace(" ", 2),
21655 word("hello", 5),
21656 whitespace(" ", 1),
21657 word("world", 5),
21658 ],
21659 ),
21660 (
21661 "这是什么 \n 钢笔",
21662 &[
21663 word("这", 1),
21664 word("是", 1),
21665 word("什", 1),
21666 word("么", 1),
21667 whitespace(" ", 1),
21668 newline(),
21669 whitespace(" ", 1),
21670 word("钢", 1),
21671 word("笔", 1),
21672 ],
21673 ),
21674 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21675 ];
21676
21677 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21678 WordBreakToken::Word {
21679 token,
21680 grapheme_len,
21681 }
21682 }
21683
21684 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21685 WordBreakToken::InlineWhitespace {
21686 token,
21687 grapheme_len,
21688 }
21689 }
21690
21691 fn newline() -> WordBreakToken<'static> {
21692 WordBreakToken::Newline
21693 }
21694
21695 for (input, result) in tests {
21696 assert_eq!(
21697 WordBreakingTokenizer::new(input)
21698 .collect::<Vec<_>>()
21699 .as_slice(),
21700 *result,
21701 );
21702 }
21703}
21704
21705fn wrap_with_prefix(
21706 first_line_prefix: String,
21707 subsequent_lines_prefix: String,
21708 unwrapped_text: String,
21709 wrap_column: usize,
21710 tab_size: NonZeroU32,
21711 preserve_existing_whitespace: bool,
21712) -> String {
21713 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21714 let subsequent_lines_prefix_len =
21715 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21716 let mut wrapped_text = String::new();
21717 let mut current_line = first_line_prefix.clone();
21718 let mut is_first_line = true;
21719
21720 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21721 let mut current_line_len = first_line_prefix_len;
21722 let mut in_whitespace = false;
21723 for token in tokenizer {
21724 let have_preceding_whitespace = in_whitespace;
21725 match token {
21726 WordBreakToken::Word {
21727 token,
21728 grapheme_len,
21729 } => {
21730 in_whitespace = false;
21731 let current_prefix_len = if is_first_line {
21732 first_line_prefix_len
21733 } else {
21734 subsequent_lines_prefix_len
21735 };
21736 if current_line_len + grapheme_len > wrap_column
21737 && current_line_len != current_prefix_len
21738 {
21739 wrapped_text.push_str(current_line.trim_end());
21740 wrapped_text.push('\n');
21741 is_first_line = false;
21742 current_line = subsequent_lines_prefix.clone();
21743 current_line_len = subsequent_lines_prefix_len;
21744 }
21745 current_line.push_str(token);
21746 current_line_len += grapheme_len;
21747 }
21748 WordBreakToken::InlineWhitespace {
21749 mut token,
21750 mut grapheme_len,
21751 } => {
21752 in_whitespace = true;
21753 if have_preceding_whitespace && !preserve_existing_whitespace {
21754 continue;
21755 }
21756 if !preserve_existing_whitespace {
21757 token = " ";
21758 grapheme_len = 1;
21759 }
21760 let current_prefix_len = if is_first_line {
21761 first_line_prefix_len
21762 } else {
21763 subsequent_lines_prefix_len
21764 };
21765 if current_line_len + grapheme_len > wrap_column {
21766 wrapped_text.push_str(current_line.trim_end());
21767 wrapped_text.push('\n');
21768 is_first_line = false;
21769 current_line = subsequent_lines_prefix.clone();
21770 current_line_len = subsequent_lines_prefix_len;
21771 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21772 current_line.push_str(token);
21773 current_line_len += grapheme_len;
21774 }
21775 }
21776 WordBreakToken::Newline => {
21777 in_whitespace = true;
21778 let current_prefix_len = if is_first_line {
21779 first_line_prefix_len
21780 } else {
21781 subsequent_lines_prefix_len
21782 };
21783 if preserve_existing_whitespace {
21784 wrapped_text.push_str(current_line.trim_end());
21785 wrapped_text.push('\n');
21786 is_first_line = false;
21787 current_line = subsequent_lines_prefix.clone();
21788 current_line_len = subsequent_lines_prefix_len;
21789 } else if have_preceding_whitespace {
21790 continue;
21791 } else if current_line_len + 1 > wrap_column
21792 && current_line_len != current_prefix_len
21793 {
21794 wrapped_text.push_str(current_line.trim_end());
21795 wrapped_text.push('\n');
21796 is_first_line = false;
21797 current_line = subsequent_lines_prefix.clone();
21798 current_line_len = subsequent_lines_prefix_len;
21799 } else if current_line_len != current_prefix_len {
21800 current_line.push(' ');
21801 current_line_len += 1;
21802 }
21803 }
21804 }
21805 }
21806
21807 if !current_line.is_empty() {
21808 wrapped_text.push_str(¤t_line);
21809 }
21810 wrapped_text
21811}
21812
21813#[test]
21814fn test_wrap_with_prefix() {
21815 assert_eq!(
21816 wrap_with_prefix(
21817 "# ".to_string(),
21818 "# ".to_string(),
21819 "abcdefg".to_string(),
21820 4,
21821 NonZeroU32::new(4).unwrap(),
21822 false,
21823 ),
21824 "# abcdefg"
21825 );
21826 assert_eq!(
21827 wrap_with_prefix(
21828 "".to_string(),
21829 "".to_string(),
21830 "\thello world".to_string(),
21831 8,
21832 NonZeroU32::new(4).unwrap(),
21833 false,
21834 ),
21835 "hello\nworld"
21836 );
21837 assert_eq!(
21838 wrap_with_prefix(
21839 "// ".to_string(),
21840 "// ".to_string(),
21841 "xx \nyy zz aa bb cc".to_string(),
21842 12,
21843 NonZeroU32::new(4).unwrap(),
21844 false,
21845 ),
21846 "// xx yy zz\n// aa bb cc"
21847 );
21848 assert_eq!(
21849 wrap_with_prefix(
21850 String::new(),
21851 String::new(),
21852 "这是什么 \n 钢笔".to_string(),
21853 3,
21854 NonZeroU32::new(4).unwrap(),
21855 false,
21856 ),
21857 "这是什\n么 钢\n笔"
21858 );
21859}
21860
21861pub trait CollaborationHub {
21862 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21863 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21864 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21865}
21866
21867impl CollaborationHub for Entity<Project> {
21868 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21869 self.read(cx).collaborators()
21870 }
21871
21872 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21873 self.read(cx).user_store().read(cx).participant_indices()
21874 }
21875
21876 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21877 let this = self.read(cx);
21878 let user_ids = this.collaborators().values().map(|c| c.user_id);
21879 this.user_store().read(cx).participant_names(user_ids, cx)
21880 }
21881}
21882
21883pub trait SemanticsProvider {
21884 fn hover(
21885 &self,
21886 buffer: &Entity<Buffer>,
21887 position: text::Anchor,
21888 cx: &mut App,
21889 ) -> Option<Task<Vec<project::Hover>>>;
21890
21891 fn inline_values(
21892 &self,
21893 buffer_handle: Entity<Buffer>,
21894 range: Range<text::Anchor>,
21895 cx: &mut App,
21896 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21897
21898 fn inlay_hints(
21899 &self,
21900 buffer_handle: Entity<Buffer>,
21901 range: Range<text::Anchor>,
21902 cx: &mut App,
21903 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21904
21905 fn resolve_inlay_hint(
21906 &self,
21907 hint: InlayHint,
21908 buffer_handle: Entity<Buffer>,
21909 server_id: LanguageServerId,
21910 cx: &mut App,
21911 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21912
21913 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21914
21915 fn document_highlights(
21916 &self,
21917 buffer: &Entity<Buffer>,
21918 position: text::Anchor,
21919 cx: &mut App,
21920 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21921
21922 fn definitions(
21923 &self,
21924 buffer: &Entity<Buffer>,
21925 position: text::Anchor,
21926 kind: GotoDefinitionKind,
21927 cx: &mut App,
21928 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21929
21930 fn range_for_rename(
21931 &self,
21932 buffer: &Entity<Buffer>,
21933 position: text::Anchor,
21934 cx: &mut App,
21935 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21936
21937 fn perform_rename(
21938 &self,
21939 buffer: &Entity<Buffer>,
21940 position: text::Anchor,
21941 new_name: String,
21942 cx: &mut App,
21943 ) -> Option<Task<Result<ProjectTransaction>>>;
21944}
21945
21946pub trait CompletionProvider {
21947 fn completions(
21948 &self,
21949 excerpt_id: ExcerptId,
21950 buffer: &Entity<Buffer>,
21951 buffer_position: text::Anchor,
21952 trigger: CompletionContext,
21953 window: &mut Window,
21954 cx: &mut Context<Editor>,
21955 ) -> Task<Result<Vec<CompletionResponse>>>;
21956
21957 fn resolve_completions(
21958 &self,
21959 _buffer: Entity<Buffer>,
21960 _completion_indices: Vec<usize>,
21961 _completions: Rc<RefCell<Box<[Completion]>>>,
21962 _cx: &mut Context<Editor>,
21963 ) -> Task<Result<bool>> {
21964 Task::ready(Ok(false))
21965 }
21966
21967 fn apply_additional_edits_for_completion(
21968 &self,
21969 _buffer: Entity<Buffer>,
21970 _completions: Rc<RefCell<Box<[Completion]>>>,
21971 _completion_index: usize,
21972 _push_to_history: bool,
21973 _cx: &mut Context<Editor>,
21974 ) -> Task<Result<Option<language::Transaction>>> {
21975 Task::ready(Ok(None))
21976 }
21977
21978 fn is_completion_trigger(
21979 &self,
21980 buffer: &Entity<Buffer>,
21981 position: language::Anchor,
21982 text: &str,
21983 trigger_in_words: bool,
21984 menu_is_open: bool,
21985 cx: &mut Context<Editor>,
21986 ) -> bool;
21987
21988 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21989
21990 fn sort_completions(&self) -> bool {
21991 true
21992 }
21993
21994 fn filter_completions(&self) -> bool {
21995 true
21996 }
21997}
21998
21999pub trait CodeActionProvider {
22000 fn id(&self) -> Arc<str>;
22001
22002 fn code_actions(
22003 &self,
22004 buffer: &Entity<Buffer>,
22005 range: Range<text::Anchor>,
22006 window: &mut Window,
22007 cx: &mut App,
22008 ) -> Task<Result<Vec<CodeAction>>>;
22009
22010 fn apply_code_action(
22011 &self,
22012 buffer_handle: Entity<Buffer>,
22013 action: CodeAction,
22014 excerpt_id: ExcerptId,
22015 push_to_history: bool,
22016 window: &mut Window,
22017 cx: &mut App,
22018 ) -> Task<Result<ProjectTransaction>>;
22019}
22020
22021impl CodeActionProvider for Entity<Project> {
22022 fn id(&self) -> Arc<str> {
22023 "project".into()
22024 }
22025
22026 fn code_actions(
22027 &self,
22028 buffer: &Entity<Buffer>,
22029 range: Range<text::Anchor>,
22030 _window: &mut Window,
22031 cx: &mut App,
22032 ) -> Task<Result<Vec<CodeAction>>> {
22033 self.update(cx, |project, cx| {
22034 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22035 let code_actions = project.code_actions(buffer, range, None, cx);
22036 cx.background_spawn(async move {
22037 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22038 Ok(code_lens_actions
22039 .context("code lens fetch")?
22040 .into_iter()
22041 .chain(code_actions.context("code action fetch")?)
22042 .collect())
22043 })
22044 })
22045 }
22046
22047 fn apply_code_action(
22048 &self,
22049 buffer_handle: Entity<Buffer>,
22050 action: CodeAction,
22051 _excerpt_id: ExcerptId,
22052 push_to_history: bool,
22053 _window: &mut Window,
22054 cx: &mut App,
22055 ) -> Task<Result<ProjectTransaction>> {
22056 self.update(cx, |project, cx| {
22057 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22058 })
22059 }
22060}
22061
22062fn snippet_completions(
22063 project: &Project,
22064 buffer: &Entity<Buffer>,
22065 buffer_position: text::Anchor,
22066 cx: &mut App,
22067) -> Task<Result<CompletionResponse>> {
22068 let languages = buffer.read(cx).languages_at(buffer_position);
22069 let snippet_store = project.snippets().read(cx);
22070
22071 let scopes: Vec<_> = languages
22072 .iter()
22073 .filter_map(|language| {
22074 let language_name = language.lsp_id();
22075 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22076
22077 if snippets.is_empty() {
22078 None
22079 } else {
22080 Some((language.default_scope(), snippets))
22081 }
22082 })
22083 .collect();
22084
22085 if scopes.is_empty() {
22086 return Task::ready(Ok(CompletionResponse {
22087 completions: vec![],
22088 is_incomplete: false,
22089 }));
22090 }
22091
22092 let snapshot = buffer.read(cx).text_snapshot();
22093 let chars: String = snapshot
22094 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22095 .collect();
22096 let executor = cx.background_executor().clone();
22097
22098 cx.background_spawn(async move {
22099 let mut is_incomplete = false;
22100 let mut completions: Vec<Completion> = Vec::new();
22101 for (scope, snippets) in scopes.into_iter() {
22102 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22103 let mut last_word = chars
22104 .chars()
22105 .take_while(|c| classifier.is_word(*c))
22106 .collect::<String>();
22107 last_word = last_word.chars().rev().collect();
22108
22109 if last_word.is_empty() {
22110 return Ok(CompletionResponse {
22111 completions: vec![],
22112 is_incomplete: true,
22113 });
22114 }
22115
22116 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22117 let to_lsp = |point: &text::Anchor| {
22118 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22119 point_to_lsp(end)
22120 };
22121 let lsp_end = to_lsp(&buffer_position);
22122
22123 let candidates = snippets
22124 .iter()
22125 .enumerate()
22126 .flat_map(|(ix, snippet)| {
22127 snippet
22128 .prefix
22129 .iter()
22130 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22131 })
22132 .collect::<Vec<StringMatchCandidate>>();
22133
22134 const MAX_RESULTS: usize = 100;
22135 let mut matches = fuzzy::match_strings(
22136 &candidates,
22137 &last_word,
22138 last_word.chars().any(|c| c.is_uppercase()),
22139 true,
22140 MAX_RESULTS,
22141 &Default::default(),
22142 executor.clone(),
22143 )
22144 .await;
22145
22146 if matches.len() >= MAX_RESULTS {
22147 is_incomplete = true;
22148 }
22149
22150 // Remove all candidates where the query's start does not match the start of any word in the candidate
22151 if let Some(query_start) = last_word.chars().next() {
22152 matches.retain(|string_match| {
22153 split_words(&string_match.string).any(|word| {
22154 // Check that the first codepoint of the word as lowercase matches the first
22155 // codepoint of the query as lowercase
22156 word.chars()
22157 .flat_map(|codepoint| codepoint.to_lowercase())
22158 .zip(query_start.to_lowercase())
22159 .all(|(word_cp, query_cp)| word_cp == query_cp)
22160 })
22161 });
22162 }
22163
22164 let matched_strings = matches
22165 .into_iter()
22166 .map(|m| m.string)
22167 .collect::<HashSet<_>>();
22168
22169 completions.extend(snippets.iter().filter_map(|snippet| {
22170 let matching_prefix = snippet
22171 .prefix
22172 .iter()
22173 .find(|prefix| matched_strings.contains(*prefix))?;
22174 let start = as_offset - last_word.len();
22175 let start = snapshot.anchor_before(start);
22176 let range = start..buffer_position;
22177 let lsp_start = to_lsp(&start);
22178 let lsp_range = lsp::Range {
22179 start: lsp_start,
22180 end: lsp_end,
22181 };
22182 Some(Completion {
22183 replace_range: range,
22184 new_text: snippet.body.clone(),
22185 source: CompletionSource::Lsp {
22186 insert_range: None,
22187 server_id: LanguageServerId(usize::MAX),
22188 resolved: true,
22189 lsp_completion: Box::new(lsp::CompletionItem {
22190 label: snippet.prefix.first().unwrap().clone(),
22191 kind: Some(CompletionItemKind::SNIPPET),
22192 label_details: snippet.description.as_ref().map(|description| {
22193 lsp::CompletionItemLabelDetails {
22194 detail: Some(description.clone()),
22195 description: None,
22196 }
22197 }),
22198 insert_text_format: Some(InsertTextFormat::SNIPPET),
22199 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22200 lsp::InsertReplaceEdit {
22201 new_text: snippet.body.clone(),
22202 insert: lsp_range,
22203 replace: lsp_range,
22204 },
22205 )),
22206 filter_text: Some(snippet.body.clone()),
22207 sort_text: Some(char::MAX.to_string()),
22208 ..lsp::CompletionItem::default()
22209 }),
22210 lsp_defaults: None,
22211 },
22212 label: CodeLabel {
22213 text: matching_prefix.clone(),
22214 runs: Vec::new(),
22215 filter_range: 0..matching_prefix.len(),
22216 },
22217 icon_path: None,
22218 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22219 single_line: snippet.name.clone().into(),
22220 plain_text: snippet
22221 .description
22222 .clone()
22223 .map(|description| description.into()),
22224 }),
22225 insert_text_mode: None,
22226 confirm: None,
22227 })
22228 }))
22229 }
22230
22231 Ok(CompletionResponse {
22232 completions,
22233 is_incomplete,
22234 })
22235 })
22236}
22237
22238impl CompletionProvider for Entity<Project> {
22239 fn completions(
22240 &self,
22241 _excerpt_id: ExcerptId,
22242 buffer: &Entity<Buffer>,
22243 buffer_position: text::Anchor,
22244 options: CompletionContext,
22245 _window: &mut Window,
22246 cx: &mut Context<Editor>,
22247 ) -> Task<Result<Vec<CompletionResponse>>> {
22248 self.update(cx, |project, cx| {
22249 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22250 let project_completions = project.completions(buffer, buffer_position, options, cx);
22251 cx.background_spawn(async move {
22252 let mut responses = project_completions.await?;
22253 let snippets = snippets.await?;
22254 if !snippets.completions.is_empty() {
22255 responses.push(snippets);
22256 }
22257 Ok(responses)
22258 })
22259 })
22260 }
22261
22262 fn resolve_completions(
22263 &self,
22264 buffer: Entity<Buffer>,
22265 completion_indices: Vec<usize>,
22266 completions: Rc<RefCell<Box<[Completion]>>>,
22267 cx: &mut Context<Editor>,
22268 ) -> Task<Result<bool>> {
22269 self.update(cx, |project, cx| {
22270 project.lsp_store().update(cx, |lsp_store, cx| {
22271 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22272 })
22273 })
22274 }
22275
22276 fn apply_additional_edits_for_completion(
22277 &self,
22278 buffer: Entity<Buffer>,
22279 completions: Rc<RefCell<Box<[Completion]>>>,
22280 completion_index: usize,
22281 push_to_history: bool,
22282 cx: &mut Context<Editor>,
22283 ) -> Task<Result<Option<language::Transaction>>> {
22284 self.update(cx, |project, cx| {
22285 project.lsp_store().update(cx, |lsp_store, cx| {
22286 lsp_store.apply_additional_edits_for_completion(
22287 buffer,
22288 completions,
22289 completion_index,
22290 push_to_history,
22291 cx,
22292 )
22293 })
22294 })
22295 }
22296
22297 fn is_completion_trigger(
22298 &self,
22299 buffer: &Entity<Buffer>,
22300 position: language::Anchor,
22301 text: &str,
22302 trigger_in_words: bool,
22303 menu_is_open: bool,
22304 cx: &mut Context<Editor>,
22305 ) -> bool {
22306 let mut chars = text.chars();
22307 let char = if let Some(char) = chars.next() {
22308 char
22309 } else {
22310 return false;
22311 };
22312 if chars.next().is_some() {
22313 return false;
22314 }
22315
22316 let buffer = buffer.read(cx);
22317 let snapshot = buffer.snapshot();
22318 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22319 return false;
22320 }
22321 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22322 if trigger_in_words && classifier.is_word(char) {
22323 return true;
22324 }
22325
22326 buffer.completion_triggers().contains(text)
22327 }
22328}
22329
22330impl SemanticsProvider for Entity<Project> {
22331 fn hover(
22332 &self,
22333 buffer: &Entity<Buffer>,
22334 position: text::Anchor,
22335 cx: &mut App,
22336 ) -> Option<Task<Vec<project::Hover>>> {
22337 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22338 }
22339
22340 fn document_highlights(
22341 &self,
22342 buffer: &Entity<Buffer>,
22343 position: text::Anchor,
22344 cx: &mut App,
22345 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22346 Some(self.update(cx, |project, cx| {
22347 project.document_highlights(buffer, position, cx)
22348 }))
22349 }
22350
22351 fn definitions(
22352 &self,
22353 buffer: &Entity<Buffer>,
22354 position: text::Anchor,
22355 kind: GotoDefinitionKind,
22356 cx: &mut App,
22357 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22358 Some(self.update(cx, |project, cx| match kind {
22359 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22360 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22361 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22362 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22363 }))
22364 }
22365
22366 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22367 self.update(cx, |project, cx| {
22368 if project
22369 .active_debug_session(cx)
22370 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22371 {
22372 return true;
22373 }
22374
22375 buffer.update(cx, |buffer, cx| {
22376 project.any_language_server_supports_inlay_hints(buffer, cx)
22377 })
22378 })
22379 }
22380
22381 fn inline_values(
22382 &self,
22383 buffer_handle: Entity<Buffer>,
22384 range: Range<text::Anchor>,
22385 cx: &mut App,
22386 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22387 self.update(cx, |project, cx| {
22388 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22389
22390 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22391 })
22392 }
22393
22394 fn inlay_hints(
22395 &self,
22396 buffer_handle: Entity<Buffer>,
22397 range: Range<text::Anchor>,
22398 cx: &mut App,
22399 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22400 Some(self.update(cx, |project, cx| {
22401 project.inlay_hints(buffer_handle, range, cx)
22402 }))
22403 }
22404
22405 fn resolve_inlay_hint(
22406 &self,
22407 hint: InlayHint,
22408 buffer_handle: Entity<Buffer>,
22409 server_id: LanguageServerId,
22410 cx: &mut App,
22411 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22412 Some(self.update(cx, |project, cx| {
22413 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22414 }))
22415 }
22416
22417 fn range_for_rename(
22418 &self,
22419 buffer: &Entity<Buffer>,
22420 position: text::Anchor,
22421 cx: &mut App,
22422 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22423 Some(self.update(cx, |project, cx| {
22424 let buffer = buffer.clone();
22425 let task = project.prepare_rename(buffer.clone(), position, cx);
22426 cx.spawn(async move |_, cx| {
22427 Ok(match task.await? {
22428 PrepareRenameResponse::Success(range) => Some(range),
22429 PrepareRenameResponse::InvalidPosition => None,
22430 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22431 // Fallback on using TreeSitter info to determine identifier range
22432 buffer.read_with(cx, |buffer, _| {
22433 let snapshot = buffer.snapshot();
22434 let (range, kind) = snapshot.surrounding_word(position, false);
22435 if kind != Some(CharKind::Word) {
22436 return None;
22437 }
22438 Some(
22439 snapshot.anchor_before(range.start)
22440 ..snapshot.anchor_after(range.end),
22441 )
22442 })?
22443 }
22444 })
22445 })
22446 }))
22447 }
22448
22449 fn perform_rename(
22450 &self,
22451 buffer: &Entity<Buffer>,
22452 position: text::Anchor,
22453 new_name: String,
22454 cx: &mut App,
22455 ) -> Option<Task<Result<ProjectTransaction>>> {
22456 Some(self.update(cx, |project, cx| {
22457 project.perform_rename(buffer.clone(), position, new_name, cx)
22458 }))
22459 }
22460}
22461
22462fn inlay_hint_settings(
22463 location: Anchor,
22464 snapshot: &MultiBufferSnapshot,
22465 cx: &mut Context<Editor>,
22466) -> InlayHintSettings {
22467 let file = snapshot.file_at(location);
22468 let language = snapshot.language_at(location).map(|l| l.name());
22469 language_settings(language, file, cx).inlay_hints
22470}
22471
22472fn consume_contiguous_rows(
22473 contiguous_row_selections: &mut Vec<Selection<Point>>,
22474 selection: &Selection<Point>,
22475 display_map: &DisplaySnapshot,
22476 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22477) -> (MultiBufferRow, MultiBufferRow) {
22478 contiguous_row_selections.push(selection.clone());
22479 let start_row = starting_row(selection, display_map);
22480 let mut end_row = ending_row(selection, display_map);
22481
22482 while let Some(next_selection) = selections.peek() {
22483 if next_selection.start.row <= end_row.0 {
22484 end_row = ending_row(next_selection, display_map);
22485 contiguous_row_selections.push(selections.next().unwrap().clone());
22486 } else {
22487 break;
22488 }
22489 }
22490 (start_row, end_row)
22491}
22492
22493fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22494 if selection.start.column > 0 {
22495 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22496 } else {
22497 MultiBufferRow(selection.start.row)
22498 }
22499}
22500
22501fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22502 if next_selection.end.column > 0 || next_selection.is_empty() {
22503 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22504 } else {
22505 MultiBufferRow(next_selection.end.row)
22506 }
22507}
22508
22509impl EditorSnapshot {
22510 pub fn remote_selections_in_range<'a>(
22511 &'a self,
22512 range: &'a Range<Anchor>,
22513 collaboration_hub: &dyn CollaborationHub,
22514 cx: &'a App,
22515 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22516 let participant_names = collaboration_hub.user_names(cx);
22517 let participant_indices = collaboration_hub.user_participant_indices(cx);
22518 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22519 let collaborators_by_replica_id = collaborators_by_peer_id
22520 .values()
22521 .map(|collaborator| (collaborator.replica_id, collaborator))
22522 .collect::<HashMap<_, _>>();
22523 self.buffer_snapshot
22524 .selections_in_range(range, false)
22525 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22526 if replica_id == AGENT_REPLICA_ID {
22527 Some(RemoteSelection {
22528 replica_id,
22529 selection,
22530 cursor_shape,
22531 line_mode,
22532 collaborator_id: CollaboratorId::Agent,
22533 user_name: Some("Agent".into()),
22534 color: cx.theme().players().agent(),
22535 })
22536 } else {
22537 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22538 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22539 let user_name = participant_names.get(&collaborator.user_id).cloned();
22540 Some(RemoteSelection {
22541 replica_id,
22542 selection,
22543 cursor_shape,
22544 line_mode,
22545 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22546 user_name,
22547 color: if let Some(index) = participant_index {
22548 cx.theme().players().color_for_participant(index.0)
22549 } else {
22550 cx.theme().players().absent()
22551 },
22552 })
22553 }
22554 })
22555 }
22556
22557 pub fn hunks_for_ranges(
22558 &self,
22559 ranges: impl IntoIterator<Item = Range<Point>>,
22560 ) -> Vec<MultiBufferDiffHunk> {
22561 let mut hunks = Vec::new();
22562 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22563 HashMap::default();
22564 for query_range in ranges {
22565 let query_rows =
22566 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22567 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22568 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22569 ) {
22570 // Include deleted hunks that are adjacent to the query range, because
22571 // otherwise they would be missed.
22572 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22573 if hunk.status().is_deleted() {
22574 intersects_range |= hunk.row_range.start == query_rows.end;
22575 intersects_range |= hunk.row_range.end == query_rows.start;
22576 }
22577 if intersects_range {
22578 if !processed_buffer_rows
22579 .entry(hunk.buffer_id)
22580 .or_default()
22581 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22582 {
22583 continue;
22584 }
22585 hunks.push(hunk);
22586 }
22587 }
22588 }
22589
22590 hunks
22591 }
22592
22593 fn display_diff_hunks_for_rows<'a>(
22594 &'a self,
22595 display_rows: Range<DisplayRow>,
22596 folded_buffers: &'a HashSet<BufferId>,
22597 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22598 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22599 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22600
22601 self.buffer_snapshot
22602 .diff_hunks_in_range(buffer_start..buffer_end)
22603 .filter_map(|hunk| {
22604 if folded_buffers.contains(&hunk.buffer_id) {
22605 return None;
22606 }
22607
22608 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22609 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22610
22611 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22612 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22613
22614 let display_hunk = if hunk_display_start.column() != 0 {
22615 DisplayDiffHunk::Folded {
22616 display_row: hunk_display_start.row(),
22617 }
22618 } else {
22619 let mut end_row = hunk_display_end.row();
22620 if hunk_display_end.column() > 0 {
22621 end_row.0 += 1;
22622 }
22623 let is_created_file = hunk.is_created_file();
22624 DisplayDiffHunk::Unfolded {
22625 status: hunk.status(),
22626 diff_base_byte_range: hunk.diff_base_byte_range,
22627 display_row_range: hunk_display_start.row()..end_row,
22628 multi_buffer_range: Anchor::range_in_buffer(
22629 hunk.excerpt_id,
22630 hunk.buffer_id,
22631 hunk.buffer_range,
22632 ),
22633 is_created_file,
22634 }
22635 };
22636
22637 Some(display_hunk)
22638 })
22639 }
22640
22641 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22642 self.display_snapshot.buffer_snapshot.language_at(position)
22643 }
22644
22645 pub fn is_focused(&self) -> bool {
22646 self.is_focused
22647 }
22648
22649 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22650 self.placeholder_text.as_ref()
22651 }
22652
22653 pub fn scroll_position(&self) -> gpui::Point<f32> {
22654 self.scroll_anchor.scroll_position(&self.display_snapshot)
22655 }
22656
22657 fn gutter_dimensions(
22658 &self,
22659 font_id: FontId,
22660 font_size: Pixels,
22661 max_line_number_width: Pixels,
22662 cx: &App,
22663 ) -> Option<GutterDimensions> {
22664 if !self.show_gutter {
22665 return None;
22666 }
22667
22668 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22669 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22670
22671 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22672 matches!(
22673 ProjectSettings::get_global(cx).git.git_gutter,
22674 Some(GitGutterSetting::TrackedFiles)
22675 )
22676 });
22677 let gutter_settings = EditorSettings::get_global(cx).gutter;
22678 let show_line_numbers = self
22679 .show_line_numbers
22680 .unwrap_or(gutter_settings.line_numbers);
22681 let line_gutter_width = if show_line_numbers {
22682 // Avoid flicker-like gutter resizes when the line number gains another digit by
22683 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22684 let min_width_for_number_on_gutter =
22685 ch_advance * gutter_settings.min_line_number_digits as f32;
22686 max_line_number_width.max(min_width_for_number_on_gutter)
22687 } else {
22688 0.0.into()
22689 };
22690
22691 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22692 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22693
22694 let git_blame_entries_width =
22695 self.git_blame_gutter_max_author_length
22696 .map(|max_author_length| {
22697 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22698 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22699
22700 /// The number of characters to dedicate to gaps and margins.
22701 const SPACING_WIDTH: usize = 4;
22702
22703 let max_char_count = max_author_length.min(renderer.max_author_length())
22704 + ::git::SHORT_SHA_LENGTH
22705 + MAX_RELATIVE_TIMESTAMP.len()
22706 + SPACING_WIDTH;
22707
22708 ch_advance * max_char_count
22709 });
22710
22711 let is_singleton = self.buffer_snapshot.is_singleton();
22712
22713 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22714 left_padding += if !is_singleton {
22715 ch_width * 4.0
22716 } else if show_runnables || show_breakpoints {
22717 ch_width * 3.0
22718 } else if show_git_gutter && show_line_numbers {
22719 ch_width * 2.0
22720 } else if show_git_gutter || show_line_numbers {
22721 ch_width
22722 } else {
22723 px(0.)
22724 };
22725
22726 let shows_folds = is_singleton && gutter_settings.folds;
22727
22728 let right_padding = if shows_folds && show_line_numbers {
22729 ch_width * 4.0
22730 } else if shows_folds || (!is_singleton && show_line_numbers) {
22731 ch_width * 3.0
22732 } else if show_line_numbers {
22733 ch_width
22734 } else {
22735 px(0.)
22736 };
22737
22738 Some(GutterDimensions {
22739 left_padding,
22740 right_padding,
22741 width: line_gutter_width + left_padding + right_padding,
22742 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22743 git_blame_entries_width,
22744 })
22745 }
22746
22747 pub fn render_crease_toggle(
22748 &self,
22749 buffer_row: MultiBufferRow,
22750 row_contains_cursor: bool,
22751 editor: Entity<Editor>,
22752 window: &mut Window,
22753 cx: &mut App,
22754 ) -> Option<AnyElement> {
22755 let folded = self.is_line_folded(buffer_row);
22756 let mut is_foldable = false;
22757
22758 if let Some(crease) = self
22759 .crease_snapshot
22760 .query_row(buffer_row, &self.buffer_snapshot)
22761 {
22762 is_foldable = true;
22763 match crease {
22764 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22765 if let Some(render_toggle) = render_toggle {
22766 let toggle_callback =
22767 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22768 if folded {
22769 editor.update(cx, |editor, cx| {
22770 editor.fold_at(buffer_row, window, cx)
22771 });
22772 } else {
22773 editor.update(cx, |editor, cx| {
22774 editor.unfold_at(buffer_row, window, cx)
22775 });
22776 }
22777 });
22778 return Some((render_toggle)(
22779 buffer_row,
22780 folded,
22781 toggle_callback,
22782 window,
22783 cx,
22784 ));
22785 }
22786 }
22787 }
22788 }
22789
22790 is_foldable |= self.starts_indent(buffer_row);
22791
22792 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22793 Some(
22794 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22795 .toggle_state(folded)
22796 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22797 if folded {
22798 this.unfold_at(buffer_row, window, cx);
22799 } else {
22800 this.fold_at(buffer_row, window, cx);
22801 }
22802 }))
22803 .into_any_element(),
22804 )
22805 } else {
22806 None
22807 }
22808 }
22809
22810 pub fn render_crease_trailer(
22811 &self,
22812 buffer_row: MultiBufferRow,
22813 window: &mut Window,
22814 cx: &mut App,
22815 ) -> Option<AnyElement> {
22816 let folded = self.is_line_folded(buffer_row);
22817 if let Crease::Inline { render_trailer, .. } = self
22818 .crease_snapshot
22819 .query_row(buffer_row, &self.buffer_snapshot)?
22820 {
22821 let render_trailer = render_trailer.as_ref()?;
22822 Some(render_trailer(buffer_row, folded, window, cx))
22823 } else {
22824 None
22825 }
22826 }
22827}
22828
22829impl Deref for EditorSnapshot {
22830 type Target = DisplaySnapshot;
22831
22832 fn deref(&self) -> &Self::Target {
22833 &self.display_snapshot
22834 }
22835}
22836
22837#[derive(Clone, Debug, PartialEq, Eq)]
22838pub enum EditorEvent {
22839 InputIgnored {
22840 text: Arc<str>,
22841 },
22842 InputHandled {
22843 utf16_range_to_replace: Option<Range<isize>>,
22844 text: Arc<str>,
22845 },
22846 ExcerptsAdded {
22847 buffer: Entity<Buffer>,
22848 predecessor: ExcerptId,
22849 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22850 },
22851 ExcerptsRemoved {
22852 ids: Vec<ExcerptId>,
22853 removed_buffer_ids: Vec<BufferId>,
22854 },
22855 BufferFoldToggled {
22856 ids: Vec<ExcerptId>,
22857 folded: bool,
22858 },
22859 ExcerptsEdited {
22860 ids: Vec<ExcerptId>,
22861 },
22862 ExcerptsExpanded {
22863 ids: Vec<ExcerptId>,
22864 },
22865 BufferEdited,
22866 Edited {
22867 transaction_id: clock::Lamport,
22868 },
22869 Reparsed(BufferId),
22870 Focused,
22871 FocusedIn,
22872 Blurred,
22873 DirtyChanged,
22874 Saved,
22875 TitleChanged,
22876 DiffBaseChanged,
22877 SelectionsChanged {
22878 local: bool,
22879 },
22880 ScrollPositionChanged {
22881 local: bool,
22882 autoscroll: bool,
22883 },
22884 Closed,
22885 TransactionUndone {
22886 transaction_id: clock::Lamport,
22887 },
22888 TransactionBegun {
22889 transaction_id: clock::Lamport,
22890 },
22891 Reloaded,
22892 CursorShapeChanged,
22893 BreadcrumbsChanged,
22894 PushedToNavHistory {
22895 anchor: Anchor,
22896 is_deactivate: bool,
22897 },
22898}
22899
22900impl EventEmitter<EditorEvent> for Editor {}
22901
22902impl Focusable for Editor {
22903 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22904 self.focus_handle.clone()
22905 }
22906}
22907
22908impl Render for Editor {
22909 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22910 let settings = ThemeSettings::get_global(cx);
22911
22912 let mut text_style = match self.mode {
22913 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22914 color: cx.theme().colors().editor_foreground,
22915 font_family: settings.ui_font.family.clone(),
22916 font_features: settings.ui_font.features.clone(),
22917 font_fallbacks: settings.ui_font.fallbacks.clone(),
22918 font_size: rems(0.875).into(),
22919 font_weight: settings.ui_font.weight,
22920 line_height: relative(settings.buffer_line_height.value()),
22921 ..Default::default()
22922 },
22923 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22924 color: cx.theme().colors().editor_foreground,
22925 font_family: settings.buffer_font.family.clone(),
22926 font_features: settings.buffer_font.features.clone(),
22927 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22928 font_size: settings.buffer_font_size(cx).into(),
22929 font_weight: settings.buffer_font.weight,
22930 line_height: relative(settings.buffer_line_height.value()),
22931 ..Default::default()
22932 },
22933 };
22934 if let Some(text_style_refinement) = &self.text_style_refinement {
22935 text_style.refine(text_style_refinement)
22936 }
22937
22938 let background = match self.mode {
22939 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22940 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22941 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22942 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22943 };
22944
22945 EditorElement::new(
22946 &cx.entity(),
22947 EditorStyle {
22948 background,
22949 border: cx.theme().colors().border,
22950 local_player: cx.theme().players().local(),
22951 text: text_style,
22952 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22953 syntax: cx.theme().syntax().clone(),
22954 status: cx.theme().status().clone(),
22955 inlay_hints_style: make_inlay_hints_style(cx),
22956 edit_prediction_styles: make_suggestion_styles(cx),
22957 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22958 show_underlines: self.diagnostics_enabled(),
22959 },
22960 )
22961 }
22962}
22963
22964impl EntityInputHandler for Editor {
22965 fn text_for_range(
22966 &mut self,
22967 range_utf16: Range<usize>,
22968 adjusted_range: &mut Option<Range<usize>>,
22969 _: &mut Window,
22970 cx: &mut Context<Self>,
22971 ) -> Option<String> {
22972 let snapshot = self.buffer.read(cx).read(cx);
22973 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22974 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22975 if (start.0..end.0) != range_utf16 {
22976 adjusted_range.replace(start.0..end.0);
22977 }
22978 Some(snapshot.text_for_range(start..end).collect())
22979 }
22980
22981 fn selected_text_range(
22982 &mut self,
22983 ignore_disabled_input: bool,
22984 _: &mut Window,
22985 cx: &mut Context<Self>,
22986 ) -> Option<UTF16Selection> {
22987 // Prevent the IME menu from appearing when holding down an alphabetic key
22988 // while input is disabled.
22989 if !ignore_disabled_input && !self.input_enabled {
22990 return None;
22991 }
22992
22993 let selection = self.selections.newest::<OffsetUtf16>(cx);
22994 let range = selection.range();
22995
22996 Some(UTF16Selection {
22997 range: range.start.0..range.end.0,
22998 reversed: selection.reversed,
22999 })
23000 }
23001
23002 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23003 let snapshot = self.buffer.read(cx).read(cx);
23004 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23005 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23006 }
23007
23008 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23009 self.clear_highlights::<InputComposition>(cx);
23010 self.ime_transaction.take();
23011 }
23012
23013 fn replace_text_in_range(
23014 &mut self,
23015 range_utf16: Option<Range<usize>>,
23016 text: &str,
23017 window: &mut Window,
23018 cx: &mut Context<Self>,
23019 ) {
23020 if !self.input_enabled {
23021 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23022 return;
23023 }
23024
23025 self.transact(window, cx, |this, window, cx| {
23026 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23027 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23028 Some(this.selection_replacement_ranges(range_utf16, cx))
23029 } else {
23030 this.marked_text_ranges(cx)
23031 };
23032
23033 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23034 let newest_selection_id = this.selections.newest_anchor().id;
23035 this.selections
23036 .all::<OffsetUtf16>(cx)
23037 .iter()
23038 .zip(ranges_to_replace.iter())
23039 .find_map(|(selection, range)| {
23040 if selection.id == newest_selection_id {
23041 Some(
23042 (range.start.0 as isize - selection.head().0 as isize)
23043 ..(range.end.0 as isize - selection.head().0 as isize),
23044 )
23045 } else {
23046 None
23047 }
23048 })
23049 });
23050
23051 cx.emit(EditorEvent::InputHandled {
23052 utf16_range_to_replace: range_to_replace,
23053 text: text.into(),
23054 });
23055
23056 if let Some(new_selected_ranges) = new_selected_ranges {
23057 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23058 selections.select_ranges(new_selected_ranges)
23059 });
23060 this.backspace(&Default::default(), window, cx);
23061 }
23062
23063 this.handle_input(text, window, cx);
23064 });
23065
23066 if let Some(transaction) = self.ime_transaction {
23067 self.buffer.update(cx, |buffer, cx| {
23068 buffer.group_until_transaction(transaction, cx);
23069 });
23070 }
23071
23072 self.unmark_text(window, cx);
23073 }
23074
23075 fn replace_and_mark_text_in_range(
23076 &mut self,
23077 range_utf16: Option<Range<usize>>,
23078 text: &str,
23079 new_selected_range_utf16: Option<Range<usize>>,
23080 window: &mut Window,
23081 cx: &mut Context<Self>,
23082 ) {
23083 if !self.input_enabled {
23084 return;
23085 }
23086
23087 let transaction = self.transact(window, cx, |this, window, cx| {
23088 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23089 let snapshot = this.buffer.read(cx).read(cx);
23090 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23091 for marked_range in &mut marked_ranges {
23092 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23093 marked_range.start.0 += relative_range_utf16.start;
23094 marked_range.start =
23095 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23096 marked_range.end =
23097 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23098 }
23099 }
23100 Some(marked_ranges)
23101 } else if let Some(range_utf16) = range_utf16 {
23102 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23103 Some(this.selection_replacement_ranges(range_utf16, cx))
23104 } else {
23105 None
23106 };
23107
23108 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23109 let newest_selection_id = this.selections.newest_anchor().id;
23110 this.selections
23111 .all::<OffsetUtf16>(cx)
23112 .iter()
23113 .zip(ranges_to_replace.iter())
23114 .find_map(|(selection, range)| {
23115 if selection.id == newest_selection_id {
23116 Some(
23117 (range.start.0 as isize - selection.head().0 as isize)
23118 ..(range.end.0 as isize - selection.head().0 as isize),
23119 )
23120 } else {
23121 None
23122 }
23123 })
23124 });
23125
23126 cx.emit(EditorEvent::InputHandled {
23127 utf16_range_to_replace: range_to_replace,
23128 text: text.into(),
23129 });
23130
23131 if let Some(ranges) = ranges_to_replace {
23132 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23133 s.select_ranges(ranges)
23134 });
23135 }
23136
23137 let marked_ranges = {
23138 let snapshot = this.buffer.read(cx).read(cx);
23139 this.selections
23140 .disjoint_anchors()
23141 .iter()
23142 .map(|selection| {
23143 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23144 })
23145 .collect::<Vec<_>>()
23146 };
23147
23148 if text.is_empty() {
23149 this.unmark_text(window, cx);
23150 } else {
23151 this.highlight_text::<InputComposition>(
23152 marked_ranges.clone(),
23153 HighlightStyle {
23154 underline: Some(UnderlineStyle {
23155 thickness: px(1.),
23156 color: None,
23157 wavy: false,
23158 }),
23159 ..Default::default()
23160 },
23161 cx,
23162 );
23163 }
23164
23165 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23166 let use_autoclose = this.use_autoclose;
23167 let use_auto_surround = this.use_auto_surround;
23168 this.set_use_autoclose(false);
23169 this.set_use_auto_surround(false);
23170 this.handle_input(text, window, cx);
23171 this.set_use_autoclose(use_autoclose);
23172 this.set_use_auto_surround(use_auto_surround);
23173
23174 if let Some(new_selected_range) = new_selected_range_utf16 {
23175 let snapshot = this.buffer.read(cx).read(cx);
23176 let new_selected_ranges = marked_ranges
23177 .into_iter()
23178 .map(|marked_range| {
23179 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23180 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23181 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23182 snapshot.clip_offset_utf16(new_start, Bias::Left)
23183 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23184 })
23185 .collect::<Vec<_>>();
23186
23187 drop(snapshot);
23188 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23189 selections.select_ranges(new_selected_ranges)
23190 });
23191 }
23192 });
23193
23194 self.ime_transaction = self.ime_transaction.or(transaction);
23195 if let Some(transaction) = self.ime_transaction {
23196 self.buffer.update(cx, |buffer, cx| {
23197 buffer.group_until_transaction(transaction, cx);
23198 });
23199 }
23200
23201 if self.text_highlights::<InputComposition>(cx).is_none() {
23202 self.ime_transaction.take();
23203 }
23204 }
23205
23206 fn bounds_for_range(
23207 &mut self,
23208 range_utf16: Range<usize>,
23209 element_bounds: gpui::Bounds<Pixels>,
23210 window: &mut Window,
23211 cx: &mut Context<Self>,
23212 ) -> Option<gpui::Bounds<Pixels>> {
23213 let text_layout_details = self.text_layout_details(window);
23214 let CharacterDimensions {
23215 em_width,
23216 em_advance,
23217 line_height,
23218 } = self.character_dimensions(window);
23219
23220 let snapshot = self.snapshot(window, cx);
23221 let scroll_position = snapshot.scroll_position();
23222 let scroll_left = scroll_position.x * em_advance;
23223
23224 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23225 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23226 + self.gutter_dimensions.full_width();
23227 let y = line_height * (start.row().as_f32() - scroll_position.y);
23228
23229 Some(Bounds {
23230 origin: element_bounds.origin + point(x, y),
23231 size: size(em_width, line_height),
23232 })
23233 }
23234
23235 fn character_index_for_point(
23236 &mut self,
23237 point: gpui::Point<Pixels>,
23238 _window: &mut Window,
23239 _cx: &mut Context<Self>,
23240 ) -> Option<usize> {
23241 let position_map = self.last_position_map.as_ref()?;
23242 if !position_map.text_hitbox.contains(&point) {
23243 return None;
23244 }
23245 let display_point = position_map.point_for_position(point).previous_valid;
23246 let anchor = position_map
23247 .snapshot
23248 .display_point_to_anchor(display_point, Bias::Left);
23249 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23250 Some(utf16_offset.0)
23251 }
23252}
23253
23254trait SelectionExt {
23255 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23256 fn spanned_rows(
23257 &self,
23258 include_end_if_at_line_start: bool,
23259 map: &DisplaySnapshot,
23260 ) -> Range<MultiBufferRow>;
23261}
23262
23263impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23264 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23265 let start = self
23266 .start
23267 .to_point(&map.buffer_snapshot)
23268 .to_display_point(map);
23269 let end = self
23270 .end
23271 .to_point(&map.buffer_snapshot)
23272 .to_display_point(map);
23273 if self.reversed {
23274 end..start
23275 } else {
23276 start..end
23277 }
23278 }
23279
23280 fn spanned_rows(
23281 &self,
23282 include_end_if_at_line_start: bool,
23283 map: &DisplaySnapshot,
23284 ) -> Range<MultiBufferRow> {
23285 let start = self.start.to_point(&map.buffer_snapshot);
23286 let mut end = self.end.to_point(&map.buffer_snapshot);
23287 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23288 end.row -= 1;
23289 }
23290
23291 let buffer_start = map.prev_line_boundary(start).0;
23292 let buffer_end = map.next_line_boundary(end).0;
23293 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23294 }
23295}
23296
23297impl<T: InvalidationRegion> InvalidationStack<T> {
23298 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23299 where
23300 S: Clone + ToOffset,
23301 {
23302 while let Some(region) = self.last() {
23303 let all_selections_inside_invalidation_ranges =
23304 if selections.len() == region.ranges().len() {
23305 selections
23306 .iter()
23307 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23308 .all(|(selection, invalidation_range)| {
23309 let head = selection.head().to_offset(buffer);
23310 invalidation_range.start <= head && invalidation_range.end >= head
23311 })
23312 } else {
23313 false
23314 };
23315
23316 if all_selections_inside_invalidation_ranges {
23317 break;
23318 } else {
23319 self.pop();
23320 }
23321 }
23322 }
23323}
23324
23325impl<T> Default for InvalidationStack<T> {
23326 fn default() -> Self {
23327 Self(Default::default())
23328 }
23329}
23330
23331impl<T> Deref for InvalidationStack<T> {
23332 type Target = Vec<T>;
23333
23334 fn deref(&self) -> &Self::Target {
23335 &self.0
23336 }
23337}
23338
23339impl<T> DerefMut for InvalidationStack<T> {
23340 fn deref_mut(&mut self) -> &mut Self::Target {
23341 &mut self.0
23342 }
23343}
23344
23345impl InvalidationRegion for SnippetState {
23346 fn ranges(&self) -> &[Range<Anchor>] {
23347 &self.ranges[self.active_index]
23348 }
23349}
23350
23351fn edit_prediction_edit_text(
23352 current_snapshot: &BufferSnapshot,
23353 edits: &[(Range<Anchor>, String)],
23354 edit_preview: &EditPreview,
23355 include_deletions: bool,
23356 cx: &App,
23357) -> HighlightedText {
23358 let edits = edits
23359 .iter()
23360 .map(|(anchor, text)| {
23361 (
23362 anchor.start.text_anchor..anchor.end.text_anchor,
23363 text.clone(),
23364 )
23365 })
23366 .collect::<Vec<_>>();
23367
23368 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23369}
23370
23371fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23372 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23373 // Just show the raw edit text with basic styling
23374 let mut text = String::new();
23375 let mut highlights = Vec::new();
23376
23377 let insertion_highlight_style = HighlightStyle {
23378 color: Some(cx.theme().colors().text),
23379 ..Default::default()
23380 };
23381
23382 for (_, edit_text) in edits {
23383 let start_offset = text.len();
23384 text.push_str(edit_text);
23385 let end_offset = text.len();
23386
23387 if start_offset < end_offset {
23388 highlights.push((start_offset..end_offset, insertion_highlight_style));
23389 }
23390 }
23391
23392 HighlightedText {
23393 text: text.into(),
23394 highlights,
23395 }
23396}
23397
23398pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23399 match severity {
23400 lsp::DiagnosticSeverity::ERROR => colors.error,
23401 lsp::DiagnosticSeverity::WARNING => colors.warning,
23402 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23403 lsp::DiagnosticSeverity::HINT => colors.info,
23404 _ => colors.ignored,
23405 }
23406}
23407
23408pub fn styled_runs_for_code_label<'a>(
23409 label: &'a CodeLabel,
23410 syntax_theme: &'a theme::SyntaxTheme,
23411) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23412 let fade_out = HighlightStyle {
23413 fade_out: Some(0.35),
23414 ..Default::default()
23415 };
23416
23417 let mut prev_end = label.filter_range.end;
23418 label
23419 .runs
23420 .iter()
23421 .enumerate()
23422 .flat_map(move |(ix, (range, highlight_id))| {
23423 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23424 style
23425 } else {
23426 return Default::default();
23427 };
23428 let mut muted_style = style;
23429 muted_style.highlight(fade_out);
23430
23431 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23432 if range.start >= label.filter_range.end {
23433 if range.start > prev_end {
23434 runs.push((prev_end..range.start, fade_out));
23435 }
23436 runs.push((range.clone(), muted_style));
23437 } else if range.end <= label.filter_range.end {
23438 runs.push((range.clone(), style));
23439 } else {
23440 runs.push((range.start..label.filter_range.end, style));
23441 runs.push((label.filter_range.end..range.end, muted_style));
23442 }
23443 prev_end = cmp::max(prev_end, range.end);
23444
23445 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23446 runs.push((prev_end..label.text.len(), fade_out));
23447 }
23448
23449 runs
23450 })
23451}
23452
23453pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23454 let mut prev_index = 0;
23455 let mut prev_codepoint: Option<char> = None;
23456 text.char_indices()
23457 .chain([(text.len(), '\0')])
23458 .filter_map(move |(index, codepoint)| {
23459 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23460 let is_boundary = index == text.len()
23461 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23462 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23463 if is_boundary {
23464 let chunk = &text[prev_index..index];
23465 prev_index = index;
23466 Some(chunk)
23467 } else {
23468 None
23469 }
23470 })
23471}
23472
23473pub trait RangeToAnchorExt: Sized {
23474 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23475
23476 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23477 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23478 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23479 }
23480}
23481
23482impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23483 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23484 let start_offset = self.start.to_offset(snapshot);
23485 let end_offset = self.end.to_offset(snapshot);
23486 if start_offset == end_offset {
23487 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23488 } else {
23489 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23490 }
23491 }
23492}
23493
23494pub trait RowExt {
23495 fn as_f32(&self) -> f32;
23496
23497 fn next_row(&self) -> Self;
23498
23499 fn previous_row(&self) -> Self;
23500
23501 fn minus(&self, other: Self) -> u32;
23502}
23503
23504impl RowExt for DisplayRow {
23505 fn as_f32(&self) -> f32 {
23506 self.0 as f32
23507 }
23508
23509 fn next_row(&self) -> Self {
23510 Self(self.0 + 1)
23511 }
23512
23513 fn previous_row(&self) -> Self {
23514 Self(self.0.saturating_sub(1))
23515 }
23516
23517 fn minus(&self, other: Self) -> u32 {
23518 self.0 - other.0
23519 }
23520}
23521
23522impl RowExt for MultiBufferRow {
23523 fn as_f32(&self) -> f32 {
23524 self.0 as f32
23525 }
23526
23527 fn next_row(&self) -> Self {
23528 Self(self.0 + 1)
23529 }
23530
23531 fn previous_row(&self) -> Self {
23532 Self(self.0.saturating_sub(1))
23533 }
23534
23535 fn minus(&self, other: Self) -> u32 {
23536 self.0 - other.0
23537 }
23538}
23539
23540trait RowRangeExt {
23541 type Row;
23542
23543 fn len(&self) -> usize;
23544
23545 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23546}
23547
23548impl RowRangeExt for Range<MultiBufferRow> {
23549 type Row = MultiBufferRow;
23550
23551 fn len(&self) -> usize {
23552 (self.end.0 - self.start.0) as usize
23553 }
23554
23555 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23556 (self.start.0..self.end.0).map(MultiBufferRow)
23557 }
23558}
23559
23560impl RowRangeExt for Range<DisplayRow> {
23561 type Row = DisplayRow;
23562
23563 fn len(&self) -> usize {
23564 (self.end.0 - self.start.0) as usize
23565 }
23566
23567 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23568 (self.start.0..self.end.0).map(DisplayRow)
23569 }
23570}
23571
23572/// If select range has more than one line, we
23573/// just point the cursor to range.start.
23574fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23575 if range.start.row == range.end.row {
23576 range
23577 } else {
23578 range.start..range.start
23579 }
23580}
23581pub struct KillRing(ClipboardItem);
23582impl Global for KillRing {}
23583
23584const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23585
23586enum BreakpointPromptEditAction {
23587 Log,
23588 Condition,
23589 HitCondition,
23590}
23591
23592struct BreakpointPromptEditor {
23593 pub(crate) prompt: Entity<Editor>,
23594 editor: WeakEntity<Editor>,
23595 breakpoint_anchor: Anchor,
23596 breakpoint: Breakpoint,
23597 edit_action: BreakpointPromptEditAction,
23598 block_ids: HashSet<CustomBlockId>,
23599 editor_margins: Arc<Mutex<EditorMargins>>,
23600 _subscriptions: Vec<Subscription>,
23601}
23602
23603impl BreakpointPromptEditor {
23604 const MAX_LINES: u8 = 4;
23605
23606 fn new(
23607 editor: WeakEntity<Editor>,
23608 breakpoint_anchor: Anchor,
23609 breakpoint: Breakpoint,
23610 edit_action: BreakpointPromptEditAction,
23611 window: &mut Window,
23612 cx: &mut Context<Self>,
23613 ) -> Self {
23614 let base_text = match edit_action {
23615 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23616 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23617 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23618 }
23619 .map(|msg| msg.to_string())
23620 .unwrap_or_default();
23621
23622 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23623 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23624
23625 let prompt = cx.new(|cx| {
23626 let mut prompt = Editor::new(
23627 EditorMode::AutoHeight {
23628 min_lines: 1,
23629 max_lines: Some(Self::MAX_LINES as usize),
23630 },
23631 buffer,
23632 None,
23633 window,
23634 cx,
23635 );
23636 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23637 prompt.set_show_cursor_when_unfocused(false, cx);
23638 prompt.set_placeholder_text(
23639 match edit_action {
23640 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23641 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23642 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23643 },
23644 cx,
23645 );
23646
23647 prompt
23648 });
23649
23650 Self {
23651 prompt,
23652 editor,
23653 breakpoint_anchor,
23654 breakpoint,
23655 edit_action,
23656 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23657 block_ids: Default::default(),
23658 _subscriptions: vec![],
23659 }
23660 }
23661
23662 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23663 self.block_ids.extend(block_ids)
23664 }
23665
23666 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23667 if let Some(editor) = self.editor.upgrade() {
23668 let message = self
23669 .prompt
23670 .read(cx)
23671 .buffer
23672 .read(cx)
23673 .as_singleton()
23674 .expect("A multi buffer in breakpoint prompt isn't possible")
23675 .read(cx)
23676 .as_rope()
23677 .to_string();
23678
23679 editor.update(cx, |editor, cx| {
23680 editor.edit_breakpoint_at_anchor(
23681 self.breakpoint_anchor,
23682 self.breakpoint.clone(),
23683 match self.edit_action {
23684 BreakpointPromptEditAction::Log => {
23685 BreakpointEditAction::EditLogMessage(message.into())
23686 }
23687 BreakpointPromptEditAction::Condition => {
23688 BreakpointEditAction::EditCondition(message.into())
23689 }
23690 BreakpointPromptEditAction::HitCondition => {
23691 BreakpointEditAction::EditHitCondition(message.into())
23692 }
23693 },
23694 cx,
23695 );
23696
23697 editor.remove_blocks(self.block_ids.clone(), None, cx);
23698 cx.focus_self(window);
23699 });
23700 }
23701 }
23702
23703 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23704 self.editor
23705 .update(cx, |editor, cx| {
23706 editor.remove_blocks(self.block_ids.clone(), None, cx);
23707 window.focus(&editor.focus_handle);
23708 })
23709 .log_err();
23710 }
23711
23712 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23713 let settings = ThemeSettings::get_global(cx);
23714 let text_style = TextStyle {
23715 color: if self.prompt.read(cx).read_only(cx) {
23716 cx.theme().colors().text_disabled
23717 } else {
23718 cx.theme().colors().text
23719 },
23720 font_family: settings.buffer_font.family.clone(),
23721 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23722 font_size: settings.buffer_font_size(cx).into(),
23723 font_weight: settings.buffer_font.weight,
23724 line_height: relative(settings.buffer_line_height.value()),
23725 ..Default::default()
23726 };
23727 EditorElement::new(
23728 &self.prompt,
23729 EditorStyle {
23730 background: cx.theme().colors().editor_background,
23731 local_player: cx.theme().players().local(),
23732 text: text_style,
23733 ..Default::default()
23734 },
23735 )
23736 }
23737}
23738
23739impl Render for BreakpointPromptEditor {
23740 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23741 let editor_margins = *self.editor_margins.lock();
23742 let gutter_dimensions = editor_margins.gutter;
23743 h_flex()
23744 .key_context("Editor")
23745 .bg(cx.theme().colors().editor_background)
23746 .border_y_1()
23747 .border_color(cx.theme().status().info_border)
23748 .size_full()
23749 .py(window.line_height() / 2.5)
23750 .on_action(cx.listener(Self::confirm))
23751 .on_action(cx.listener(Self::cancel))
23752 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23753 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23754 }
23755}
23756
23757impl Focusable for BreakpointPromptEditor {
23758 fn focus_handle(&self, cx: &App) -> FocusHandle {
23759 self.prompt.focus_handle(cx)
23760 }
23761}
23762
23763fn all_edits_insertions_or_deletions(
23764 edits: &Vec<(Range<Anchor>, String)>,
23765 snapshot: &MultiBufferSnapshot,
23766) -> bool {
23767 let mut all_insertions = true;
23768 let mut all_deletions = true;
23769
23770 for (range, new_text) in edits.iter() {
23771 let range_is_empty = range.to_offset(snapshot).is_empty();
23772 let text_is_empty = new_text.is_empty();
23773
23774 if range_is_empty != text_is_empty {
23775 if range_is_empty {
23776 all_deletions = false;
23777 } else {
23778 all_insertions = false;
23779 }
23780 } else {
23781 return false;
23782 }
23783
23784 if !all_insertions && !all_deletions {
23785 return false;
23786 }
23787 }
23788 all_insertions || all_deletions
23789}
23790
23791struct MissingEditPredictionKeybindingTooltip;
23792
23793impl Render for MissingEditPredictionKeybindingTooltip {
23794 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23795 ui::tooltip_container(window, cx, |container, _, cx| {
23796 container
23797 .flex_shrink_0()
23798 .max_w_80()
23799 .min_h(rems_from_px(124.))
23800 .justify_between()
23801 .child(
23802 v_flex()
23803 .flex_1()
23804 .text_ui_sm(cx)
23805 .child(Label::new("Conflict with Accept Keybinding"))
23806 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23807 )
23808 .child(
23809 h_flex()
23810 .pb_1()
23811 .gap_1()
23812 .items_end()
23813 .w_full()
23814 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23815 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23816 }))
23817 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23818 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23819 })),
23820 )
23821 })
23822 }
23823}
23824
23825#[derive(Debug, Clone, Copy, PartialEq)]
23826pub struct LineHighlight {
23827 pub background: Background,
23828 pub border: Option<gpui::Hsla>,
23829 pub include_gutter: bool,
23830 pub type_id: Option<TypeId>,
23831}
23832
23833struct LineManipulationResult {
23834 pub new_text: String,
23835 pub line_count_before: usize,
23836 pub line_count_after: usize,
23837}
23838
23839fn render_diff_hunk_controls(
23840 row: u32,
23841 status: &DiffHunkStatus,
23842 hunk_range: Range<Anchor>,
23843 is_created_file: bool,
23844 line_height: Pixels,
23845 editor: &Entity<Editor>,
23846 _window: &mut Window,
23847 cx: &mut App,
23848) -> AnyElement {
23849 h_flex()
23850 .h(line_height)
23851 .mr_1()
23852 .gap_1()
23853 .px_0p5()
23854 .pb_1()
23855 .border_x_1()
23856 .border_b_1()
23857 .border_color(cx.theme().colors().border_variant)
23858 .rounded_b_lg()
23859 .bg(cx.theme().colors().editor_background)
23860 .gap_1()
23861 .block_mouse_except_scroll()
23862 .shadow_md()
23863 .child(if status.has_secondary_hunk() {
23864 Button::new(("stage", row as u64), "Stage")
23865 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23866 .tooltip({
23867 let focus_handle = editor.focus_handle(cx);
23868 move |window, cx| {
23869 Tooltip::for_action_in(
23870 "Stage Hunk",
23871 &::git::ToggleStaged,
23872 &focus_handle,
23873 window,
23874 cx,
23875 )
23876 }
23877 })
23878 .on_click({
23879 let editor = editor.clone();
23880 move |_event, _window, cx| {
23881 editor.update(cx, |editor, cx| {
23882 editor.stage_or_unstage_diff_hunks(
23883 true,
23884 vec![hunk_range.start..hunk_range.start],
23885 cx,
23886 );
23887 });
23888 }
23889 })
23890 } else {
23891 Button::new(("unstage", row as u64), "Unstage")
23892 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23893 .tooltip({
23894 let focus_handle = editor.focus_handle(cx);
23895 move |window, cx| {
23896 Tooltip::for_action_in(
23897 "Unstage Hunk",
23898 &::git::ToggleStaged,
23899 &focus_handle,
23900 window,
23901 cx,
23902 )
23903 }
23904 })
23905 .on_click({
23906 let editor = editor.clone();
23907 move |_event, _window, cx| {
23908 editor.update(cx, |editor, cx| {
23909 editor.stage_or_unstage_diff_hunks(
23910 false,
23911 vec![hunk_range.start..hunk_range.start],
23912 cx,
23913 );
23914 });
23915 }
23916 })
23917 })
23918 .child(
23919 Button::new(("restore", row as u64), "Restore")
23920 .tooltip({
23921 let focus_handle = editor.focus_handle(cx);
23922 move |window, cx| {
23923 Tooltip::for_action_in(
23924 "Restore Hunk",
23925 &::git::Restore,
23926 &focus_handle,
23927 window,
23928 cx,
23929 )
23930 }
23931 })
23932 .on_click({
23933 let editor = editor.clone();
23934 move |_event, window, cx| {
23935 editor.update(cx, |editor, cx| {
23936 let snapshot = editor.snapshot(window, cx);
23937 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23938 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23939 });
23940 }
23941 })
23942 .disabled(is_created_file),
23943 )
23944 .when(
23945 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23946 |el| {
23947 el.child(
23948 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23949 .shape(IconButtonShape::Square)
23950 .icon_size(IconSize::Small)
23951 // .disabled(!has_multiple_hunks)
23952 .tooltip({
23953 let focus_handle = editor.focus_handle(cx);
23954 move |window, cx| {
23955 Tooltip::for_action_in(
23956 "Next Hunk",
23957 &GoToHunk,
23958 &focus_handle,
23959 window,
23960 cx,
23961 )
23962 }
23963 })
23964 .on_click({
23965 let editor = editor.clone();
23966 move |_event, window, cx| {
23967 editor.update(cx, |editor, cx| {
23968 let snapshot = editor.snapshot(window, cx);
23969 let position =
23970 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23971 editor.go_to_hunk_before_or_after_position(
23972 &snapshot,
23973 position,
23974 Direction::Next,
23975 window,
23976 cx,
23977 );
23978 editor.expand_selected_diff_hunks(cx);
23979 });
23980 }
23981 }),
23982 )
23983 .child(
23984 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23985 .shape(IconButtonShape::Square)
23986 .icon_size(IconSize::Small)
23987 // .disabled(!has_multiple_hunks)
23988 .tooltip({
23989 let focus_handle = editor.focus_handle(cx);
23990 move |window, cx| {
23991 Tooltip::for_action_in(
23992 "Previous Hunk",
23993 &GoToPreviousHunk,
23994 &focus_handle,
23995 window,
23996 cx,
23997 )
23998 }
23999 })
24000 .on_click({
24001 let editor = editor.clone();
24002 move |_event, window, cx| {
24003 editor.update(cx, |editor, cx| {
24004 let snapshot = editor.snapshot(window, cx);
24005 let point =
24006 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24007 editor.go_to_hunk_before_or_after_position(
24008 &snapshot,
24009 point,
24010 Direction::Prev,
24011 window,
24012 cx,
24013 );
24014 editor.expand_selected_diff_hunks(cx);
24015 });
24016 }
24017 }),
24018 )
24019 },
24020 )
24021 .into_any_element()
24022}