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 if let Some(current) = last.current.as_mut() {
947 mem::swap(&mut last.original, current);
948 }
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
1043 // TODO: make this a access method
1044 pub project: Option<Entity<Project>>,
1045 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1046 completion_provider: Option<Rc<dyn CompletionProvider>>,
1047 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1048 blink_manager: Entity<BlinkManager>,
1049 show_cursor_names: bool,
1050 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1051 pub show_local_selections: bool,
1052 mode: EditorMode,
1053 show_breadcrumbs: bool,
1054 show_gutter: bool,
1055 show_scrollbars: ScrollbarAxes,
1056 minimap_visibility: MinimapVisibility,
1057 offset_content: bool,
1058 disable_expand_excerpt_buttons: bool,
1059 show_line_numbers: Option<bool>,
1060 use_relative_line_numbers: Option<bool>,
1061 show_git_diff_gutter: Option<bool>,
1062 show_code_actions: Option<bool>,
1063 show_runnables: Option<bool>,
1064 show_breakpoints: Option<bool>,
1065 show_wrap_guides: Option<bool>,
1066 show_indent_guides: Option<bool>,
1067 placeholder_text: Option<Arc<str>>,
1068 highlight_order: usize,
1069 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1070 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1071 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1072 scrollbar_marker_state: ScrollbarMarkerState,
1073 active_indent_guides_state: ActiveIndentGuidesState,
1074 nav_history: Option<ItemNavHistory>,
1075 context_menu: RefCell<Option<CodeContextMenu>>,
1076 context_menu_options: Option<ContextMenuOptions>,
1077 mouse_context_menu: Option<MouseContextMenu>,
1078 completion_tasks: Vec<(CompletionId, Task<()>)>,
1079 inline_blame_popover: Option<InlineBlamePopover>,
1080 inline_blame_popover_show_task: Option<Task<()>>,
1081 signature_help_state: SignatureHelpState,
1082 auto_signature_help: Option<bool>,
1083 find_all_references_task_sources: Vec<Anchor>,
1084 next_completion_id: CompletionId,
1085 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1086 code_actions_task: Option<Task<Result<()>>>,
1087 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1088 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1089 document_highlights_task: Option<Task<()>>,
1090 linked_editing_range_task: Option<Task<Option<()>>>,
1091 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1092 pending_rename: Option<RenameState>,
1093 searchable: bool,
1094 cursor_shape: CursorShape,
1095 current_line_highlight: Option<CurrentLineHighlight>,
1096 collapse_matches: bool,
1097 autoindent_mode: Option<AutoindentMode>,
1098 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1099 input_enabled: bool,
1100 use_modal_editing: bool,
1101 read_only: bool,
1102 leader_id: Option<CollaboratorId>,
1103 remote_id: Option<ViewId>,
1104 pub hover_state: HoverState,
1105 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1106 gutter_hovered: bool,
1107 hovered_link_state: Option<HoveredLinkState>,
1108 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1109 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1110 active_edit_prediction: Option<EditPredictionState>,
1111 /// Used to prevent flickering as the user types while the menu is open
1112 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1113 edit_prediction_settings: EditPredictionSettings,
1114 edit_predictions_hidden_for_vim_mode: bool,
1115 show_edit_predictions_override: Option<bool>,
1116 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1117 edit_prediction_preview: EditPredictionPreview,
1118 edit_prediction_indent_conflict: bool,
1119 edit_prediction_requires_modifier_in_indent_conflict: bool,
1120 inlay_hint_cache: InlayHintCache,
1121 next_inlay_id: usize,
1122 _subscriptions: Vec<Subscription>,
1123 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1124 gutter_dimensions: GutterDimensions,
1125 style: Option<EditorStyle>,
1126 text_style_refinement: Option<TextStyleRefinement>,
1127 next_editor_action_id: EditorActionId,
1128 editor_actions: Rc<
1129 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1130 >,
1131 use_autoclose: bool,
1132 use_auto_surround: bool,
1133 auto_replace_emoji_shortcode: bool,
1134 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1135 show_git_blame_gutter: bool,
1136 show_git_blame_inline: bool,
1137 show_git_blame_inline_delay_task: Option<Task<()>>,
1138 git_blame_inline_enabled: bool,
1139 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1140 serialize_dirty_buffers: bool,
1141 show_selection_menu: Option<bool>,
1142 blame: Option<Entity<GitBlame>>,
1143 blame_subscription: Option<Subscription>,
1144 custom_context_menu: Option<
1145 Box<
1146 dyn 'static
1147 + Fn(
1148 &mut Self,
1149 DisplayPoint,
1150 &mut Window,
1151 &mut Context<Self>,
1152 ) -> Option<Entity<ui::ContextMenu>>,
1153 >,
1154 >,
1155 last_bounds: Option<Bounds<Pixels>>,
1156 last_position_map: Option<Rc<PositionMap>>,
1157 expect_bounds_change: Option<Bounds<Pixels>>,
1158 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1159 tasks_update_task: Option<Task<()>>,
1160 breakpoint_store: Option<Entity<BreakpointStore>>,
1161 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1162 hovered_diff_hunk_row: Option<DisplayRow>,
1163 pull_diagnostics_task: Task<()>,
1164 in_project_search: bool,
1165 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1166 breadcrumb_header: Option<String>,
1167 focused_block: Option<FocusedBlock>,
1168 next_scroll_position: NextScrollCursorCenterTopBottom,
1169 addons: HashMap<TypeId, Box<dyn Addon>>,
1170 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1171 load_diff_task: Option<Shared<Task<()>>>,
1172 /// Whether we are temporarily displaying a diff other than git's
1173 temporary_diff_override: bool,
1174 selection_mark_mode: bool,
1175 toggle_fold_multiple_buffers: Task<()>,
1176 _scroll_cursor_center_top_bottom_task: Task<()>,
1177 serialize_selections: Task<()>,
1178 serialize_folds: Task<()>,
1179 mouse_cursor_hidden: bool,
1180 minimap: Option<Entity<Self>>,
1181 hide_mouse_mode: HideMouseMode,
1182 pub change_list: ChangeList,
1183 inline_value_cache: InlineValueCache,
1184 selection_drag_state: SelectionDragState,
1185 next_color_inlay_id: usize,
1186 colors: Option<LspColorData>,
1187 folding_newlines: Task<()>,
1188}
1189
1190#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1191enum NextScrollCursorCenterTopBottom {
1192 #[default]
1193 Center,
1194 Top,
1195 Bottom,
1196}
1197
1198impl NextScrollCursorCenterTopBottom {
1199 fn next(&self) -> Self {
1200 match self {
1201 Self::Center => Self::Top,
1202 Self::Top => Self::Bottom,
1203 Self::Bottom => Self::Center,
1204 }
1205 }
1206}
1207
1208#[derive(Clone)]
1209pub struct EditorSnapshot {
1210 pub mode: EditorMode,
1211 show_gutter: bool,
1212 show_line_numbers: Option<bool>,
1213 show_git_diff_gutter: Option<bool>,
1214 show_code_actions: Option<bool>,
1215 show_runnables: Option<bool>,
1216 show_breakpoints: Option<bool>,
1217 git_blame_gutter_max_author_length: Option<usize>,
1218 pub display_snapshot: DisplaySnapshot,
1219 pub placeholder_text: Option<Arc<str>>,
1220 is_focused: bool,
1221 scroll_anchor: ScrollAnchor,
1222 ongoing_scroll: OngoingScroll,
1223 current_line_highlight: CurrentLineHighlight,
1224 gutter_hovered: bool,
1225}
1226
1227#[derive(Default, Debug, Clone, Copy)]
1228pub struct GutterDimensions {
1229 pub left_padding: Pixels,
1230 pub right_padding: Pixels,
1231 pub width: Pixels,
1232 pub margin: Pixels,
1233 pub git_blame_entries_width: Option<Pixels>,
1234}
1235
1236impl GutterDimensions {
1237 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1238 Self {
1239 margin: Self::default_gutter_margin(font_id, font_size, cx),
1240 ..Default::default()
1241 }
1242 }
1243
1244 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1245 -cx.text_system().descent(font_id, font_size)
1246 }
1247 /// The full width of the space taken up by the gutter.
1248 pub fn full_width(&self) -> Pixels {
1249 self.margin + self.width
1250 }
1251
1252 /// The width of the space reserved for the fold indicators,
1253 /// use alongside 'justify_end' and `gutter_width` to
1254 /// right align content with the line numbers
1255 pub fn fold_area_width(&self) -> Pixels {
1256 self.margin + self.right_padding
1257 }
1258}
1259
1260struct CharacterDimensions {
1261 em_width: Pixels,
1262 em_advance: Pixels,
1263 line_height: Pixels,
1264}
1265
1266#[derive(Debug)]
1267pub struct RemoteSelection {
1268 pub replica_id: ReplicaId,
1269 pub selection: Selection<Anchor>,
1270 pub cursor_shape: CursorShape,
1271 pub collaborator_id: CollaboratorId,
1272 pub line_mode: bool,
1273 pub user_name: Option<SharedString>,
1274 pub color: PlayerColor,
1275}
1276
1277#[derive(Clone, Debug)]
1278struct SelectionHistoryEntry {
1279 selections: Arc<[Selection<Anchor>]>,
1280 select_next_state: Option<SelectNextState>,
1281 select_prev_state: Option<SelectNextState>,
1282 add_selections_state: Option<AddSelectionsState>,
1283}
1284
1285#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1286enum SelectionHistoryMode {
1287 Normal,
1288 Undoing,
1289 Redoing,
1290 Skipping,
1291}
1292
1293#[derive(Clone, PartialEq, Eq, Hash)]
1294struct HoveredCursor {
1295 replica_id: u16,
1296 selection_id: usize,
1297}
1298
1299impl Default for SelectionHistoryMode {
1300 fn default() -> Self {
1301 Self::Normal
1302 }
1303}
1304
1305#[derive(Debug)]
1306/// SelectionEffects controls the side-effects of updating the selection.
1307///
1308/// The default behaviour does "what you mostly want":
1309/// - it pushes to the nav history if the cursor moved by >10 lines
1310/// - it re-triggers completion requests
1311/// - it scrolls to fit
1312///
1313/// You might want to modify these behaviours. For example when doing a "jump"
1314/// like go to definition, we always want to add to nav history; but when scrolling
1315/// in vim mode we never do.
1316///
1317/// Similarly, you might want to disable scrolling if you don't want the viewport to
1318/// move.
1319#[derive(Clone)]
1320pub struct SelectionEffects {
1321 nav_history: Option<bool>,
1322 completions: bool,
1323 scroll: Option<Autoscroll>,
1324}
1325
1326impl Default for SelectionEffects {
1327 fn default() -> Self {
1328 Self {
1329 nav_history: None,
1330 completions: true,
1331 scroll: Some(Autoscroll::fit()),
1332 }
1333 }
1334}
1335impl SelectionEffects {
1336 pub fn scroll(scroll: Autoscroll) -> Self {
1337 Self {
1338 scroll: Some(scroll),
1339 ..Default::default()
1340 }
1341 }
1342
1343 pub fn no_scroll() -> Self {
1344 Self {
1345 scroll: None,
1346 ..Default::default()
1347 }
1348 }
1349
1350 pub fn completions(self, completions: bool) -> Self {
1351 Self {
1352 completions,
1353 ..self
1354 }
1355 }
1356
1357 pub fn nav_history(self, nav_history: bool) -> Self {
1358 Self {
1359 nav_history: Some(nav_history),
1360 ..self
1361 }
1362 }
1363}
1364
1365struct DeferredSelectionEffectsState {
1366 changed: bool,
1367 effects: SelectionEffects,
1368 old_cursor_position: Anchor,
1369 history_entry: SelectionHistoryEntry,
1370}
1371
1372#[derive(Default)]
1373struct SelectionHistory {
1374 #[allow(clippy::type_complexity)]
1375 selections_by_transaction:
1376 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1377 mode: SelectionHistoryMode,
1378 undo_stack: VecDeque<SelectionHistoryEntry>,
1379 redo_stack: VecDeque<SelectionHistoryEntry>,
1380}
1381
1382impl SelectionHistory {
1383 #[track_caller]
1384 fn insert_transaction(
1385 &mut self,
1386 transaction_id: TransactionId,
1387 selections: Arc<[Selection<Anchor>]>,
1388 ) {
1389 if selections.is_empty() {
1390 log::error!(
1391 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1392 std::panic::Location::caller()
1393 );
1394 return;
1395 }
1396 self.selections_by_transaction
1397 .insert(transaction_id, (selections, None));
1398 }
1399
1400 #[allow(clippy::type_complexity)]
1401 fn transaction(
1402 &self,
1403 transaction_id: TransactionId,
1404 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1405 self.selections_by_transaction.get(&transaction_id)
1406 }
1407
1408 #[allow(clippy::type_complexity)]
1409 fn transaction_mut(
1410 &mut self,
1411 transaction_id: TransactionId,
1412 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1413 self.selections_by_transaction.get_mut(&transaction_id)
1414 }
1415
1416 fn push(&mut self, entry: SelectionHistoryEntry) {
1417 if !entry.selections.is_empty() {
1418 match self.mode {
1419 SelectionHistoryMode::Normal => {
1420 self.push_undo(entry);
1421 self.redo_stack.clear();
1422 }
1423 SelectionHistoryMode::Undoing => self.push_redo(entry),
1424 SelectionHistoryMode::Redoing => self.push_undo(entry),
1425 SelectionHistoryMode::Skipping => {}
1426 }
1427 }
1428 }
1429
1430 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1431 if self
1432 .undo_stack
1433 .back()
1434 .map_or(true, |e| e.selections != entry.selections)
1435 {
1436 self.undo_stack.push_back(entry);
1437 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1438 self.undo_stack.pop_front();
1439 }
1440 }
1441 }
1442
1443 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1444 if self
1445 .redo_stack
1446 .back()
1447 .map_or(true, |e| e.selections != entry.selections)
1448 {
1449 self.redo_stack.push_back(entry);
1450 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1451 self.redo_stack.pop_front();
1452 }
1453 }
1454 }
1455}
1456
1457#[derive(Clone, Copy)]
1458pub struct RowHighlightOptions {
1459 pub autoscroll: bool,
1460 pub include_gutter: bool,
1461}
1462
1463impl Default for RowHighlightOptions {
1464 fn default() -> Self {
1465 Self {
1466 autoscroll: Default::default(),
1467 include_gutter: true,
1468 }
1469 }
1470}
1471
1472struct RowHighlight {
1473 index: usize,
1474 range: Range<Anchor>,
1475 color: Hsla,
1476 options: RowHighlightOptions,
1477 type_id: TypeId,
1478}
1479
1480#[derive(Clone, Debug)]
1481struct AddSelectionsState {
1482 groups: Vec<AddSelectionsGroup>,
1483}
1484
1485#[derive(Clone, Debug)]
1486struct AddSelectionsGroup {
1487 above: bool,
1488 stack: Vec<usize>,
1489}
1490
1491#[derive(Clone)]
1492struct SelectNextState {
1493 query: AhoCorasick,
1494 wordwise: bool,
1495 done: bool,
1496}
1497
1498impl std::fmt::Debug for SelectNextState {
1499 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1500 f.debug_struct(std::any::type_name::<Self>())
1501 .field("wordwise", &self.wordwise)
1502 .field("done", &self.done)
1503 .finish()
1504 }
1505}
1506
1507#[derive(Debug)]
1508struct AutocloseRegion {
1509 selection_id: usize,
1510 range: Range<Anchor>,
1511 pair: BracketPair,
1512}
1513
1514#[derive(Debug)]
1515struct SnippetState {
1516 ranges: Vec<Vec<Range<Anchor>>>,
1517 active_index: usize,
1518 choices: Vec<Option<Vec<String>>>,
1519}
1520
1521#[doc(hidden)]
1522pub struct RenameState {
1523 pub range: Range<Anchor>,
1524 pub old_name: Arc<str>,
1525 pub editor: Entity<Editor>,
1526 block_id: CustomBlockId,
1527}
1528
1529struct InvalidationStack<T>(Vec<T>);
1530
1531struct RegisteredEditPredictionProvider {
1532 provider: Arc<dyn EditPredictionProviderHandle>,
1533 _subscription: Subscription,
1534}
1535
1536#[derive(Debug, PartialEq, Eq)]
1537pub struct ActiveDiagnosticGroup {
1538 pub active_range: Range<Anchor>,
1539 pub active_message: String,
1540 pub group_id: usize,
1541 pub blocks: HashSet<CustomBlockId>,
1542}
1543
1544#[derive(Debug, PartialEq, Eq)]
1545
1546pub(crate) enum ActiveDiagnostic {
1547 None,
1548 All,
1549 Group(ActiveDiagnosticGroup),
1550}
1551
1552#[derive(Serialize, Deserialize, Clone, Debug)]
1553pub struct ClipboardSelection {
1554 /// The number of bytes in this selection.
1555 pub len: usize,
1556 /// Whether this was a full-line selection.
1557 pub is_entire_line: bool,
1558 /// The indentation of the first line when this content was originally copied.
1559 pub first_line_indent: u32,
1560}
1561
1562// selections, scroll behavior, was newest selection reversed
1563type SelectSyntaxNodeHistoryState = (
1564 Box<[Selection<usize>]>,
1565 SelectSyntaxNodeScrollBehavior,
1566 bool,
1567);
1568
1569#[derive(Default)]
1570struct SelectSyntaxNodeHistory {
1571 stack: Vec<SelectSyntaxNodeHistoryState>,
1572 // disable temporarily to allow changing selections without losing the stack
1573 pub disable_clearing: bool,
1574}
1575
1576impl SelectSyntaxNodeHistory {
1577 pub fn try_clear(&mut self) {
1578 if !self.disable_clearing {
1579 self.stack.clear();
1580 }
1581 }
1582
1583 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1584 self.stack.push(selection);
1585 }
1586
1587 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1588 self.stack.pop()
1589 }
1590}
1591
1592enum SelectSyntaxNodeScrollBehavior {
1593 CursorTop,
1594 FitSelection,
1595 CursorBottom,
1596}
1597
1598#[derive(Debug)]
1599pub(crate) struct NavigationData {
1600 cursor_anchor: Anchor,
1601 cursor_position: Point,
1602 scroll_anchor: ScrollAnchor,
1603 scroll_top_row: u32,
1604}
1605
1606#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1607pub enum GotoDefinitionKind {
1608 Symbol,
1609 Declaration,
1610 Type,
1611 Implementation,
1612}
1613
1614#[derive(Debug, Clone)]
1615enum InlayHintRefreshReason {
1616 ModifiersChanged(bool),
1617 Toggle(bool),
1618 SettingsChange(InlayHintSettings),
1619 NewLinesShown,
1620 BufferEdited(HashSet<Arc<Language>>),
1621 RefreshRequested,
1622 ExcerptsRemoved(Vec<ExcerptId>),
1623}
1624
1625impl InlayHintRefreshReason {
1626 fn description(&self) -> &'static str {
1627 match self {
1628 Self::ModifiersChanged(_) => "modifiers changed",
1629 Self::Toggle(_) => "toggle",
1630 Self::SettingsChange(_) => "settings change",
1631 Self::NewLinesShown => "new lines shown",
1632 Self::BufferEdited(_) => "buffer edited",
1633 Self::RefreshRequested => "refresh requested",
1634 Self::ExcerptsRemoved(_) => "excerpts removed",
1635 }
1636 }
1637}
1638
1639pub enum FormatTarget {
1640 Buffers(HashSet<Entity<Buffer>>),
1641 Ranges(Vec<Range<MultiBufferPoint>>),
1642}
1643
1644pub(crate) struct FocusedBlock {
1645 id: BlockId,
1646 focus_handle: WeakFocusHandle,
1647}
1648
1649#[derive(Clone)]
1650enum JumpData {
1651 MultiBufferRow {
1652 row: MultiBufferRow,
1653 line_offset_from_top: u32,
1654 },
1655 MultiBufferPoint {
1656 excerpt_id: ExcerptId,
1657 position: Point,
1658 anchor: text::Anchor,
1659 line_offset_from_top: u32,
1660 },
1661}
1662
1663pub enum MultibufferSelectionMode {
1664 First,
1665 All,
1666}
1667
1668#[derive(Clone, Copy, Debug, Default)]
1669pub struct RewrapOptions {
1670 pub override_language_settings: bool,
1671 pub preserve_existing_whitespace: bool,
1672}
1673
1674impl Editor {
1675 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1676 let buffer = cx.new(|cx| Buffer::local("", cx));
1677 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1678 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1679 }
1680
1681 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1682 let buffer = cx.new(|cx| Buffer::local("", cx));
1683 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1684 Self::new(EditorMode::full(), buffer, None, window, cx)
1685 }
1686
1687 pub fn auto_height(
1688 min_lines: usize,
1689 max_lines: usize,
1690 window: &mut Window,
1691 cx: &mut Context<Self>,
1692 ) -> Self {
1693 let buffer = cx.new(|cx| Buffer::local("", cx));
1694 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1695 Self::new(
1696 EditorMode::AutoHeight {
1697 min_lines,
1698 max_lines: Some(max_lines),
1699 },
1700 buffer,
1701 None,
1702 window,
1703 cx,
1704 )
1705 }
1706
1707 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1708 /// The editor grows as tall as needed to fit its content.
1709 pub fn auto_height_unbounded(
1710 min_lines: usize,
1711 window: &mut Window,
1712 cx: &mut Context<Self>,
1713 ) -> Self {
1714 let buffer = cx.new(|cx| Buffer::local("", cx));
1715 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1716 Self::new(
1717 EditorMode::AutoHeight {
1718 min_lines,
1719 max_lines: None,
1720 },
1721 buffer,
1722 None,
1723 window,
1724 cx,
1725 )
1726 }
1727
1728 pub fn for_buffer(
1729 buffer: Entity<Buffer>,
1730 project: Option<Entity<Project>>,
1731 window: &mut Window,
1732 cx: &mut Context<Self>,
1733 ) -> Self {
1734 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1735 Self::new(EditorMode::full(), buffer, project, window, cx)
1736 }
1737
1738 pub fn for_multibuffer(
1739 buffer: Entity<MultiBuffer>,
1740 project: Option<Entity<Project>>,
1741 window: &mut Window,
1742 cx: &mut Context<Self>,
1743 ) -> Self {
1744 Self::new(EditorMode::full(), buffer, project, window, cx)
1745 }
1746
1747 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1748 let mut clone = Self::new(
1749 self.mode.clone(),
1750 self.buffer.clone(),
1751 self.project.clone(),
1752 window,
1753 cx,
1754 );
1755 self.display_map.update(cx, |display_map, cx| {
1756 let snapshot = display_map.snapshot(cx);
1757 clone.display_map.update(cx, |display_map, cx| {
1758 display_map.set_state(&snapshot, cx);
1759 });
1760 });
1761 clone.folds_did_change(cx);
1762 clone.selections.clone_state(&self.selections);
1763 clone.scroll_manager.clone_state(&self.scroll_manager);
1764 clone.searchable = self.searchable;
1765 clone.read_only = self.read_only;
1766 clone
1767 }
1768
1769 pub fn new(
1770 mode: EditorMode,
1771 buffer: Entity<MultiBuffer>,
1772 project: Option<Entity<Project>>,
1773 window: &mut Window,
1774 cx: &mut Context<Self>,
1775 ) -> Self {
1776 Editor::new_internal(mode, buffer, project, None, window, cx)
1777 }
1778
1779 fn new_internal(
1780 mode: EditorMode,
1781 buffer: Entity<MultiBuffer>,
1782 project: Option<Entity<Project>>,
1783 display_map: Option<Entity<DisplayMap>>,
1784 window: &mut Window,
1785 cx: &mut Context<Self>,
1786 ) -> Self {
1787 debug_assert!(
1788 display_map.is_none() || mode.is_minimap(),
1789 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1790 );
1791
1792 let full_mode = mode.is_full();
1793 let is_minimap = mode.is_minimap();
1794 let diagnostics_max_severity = if full_mode {
1795 EditorSettings::get_global(cx)
1796 .diagnostics_max_severity
1797 .unwrap_or(DiagnosticSeverity::Hint)
1798 } else {
1799 DiagnosticSeverity::Off
1800 };
1801 let style = window.text_style();
1802 let font_size = style.font_size.to_pixels(window.rem_size());
1803 let editor = cx.entity().downgrade();
1804 let fold_placeholder = FoldPlaceholder {
1805 constrain_width: true,
1806 render: Arc::new(move |fold_id, fold_range, cx| {
1807 let editor = editor.clone();
1808 div()
1809 .id(fold_id)
1810 .bg(cx.theme().colors().ghost_element_background)
1811 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1812 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1813 .rounded_xs()
1814 .size_full()
1815 .cursor_pointer()
1816 .child("⋯")
1817 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1818 .on_click(move |_, _window, cx| {
1819 editor
1820 .update(cx, |editor, cx| {
1821 editor.unfold_ranges(
1822 &[fold_range.start..fold_range.end],
1823 true,
1824 false,
1825 cx,
1826 );
1827 cx.stop_propagation();
1828 })
1829 .ok();
1830 })
1831 .into_any()
1832 }),
1833 merge_adjacent: true,
1834 ..FoldPlaceholder::default()
1835 };
1836 let display_map = display_map.unwrap_or_else(|| {
1837 cx.new(|cx| {
1838 DisplayMap::new(
1839 buffer.clone(),
1840 style.font(),
1841 font_size,
1842 None,
1843 FILE_HEADER_HEIGHT,
1844 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1845 fold_placeholder,
1846 diagnostics_max_severity,
1847 cx,
1848 )
1849 })
1850 });
1851
1852 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1853
1854 let blink_manager = cx.new(|cx| {
1855 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1856 if is_minimap {
1857 blink_manager.disable(cx);
1858 }
1859 blink_manager
1860 });
1861
1862 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1863 .then(|| language_settings::SoftWrap::None);
1864
1865 let mut project_subscriptions = Vec::new();
1866 if full_mode {
1867 if let Some(project) = project.as_ref() {
1868 project_subscriptions.push(cx.subscribe_in(
1869 project,
1870 window,
1871 |editor, _, event, window, cx| match event {
1872 project::Event::RefreshCodeLens => {
1873 // we always query lens with actions, without storing them, always refreshing them
1874 }
1875 project::Event::RefreshInlayHints => {
1876 editor
1877 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1878 }
1879 project::Event::LanguageServerAdded(..)
1880 | project::Event::LanguageServerRemoved(..) => {
1881 if editor.tasks_update_task.is_none() {
1882 editor.tasks_update_task =
1883 Some(editor.refresh_runnables(window, cx));
1884 }
1885 }
1886 project::Event::SnippetEdit(id, snippet_edits) => {
1887 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1888 let focus_handle = editor.focus_handle(cx);
1889 if focus_handle.is_focused(window) {
1890 let snapshot = buffer.read(cx).snapshot();
1891 for (range, snippet) in snippet_edits {
1892 let editor_range =
1893 language::range_from_lsp(*range).to_offset(&snapshot);
1894 editor
1895 .insert_snippet(
1896 &[editor_range],
1897 snippet.clone(),
1898 window,
1899 cx,
1900 )
1901 .ok();
1902 }
1903 }
1904 }
1905 }
1906 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1907 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1908 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1909 }
1910 }
1911 _ => {}
1912 },
1913 ));
1914 if let Some(task_inventory) = project
1915 .read(cx)
1916 .task_store()
1917 .read(cx)
1918 .task_inventory()
1919 .cloned()
1920 {
1921 project_subscriptions.push(cx.observe_in(
1922 &task_inventory,
1923 window,
1924 |editor, _, window, cx| {
1925 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1926 },
1927 ));
1928 };
1929
1930 project_subscriptions.push(cx.subscribe_in(
1931 &project.read(cx).breakpoint_store(),
1932 window,
1933 |editor, _, event, window, cx| match event {
1934 BreakpointStoreEvent::ClearDebugLines => {
1935 editor.clear_row_highlights::<ActiveDebugLine>();
1936 editor.refresh_inline_values(cx);
1937 }
1938 BreakpointStoreEvent::SetDebugLine => {
1939 if editor.go_to_active_debug_line(window, cx) {
1940 cx.stop_propagation();
1941 }
1942
1943 editor.refresh_inline_values(cx);
1944 }
1945 _ => {}
1946 },
1947 ));
1948 let git_store = project.read(cx).git_store().clone();
1949 let project = project.clone();
1950 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1951 match event {
1952 GitStoreEvent::RepositoryUpdated(
1953 _,
1954 RepositoryEvent::Updated {
1955 new_instance: true, ..
1956 },
1957 _,
1958 ) => {
1959 this.load_diff_task = Some(
1960 update_uncommitted_diff_for_buffer(
1961 cx.entity(),
1962 &project,
1963 this.buffer.read(cx).all_buffers(),
1964 this.buffer.clone(),
1965 cx,
1966 )
1967 .shared(),
1968 );
1969 }
1970 _ => {}
1971 }
1972 }));
1973 }
1974 }
1975
1976 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1977
1978 let inlay_hint_settings =
1979 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1980 let focus_handle = cx.focus_handle();
1981 if !is_minimap {
1982 cx.on_focus(&focus_handle, window, Self::handle_focus)
1983 .detach();
1984 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1985 .detach();
1986 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1987 .detach();
1988 cx.on_blur(&focus_handle, window, Self::handle_blur)
1989 .detach();
1990 cx.observe_pending_input(window, Self::observe_pending_input)
1991 .detach();
1992 }
1993
1994 let show_indent_guides = if matches!(
1995 mode,
1996 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1997 ) {
1998 Some(false)
1999 } else {
2000 None
2001 };
2002
2003 let breakpoint_store = match (&mode, project.as_ref()) {
2004 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2005 _ => None,
2006 };
2007
2008 let mut code_action_providers = Vec::new();
2009 let mut load_uncommitted_diff = None;
2010 if let Some(project) = project.clone() {
2011 load_uncommitted_diff = Some(
2012 update_uncommitted_diff_for_buffer(
2013 cx.entity(),
2014 &project,
2015 buffer.read(cx).all_buffers(),
2016 buffer.clone(),
2017 cx,
2018 )
2019 .shared(),
2020 );
2021 code_action_providers.push(Rc::new(project) as Rc<_>);
2022 }
2023
2024 let mut editor = Self {
2025 focus_handle,
2026 show_cursor_when_unfocused: false,
2027 last_focused_descendant: None,
2028 buffer: buffer.clone(),
2029 display_map: display_map.clone(),
2030 selections,
2031 scroll_manager: ScrollManager::new(cx),
2032 columnar_selection_state: None,
2033 add_selections_state: None,
2034 select_next_state: None,
2035 select_prev_state: None,
2036 selection_history: SelectionHistory::default(),
2037 defer_selection_effects: false,
2038 deferred_selection_effects_state: None,
2039 autoclose_regions: Vec::new(),
2040 snippet_stack: InvalidationStack::default(),
2041 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2042 ime_transaction: None,
2043 active_diagnostics: ActiveDiagnostic::None,
2044 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2045 inline_diagnostics_update: Task::ready(()),
2046 inline_diagnostics: Vec::new(),
2047 soft_wrap_mode_override,
2048 diagnostics_max_severity,
2049 hard_wrap: None,
2050 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2051 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2052 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2053 project,
2054 blink_manager: blink_manager.clone(),
2055 show_local_selections: true,
2056 show_scrollbars: ScrollbarAxes {
2057 horizontal: full_mode,
2058 vertical: full_mode,
2059 },
2060 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2061 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2062 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2063 show_gutter: full_mode,
2064 show_line_numbers: (!full_mode).then_some(false),
2065 use_relative_line_numbers: None,
2066 disable_expand_excerpt_buttons: !full_mode,
2067 show_git_diff_gutter: None,
2068 show_code_actions: None,
2069 show_runnables: None,
2070 show_breakpoints: None,
2071 show_wrap_guides: None,
2072 show_indent_guides,
2073 placeholder_text: None,
2074 highlight_order: 0,
2075 highlighted_rows: HashMap::default(),
2076 background_highlights: TreeMap::default(),
2077 gutter_highlights: TreeMap::default(),
2078 scrollbar_marker_state: ScrollbarMarkerState::default(),
2079 active_indent_guides_state: ActiveIndentGuidesState::default(),
2080 nav_history: None,
2081 context_menu: RefCell::new(None),
2082 context_menu_options: None,
2083 mouse_context_menu: None,
2084 completion_tasks: Vec::new(),
2085 inline_blame_popover: None,
2086 inline_blame_popover_show_task: None,
2087 signature_help_state: SignatureHelpState::default(),
2088 auto_signature_help: None,
2089 find_all_references_task_sources: Vec::new(),
2090 next_completion_id: 0,
2091 next_inlay_id: 0,
2092 code_action_providers,
2093 available_code_actions: None,
2094 code_actions_task: None,
2095 quick_selection_highlight_task: None,
2096 debounced_selection_highlight_task: None,
2097 document_highlights_task: None,
2098 linked_editing_range_task: None,
2099 pending_rename: None,
2100 searchable: !is_minimap,
2101 cursor_shape: EditorSettings::get_global(cx)
2102 .cursor_shape
2103 .unwrap_or_default(),
2104 current_line_highlight: None,
2105 autoindent_mode: Some(AutoindentMode::EachLine),
2106 collapse_matches: false,
2107 workspace: None,
2108 input_enabled: !is_minimap,
2109 use_modal_editing: full_mode,
2110 read_only: is_minimap,
2111 use_autoclose: true,
2112 use_auto_surround: true,
2113 auto_replace_emoji_shortcode: false,
2114 jsx_tag_auto_close_enabled_in_any_buffer: false,
2115 leader_id: None,
2116 remote_id: None,
2117 hover_state: HoverState::default(),
2118 pending_mouse_down: None,
2119 hovered_link_state: None,
2120 edit_prediction_provider: None,
2121 active_edit_prediction: None,
2122 stale_edit_prediction_in_menu: None,
2123 edit_prediction_preview: EditPredictionPreview::Inactive {
2124 released_too_fast: false,
2125 },
2126 inline_diagnostics_enabled: full_mode,
2127 diagnostics_enabled: full_mode,
2128 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2129 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2130 gutter_hovered: false,
2131 pixel_position_of_newest_cursor: None,
2132 last_bounds: None,
2133 last_position_map: None,
2134 expect_bounds_change: None,
2135 gutter_dimensions: GutterDimensions::default(),
2136 style: None,
2137 show_cursor_names: false,
2138 hovered_cursors: HashMap::default(),
2139 next_editor_action_id: EditorActionId::default(),
2140 editor_actions: Rc::default(),
2141 edit_predictions_hidden_for_vim_mode: false,
2142 show_edit_predictions_override: None,
2143 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2144 edit_prediction_settings: EditPredictionSettings::Disabled,
2145 edit_prediction_indent_conflict: false,
2146 edit_prediction_requires_modifier_in_indent_conflict: true,
2147 custom_context_menu: None,
2148 show_git_blame_gutter: false,
2149 show_git_blame_inline: false,
2150 show_selection_menu: None,
2151 show_git_blame_inline_delay_task: None,
2152 git_blame_inline_enabled: full_mode
2153 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2154 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2155 serialize_dirty_buffers: !is_minimap
2156 && ProjectSettings::get_global(cx)
2157 .session
2158 .restore_unsaved_buffers,
2159 blame: None,
2160 blame_subscription: None,
2161 tasks: BTreeMap::default(),
2162
2163 breakpoint_store,
2164 gutter_breakpoint_indicator: (None, None),
2165 hovered_diff_hunk_row: None,
2166 _subscriptions: (!is_minimap)
2167 .then(|| {
2168 vec![
2169 cx.observe(&buffer, Self::on_buffer_changed),
2170 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2171 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2172 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2173 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2174 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2175 cx.observe_window_activation(window, |editor, window, cx| {
2176 let active = window.is_window_active();
2177 editor.blink_manager.update(cx, |blink_manager, cx| {
2178 if active {
2179 blink_manager.enable(cx);
2180 } else {
2181 blink_manager.disable(cx);
2182 }
2183 });
2184 if active {
2185 editor.show_mouse_cursor(cx);
2186 }
2187 }),
2188 ]
2189 })
2190 .unwrap_or_default(),
2191 tasks_update_task: None,
2192 pull_diagnostics_task: Task::ready(()),
2193 colors: None,
2194 next_color_inlay_id: 0,
2195 linked_edit_ranges: Default::default(),
2196 in_project_search: false,
2197 previous_search_ranges: None,
2198 breadcrumb_header: None,
2199 focused_block: None,
2200 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2201 addons: HashMap::default(),
2202 registered_buffers: HashMap::default(),
2203 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2204 selection_mark_mode: false,
2205 toggle_fold_multiple_buffers: Task::ready(()),
2206 serialize_selections: Task::ready(()),
2207 serialize_folds: Task::ready(()),
2208 text_style_refinement: None,
2209 load_diff_task: load_uncommitted_diff,
2210 temporary_diff_override: false,
2211 mouse_cursor_hidden: false,
2212 minimap: None,
2213 hide_mouse_mode: EditorSettings::get_global(cx)
2214 .hide_mouse
2215 .unwrap_or_default(),
2216 change_list: ChangeList::new(),
2217 mode,
2218 selection_drag_state: SelectionDragState::None,
2219 folding_newlines: Task::ready(()),
2220 };
2221
2222 if is_minimap {
2223 return editor;
2224 }
2225
2226 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2227 editor
2228 ._subscriptions
2229 .push(cx.observe(breakpoints, |_, _, cx| {
2230 cx.notify();
2231 }));
2232 }
2233 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2234 editor._subscriptions.extend(project_subscriptions);
2235
2236 editor._subscriptions.push(cx.subscribe_in(
2237 &cx.entity(),
2238 window,
2239 |editor, _, e: &EditorEvent, window, cx| match e {
2240 EditorEvent::ScrollPositionChanged { local, .. } => {
2241 if *local {
2242 let new_anchor = editor.scroll_manager.anchor();
2243 let snapshot = editor.snapshot(window, cx);
2244 editor.update_restoration_data(cx, move |data| {
2245 data.scroll_position = (
2246 new_anchor.top_row(&snapshot.buffer_snapshot),
2247 new_anchor.offset,
2248 );
2249 });
2250 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2251 editor.inline_blame_popover.take();
2252 }
2253 }
2254 EditorEvent::Edited { .. } => {
2255 if !vim_enabled(cx) {
2256 let (map, selections) = editor.selections.all_adjusted_display(cx);
2257 let pop_state = editor
2258 .change_list
2259 .last()
2260 .map(|previous| {
2261 previous.len() == selections.len()
2262 && previous.iter().enumerate().all(|(ix, p)| {
2263 p.to_display_point(&map).row()
2264 == selections[ix].head().row()
2265 })
2266 })
2267 .unwrap_or(false);
2268 let new_positions = selections
2269 .into_iter()
2270 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2271 .collect();
2272 editor
2273 .change_list
2274 .push_to_change_list(pop_state, new_positions);
2275 }
2276 }
2277 _ => (),
2278 },
2279 ));
2280
2281 if let Some(dap_store) = editor
2282 .project
2283 .as_ref()
2284 .map(|project| project.read(cx).dap_store())
2285 {
2286 let weak_editor = cx.weak_entity();
2287
2288 editor
2289 ._subscriptions
2290 .push(
2291 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2292 let session_entity = cx.entity();
2293 weak_editor
2294 .update(cx, |editor, cx| {
2295 editor._subscriptions.push(
2296 cx.subscribe(&session_entity, Self::on_debug_session_event),
2297 );
2298 })
2299 .ok();
2300 }),
2301 );
2302
2303 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2304 editor
2305 ._subscriptions
2306 .push(cx.subscribe(&session, Self::on_debug_session_event));
2307 }
2308 }
2309
2310 // skip adding the initial selection to selection history
2311 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2312 editor.end_selection(window, cx);
2313 editor.selection_history.mode = SelectionHistoryMode::Normal;
2314
2315 editor.scroll_manager.show_scrollbars(window, cx);
2316 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2317
2318 if full_mode {
2319 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2320 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2321
2322 if editor.git_blame_inline_enabled {
2323 editor.start_git_blame_inline(false, window, cx);
2324 }
2325
2326 editor.go_to_active_debug_line(window, cx);
2327
2328 if let Some(buffer) = buffer.read(cx).as_singleton() {
2329 if let Some(project) = editor.project.as_ref() {
2330 let handle = project.update(cx, |project, cx| {
2331 project.register_buffer_with_language_servers(&buffer, cx)
2332 });
2333 editor
2334 .registered_buffers
2335 .insert(buffer.read(cx).remote_id(), handle);
2336 }
2337 }
2338
2339 editor.minimap =
2340 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2341 editor.colors = Some(LspColorData::new(cx));
2342 editor.update_lsp_data(false, None, window, cx);
2343 }
2344
2345 if editor.mode.is_full() {
2346 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2347 }
2348
2349 editor
2350 }
2351
2352 pub fn deploy_mouse_context_menu(
2353 &mut self,
2354 position: gpui::Point<Pixels>,
2355 context_menu: Entity<ContextMenu>,
2356 window: &mut Window,
2357 cx: &mut Context<Self>,
2358 ) {
2359 self.mouse_context_menu = Some(MouseContextMenu::new(
2360 self,
2361 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2362 context_menu,
2363 window,
2364 cx,
2365 ));
2366 }
2367
2368 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2369 self.mouse_context_menu
2370 .as_ref()
2371 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2372 }
2373
2374 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2375 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2376 }
2377
2378 fn key_context_internal(
2379 &self,
2380 has_active_edit_prediction: bool,
2381 window: &Window,
2382 cx: &App,
2383 ) -> KeyContext {
2384 let mut key_context = KeyContext::new_with_defaults();
2385 key_context.add("Editor");
2386 let mode = match self.mode {
2387 EditorMode::SingleLine { .. } => "single_line",
2388 EditorMode::AutoHeight { .. } => "auto_height",
2389 EditorMode::Minimap { .. } => "minimap",
2390 EditorMode::Full { .. } => "full",
2391 };
2392
2393 if EditorSettings::jupyter_enabled(cx) {
2394 key_context.add("jupyter");
2395 }
2396
2397 key_context.set("mode", mode);
2398 if self.pending_rename.is_some() {
2399 key_context.add("renaming");
2400 }
2401
2402 match self.context_menu.borrow().as_ref() {
2403 Some(CodeContextMenu::Completions(menu)) => {
2404 if menu.visible() {
2405 key_context.add("menu");
2406 key_context.add("showing_completions");
2407 }
2408 }
2409 Some(CodeContextMenu::CodeActions(menu)) => {
2410 if menu.visible() {
2411 key_context.add("menu");
2412 key_context.add("showing_code_actions")
2413 }
2414 }
2415 None => {}
2416 }
2417
2418 if self.signature_help_state.has_multiple_signatures() {
2419 key_context.add("showing_signature_help");
2420 }
2421
2422 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2423 if !self.focus_handle(cx).contains_focused(window, cx)
2424 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2425 {
2426 for addon in self.addons.values() {
2427 addon.extend_key_context(&mut key_context, cx)
2428 }
2429 }
2430
2431 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2432 if let Some(extension) = singleton_buffer
2433 .read(cx)
2434 .file()
2435 .and_then(|file| file.path().extension()?.to_str())
2436 {
2437 key_context.set("extension", extension.to_string());
2438 }
2439 } else {
2440 key_context.add("multibuffer");
2441 }
2442
2443 if has_active_edit_prediction {
2444 if self.edit_prediction_in_conflict() {
2445 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2446 } else {
2447 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2448 key_context.add("copilot_suggestion");
2449 }
2450 }
2451
2452 if self.selection_mark_mode {
2453 key_context.add("selection_mode");
2454 }
2455
2456 key_context
2457 }
2458
2459 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2460 if self.mouse_cursor_hidden {
2461 self.mouse_cursor_hidden = false;
2462 cx.notify();
2463 }
2464 }
2465
2466 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2467 let hide_mouse_cursor = match origin {
2468 HideMouseCursorOrigin::TypingAction => {
2469 matches!(
2470 self.hide_mouse_mode,
2471 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2472 )
2473 }
2474 HideMouseCursorOrigin::MovementAction => {
2475 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2476 }
2477 };
2478 if self.mouse_cursor_hidden != hide_mouse_cursor {
2479 self.mouse_cursor_hidden = hide_mouse_cursor;
2480 cx.notify();
2481 }
2482 }
2483
2484 pub fn edit_prediction_in_conflict(&self) -> bool {
2485 if !self.show_edit_predictions_in_menu() {
2486 return false;
2487 }
2488
2489 let showing_completions = self
2490 .context_menu
2491 .borrow()
2492 .as_ref()
2493 .map_or(false, |context| {
2494 matches!(context, CodeContextMenu::Completions(_))
2495 });
2496
2497 showing_completions
2498 || self.edit_prediction_requires_modifier()
2499 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2500 // bindings to insert tab characters.
2501 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2502 }
2503
2504 pub fn accept_edit_prediction_keybind(
2505 &self,
2506 accept_partial: bool,
2507 window: &Window,
2508 cx: &App,
2509 ) -> AcceptEditPredictionBinding {
2510 let key_context = self.key_context_internal(true, window, cx);
2511 let in_conflict = self.edit_prediction_in_conflict();
2512
2513 let bindings = if accept_partial {
2514 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2515 } else {
2516 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2517 };
2518
2519 // TODO: if the binding contains multiple keystrokes, display all of them, not
2520 // just the first one.
2521 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2522 !in_conflict
2523 || binding
2524 .keystrokes()
2525 .first()
2526 .map_or(false, |keystroke| keystroke.modifiers.modified())
2527 }))
2528 }
2529
2530 pub fn new_file(
2531 workspace: &mut Workspace,
2532 _: &workspace::NewFile,
2533 window: &mut Window,
2534 cx: &mut Context<Workspace>,
2535 ) {
2536 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2537 "Failed to create buffer",
2538 window,
2539 cx,
2540 |e, _, _| match e.error_code() {
2541 ErrorCode::RemoteUpgradeRequired => Some(format!(
2542 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2543 e.error_tag("required").unwrap_or("the latest version")
2544 )),
2545 _ => None,
2546 },
2547 );
2548 }
2549
2550 pub fn new_in_workspace(
2551 workspace: &mut Workspace,
2552 window: &mut Window,
2553 cx: &mut Context<Workspace>,
2554 ) -> Task<Result<Entity<Editor>>> {
2555 let project = workspace.project().clone();
2556 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2557
2558 cx.spawn_in(window, async move |workspace, cx| {
2559 let buffer = create.await?;
2560 workspace.update_in(cx, |workspace, window, cx| {
2561 let editor =
2562 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2563 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2564 editor
2565 })
2566 })
2567 }
2568
2569 fn new_file_vertical(
2570 workspace: &mut Workspace,
2571 _: &workspace::NewFileSplitVertical,
2572 window: &mut Window,
2573 cx: &mut Context<Workspace>,
2574 ) {
2575 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2576 }
2577
2578 fn new_file_horizontal(
2579 workspace: &mut Workspace,
2580 _: &workspace::NewFileSplitHorizontal,
2581 window: &mut Window,
2582 cx: &mut Context<Workspace>,
2583 ) {
2584 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2585 }
2586
2587 fn new_file_in_direction(
2588 workspace: &mut Workspace,
2589 direction: SplitDirection,
2590 window: &mut Window,
2591 cx: &mut Context<Workspace>,
2592 ) {
2593 let project = workspace.project().clone();
2594 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2595
2596 cx.spawn_in(window, async move |workspace, cx| {
2597 let buffer = create.await?;
2598 workspace.update_in(cx, move |workspace, window, cx| {
2599 workspace.split_item(
2600 direction,
2601 Box::new(
2602 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2603 ),
2604 window,
2605 cx,
2606 )
2607 })?;
2608 anyhow::Ok(())
2609 })
2610 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2611 match e.error_code() {
2612 ErrorCode::RemoteUpgradeRequired => Some(format!(
2613 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2614 e.error_tag("required").unwrap_or("the latest version")
2615 )),
2616 _ => None,
2617 }
2618 });
2619 }
2620
2621 pub fn leader_id(&self) -> Option<CollaboratorId> {
2622 self.leader_id
2623 }
2624
2625 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2626 &self.buffer
2627 }
2628
2629 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2630 self.workspace.as_ref()?.0.upgrade()
2631 }
2632
2633 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2634 self.buffer().read(cx).title(cx)
2635 }
2636
2637 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2638 let git_blame_gutter_max_author_length = self
2639 .render_git_blame_gutter(cx)
2640 .then(|| {
2641 if let Some(blame) = self.blame.as_ref() {
2642 let max_author_length =
2643 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2644 Some(max_author_length)
2645 } else {
2646 None
2647 }
2648 })
2649 .flatten();
2650
2651 EditorSnapshot {
2652 mode: self.mode.clone(),
2653 show_gutter: self.show_gutter,
2654 show_line_numbers: self.show_line_numbers,
2655 show_git_diff_gutter: self.show_git_diff_gutter,
2656 show_code_actions: self.show_code_actions,
2657 show_runnables: self.show_runnables,
2658 show_breakpoints: self.show_breakpoints,
2659 git_blame_gutter_max_author_length,
2660 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2661 scroll_anchor: self.scroll_manager.anchor(),
2662 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2663 placeholder_text: self.placeholder_text.clone(),
2664 is_focused: self.focus_handle.is_focused(window),
2665 current_line_highlight: self
2666 .current_line_highlight
2667 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2668 gutter_hovered: self.gutter_hovered,
2669 }
2670 }
2671
2672 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2673 self.buffer.read(cx).language_at(point, cx)
2674 }
2675
2676 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2677 self.buffer.read(cx).read(cx).file_at(point).cloned()
2678 }
2679
2680 pub fn active_excerpt(
2681 &self,
2682 cx: &App,
2683 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2684 self.buffer
2685 .read(cx)
2686 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2687 }
2688
2689 pub fn mode(&self) -> &EditorMode {
2690 &self.mode
2691 }
2692
2693 pub fn set_mode(&mut self, mode: EditorMode) {
2694 self.mode = mode;
2695 }
2696
2697 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2698 self.collaboration_hub.as_deref()
2699 }
2700
2701 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2702 self.collaboration_hub = Some(hub);
2703 }
2704
2705 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2706 self.in_project_search = in_project_search;
2707 }
2708
2709 pub fn set_custom_context_menu(
2710 &mut self,
2711 f: impl 'static
2712 + Fn(
2713 &mut Self,
2714 DisplayPoint,
2715 &mut Window,
2716 &mut Context<Self>,
2717 ) -> Option<Entity<ui::ContextMenu>>,
2718 ) {
2719 self.custom_context_menu = Some(Box::new(f))
2720 }
2721
2722 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2723 self.completion_provider = provider;
2724 }
2725
2726 #[cfg(any(test, feature = "test-support"))]
2727 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2728 self.completion_provider.clone()
2729 }
2730
2731 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2732 self.semantics_provider.clone()
2733 }
2734
2735 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2736 self.semantics_provider = provider;
2737 }
2738
2739 pub fn set_edit_prediction_provider<T>(
2740 &mut self,
2741 provider: Option<Entity<T>>,
2742 window: &mut Window,
2743 cx: &mut Context<Self>,
2744 ) where
2745 T: EditPredictionProvider,
2746 {
2747 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2748 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2749 if this.focus_handle.is_focused(window) {
2750 this.update_visible_edit_prediction(window, cx);
2751 }
2752 }),
2753 provider: Arc::new(provider),
2754 });
2755 self.update_edit_prediction_settings(cx);
2756 self.refresh_edit_prediction(false, false, window, cx);
2757 }
2758
2759 pub fn placeholder_text(&self) -> Option<&str> {
2760 self.placeholder_text.as_deref()
2761 }
2762
2763 pub fn set_placeholder_text(
2764 &mut self,
2765 placeholder_text: impl Into<Arc<str>>,
2766 cx: &mut Context<Self>,
2767 ) {
2768 let placeholder_text = Some(placeholder_text.into());
2769 if self.placeholder_text != placeholder_text {
2770 self.placeholder_text = placeholder_text;
2771 cx.notify();
2772 }
2773 }
2774
2775 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2776 self.cursor_shape = cursor_shape;
2777
2778 // Disrupt blink for immediate user feedback that the cursor shape has changed
2779 self.blink_manager.update(cx, BlinkManager::show_cursor);
2780
2781 cx.notify();
2782 }
2783
2784 pub fn set_current_line_highlight(
2785 &mut self,
2786 current_line_highlight: Option<CurrentLineHighlight>,
2787 ) {
2788 self.current_line_highlight = current_line_highlight;
2789 }
2790
2791 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2792 self.collapse_matches = collapse_matches;
2793 }
2794
2795 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2796 let buffers = self.buffer.read(cx).all_buffers();
2797 let Some(project) = self.project.as_ref() else {
2798 return;
2799 };
2800 project.update(cx, |project, cx| {
2801 for buffer in buffers {
2802 self.registered_buffers
2803 .entry(buffer.read(cx).remote_id())
2804 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2805 }
2806 })
2807 }
2808
2809 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2810 if self.collapse_matches {
2811 return range.start..range.start;
2812 }
2813 range.clone()
2814 }
2815
2816 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2817 if self.display_map.read(cx).clip_at_line_ends != clip {
2818 self.display_map
2819 .update(cx, |map, _| map.clip_at_line_ends = clip);
2820 }
2821 }
2822
2823 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2824 self.input_enabled = input_enabled;
2825 }
2826
2827 pub fn set_edit_predictions_hidden_for_vim_mode(
2828 &mut self,
2829 hidden: bool,
2830 window: &mut Window,
2831 cx: &mut Context<Self>,
2832 ) {
2833 if hidden != self.edit_predictions_hidden_for_vim_mode {
2834 self.edit_predictions_hidden_for_vim_mode = hidden;
2835 if hidden {
2836 self.update_visible_edit_prediction(window, cx);
2837 } else {
2838 self.refresh_edit_prediction(true, false, window, cx);
2839 }
2840 }
2841 }
2842
2843 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2844 self.menu_edit_predictions_policy = value;
2845 }
2846
2847 pub fn set_autoindent(&mut self, autoindent: bool) {
2848 if autoindent {
2849 self.autoindent_mode = Some(AutoindentMode::EachLine);
2850 } else {
2851 self.autoindent_mode = None;
2852 }
2853 }
2854
2855 pub fn read_only(&self, cx: &App) -> bool {
2856 self.read_only || self.buffer.read(cx).read_only()
2857 }
2858
2859 pub fn set_read_only(&mut self, read_only: bool) {
2860 self.read_only = read_only;
2861 }
2862
2863 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2864 self.use_autoclose = autoclose;
2865 }
2866
2867 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2868 self.use_auto_surround = auto_surround;
2869 }
2870
2871 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2872 self.auto_replace_emoji_shortcode = auto_replace;
2873 }
2874
2875 pub fn toggle_edit_predictions(
2876 &mut self,
2877 _: &ToggleEditPrediction,
2878 window: &mut Window,
2879 cx: &mut Context<Self>,
2880 ) {
2881 if self.show_edit_predictions_override.is_some() {
2882 self.set_show_edit_predictions(None, window, cx);
2883 } else {
2884 let show_edit_predictions = !self.edit_predictions_enabled();
2885 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2886 }
2887 }
2888
2889 pub fn set_show_edit_predictions(
2890 &mut self,
2891 show_edit_predictions: Option<bool>,
2892 window: &mut Window,
2893 cx: &mut Context<Self>,
2894 ) {
2895 self.show_edit_predictions_override = show_edit_predictions;
2896 self.update_edit_prediction_settings(cx);
2897
2898 if let Some(false) = show_edit_predictions {
2899 self.discard_edit_prediction(false, cx);
2900 } else {
2901 self.refresh_edit_prediction(false, true, window, cx);
2902 }
2903 }
2904
2905 fn edit_predictions_disabled_in_scope(
2906 &self,
2907 buffer: &Entity<Buffer>,
2908 buffer_position: language::Anchor,
2909 cx: &App,
2910 ) -> bool {
2911 let snapshot = buffer.read(cx).snapshot();
2912 let settings = snapshot.settings_at(buffer_position, cx);
2913
2914 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2915 return false;
2916 };
2917
2918 scope.override_name().map_or(false, |scope_name| {
2919 settings
2920 .edit_predictions_disabled_in
2921 .iter()
2922 .any(|s| s == scope_name)
2923 })
2924 }
2925
2926 pub fn set_use_modal_editing(&mut self, to: bool) {
2927 self.use_modal_editing = to;
2928 }
2929
2930 pub fn use_modal_editing(&self) -> bool {
2931 self.use_modal_editing
2932 }
2933
2934 fn selections_did_change(
2935 &mut self,
2936 local: bool,
2937 old_cursor_position: &Anchor,
2938 effects: SelectionEffects,
2939 window: &mut Window,
2940 cx: &mut Context<Self>,
2941 ) {
2942 window.invalidate_character_coordinates();
2943
2944 // Copy selections to primary selection buffer
2945 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2946 if local {
2947 let selections = self.selections.all::<usize>(cx);
2948 let buffer_handle = self.buffer.read(cx).read(cx);
2949
2950 let mut text = String::new();
2951 for (index, selection) in selections.iter().enumerate() {
2952 let text_for_selection = buffer_handle
2953 .text_for_range(selection.start..selection.end)
2954 .collect::<String>();
2955
2956 text.push_str(&text_for_selection);
2957 if index != selections.len() - 1 {
2958 text.push('\n');
2959 }
2960 }
2961
2962 if !text.is_empty() {
2963 cx.write_to_primary(ClipboardItem::new_string(text));
2964 }
2965 }
2966
2967 let selection_anchors = self.selections.disjoint_anchors();
2968
2969 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2970 self.buffer.update(cx, |buffer, cx| {
2971 buffer.set_active_selections(
2972 &selection_anchors,
2973 self.selections.line_mode,
2974 self.cursor_shape,
2975 cx,
2976 )
2977 });
2978 }
2979 let display_map = self
2980 .display_map
2981 .update(cx, |display_map, cx| display_map.snapshot(cx));
2982 let buffer = &display_map.buffer_snapshot;
2983 if self.selections.count() == 1 {
2984 self.add_selections_state = None;
2985 }
2986 self.select_next_state = None;
2987 self.select_prev_state = None;
2988 self.select_syntax_node_history.try_clear();
2989 self.invalidate_autoclose_regions(&selection_anchors, buffer);
2990 self.snippet_stack.invalidate(&selection_anchors, buffer);
2991 self.take_rename(false, window, cx);
2992
2993 let newest_selection = self.selections.newest_anchor();
2994 let new_cursor_position = newest_selection.head();
2995 let selection_start = newest_selection.start;
2996
2997 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2998 self.push_to_nav_history(
2999 *old_cursor_position,
3000 Some(new_cursor_position.to_point(buffer)),
3001 false,
3002 effects.nav_history == Some(true),
3003 cx,
3004 );
3005 }
3006
3007 if local {
3008 if let Some(buffer_id) = new_cursor_position.buffer_id {
3009 if !self.registered_buffers.contains_key(&buffer_id) {
3010 if let Some(project) = self.project.as_ref() {
3011 project.update(cx, |project, cx| {
3012 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3013 return;
3014 };
3015 self.registered_buffers.insert(
3016 buffer_id,
3017 project.register_buffer_with_language_servers(&buffer, cx),
3018 );
3019 })
3020 }
3021 }
3022 }
3023
3024 let mut context_menu = self.context_menu.borrow_mut();
3025 let completion_menu = match context_menu.as_ref() {
3026 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3027 Some(CodeContextMenu::CodeActions(_)) => {
3028 *context_menu = None;
3029 None
3030 }
3031 None => None,
3032 };
3033 let completion_position = completion_menu.map(|menu| menu.initial_position);
3034 drop(context_menu);
3035
3036 if effects.completions {
3037 if let Some(completion_position) = completion_position {
3038 let start_offset = selection_start.to_offset(buffer);
3039 let position_matches = start_offset == completion_position.to_offset(buffer);
3040 let continue_showing = if position_matches {
3041 if self.snippet_stack.is_empty() {
3042 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3043 } else {
3044 // Snippet choices can be shown even when the cursor is in whitespace.
3045 // Dismissing the menu with actions like backspace is handled by
3046 // invalidation regions.
3047 true
3048 }
3049 } else {
3050 false
3051 };
3052
3053 if continue_showing {
3054 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3055 } else {
3056 self.hide_context_menu(window, cx);
3057 }
3058 }
3059 }
3060
3061 hide_hover(self, cx);
3062
3063 if old_cursor_position.to_display_point(&display_map).row()
3064 != new_cursor_position.to_display_point(&display_map).row()
3065 {
3066 self.available_code_actions.take();
3067 }
3068 self.refresh_code_actions(window, cx);
3069 self.refresh_document_highlights(cx);
3070 self.refresh_selected_text_highlights(false, window, cx);
3071 refresh_matching_bracket_highlights(self, window, cx);
3072 self.update_visible_edit_prediction(window, cx);
3073 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3074 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3075 self.inline_blame_popover.take();
3076 if self.git_blame_inline_enabled {
3077 self.start_inline_blame_timer(window, cx);
3078 }
3079 }
3080
3081 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3082 cx.emit(EditorEvent::SelectionsChanged { local });
3083
3084 let selections = &self.selections.disjoint;
3085 if selections.len() == 1 {
3086 cx.emit(SearchEvent::ActiveMatchChanged)
3087 }
3088 if local {
3089 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3090 let inmemory_selections = selections
3091 .iter()
3092 .map(|s| {
3093 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3094 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3095 })
3096 .collect();
3097 self.update_restoration_data(cx, |data| {
3098 data.selections = inmemory_selections;
3099 });
3100
3101 if WorkspaceSettings::get(None, cx).restore_on_startup
3102 != RestoreOnStartupBehavior::None
3103 {
3104 if let Some(workspace_id) =
3105 self.workspace.as_ref().and_then(|workspace| workspace.1)
3106 {
3107 let snapshot = self.buffer().read(cx).snapshot(cx);
3108 let selections = selections.clone();
3109 let background_executor = cx.background_executor().clone();
3110 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3111 self.serialize_selections = cx.background_spawn(async move {
3112 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3113 let db_selections = selections
3114 .iter()
3115 .map(|selection| {
3116 (
3117 selection.start.to_offset(&snapshot),
3118 selection.end.to_offset(&snapshot),
3119 )
3120 })
3121 .collect();
3122
3123 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3124 .await
3125 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3126 .log_err();
3127 });
3128 }
3129 }
3130 }
3131 }
3132
3133 cx.notify();
3134 }
3135
3136 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3137 use text::ToOffset as _;
3138 use text::ToPoint as _;
3139
3140 if self.mode.is_minimap()
3141 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3142 {
3143 return;
3144 }
3145
3146 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3147 return;
3148 };
3149
3150 let snapshot = singleton.read(cx).snapshot();
3151 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3152 let display_snapshot = display_map.snapshot(cx);
3153
3154 display_snapshot
3155 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3156 .map(|fold| {
3157 fold.range.start.text_anchor.to_point(&snapshot)
3158 ..fold.range.end.text_anchor.to_point(&snapshot)
3159 })
3160 .collect()
3161 });
3162 self.update_restoration_data(cx, |data| {
3163 data.folds = inmemory_folds;
3164 });
3165
3166 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3167 return;
3168 };
3169 let background_executor = cx.background_executor().clone();
3170 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3171 let db_folds = self.display_map.update(cx, |display_map, cx| {
3172 display_map
3173 .snapshot(cx)
3174 .folds_in_range(0..snapshot.len())
3175 .map(|fold| {
3176 (
3177 fold.range.start.text_anchor.to_offset(&snapshot),
3178 fold.range.end.text_anchor.to_offset(&snapshot),
3179 )
3180 })
3181 .collect()
3182 });
3183 self.serialize_folds = cx.background_spawn(async move {
3184 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3185 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3186 .await
3187 .with_context(|| {
3188 format!(
3189 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3190 )
3191 })
3192 .log_err();
3193 });
3194 }
3195
3196 pub fn sync_selections(
3197 &mut self,
3198 other: Entity<Editor>,
3199 cx: &mut Context<Self>,
3200 ) -> gpui::Subscription {
3201 let other_selections = other.read(cx).selections.disjoint.to_vec();
3202 self.selections.change_with(cx, |selections| {
3203 selections.select_anchors(other_selections);
3204 });
3205
3206 let other_subscription =
3207 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3208 EditorEvent::SelectionsChanged { local: true } => {
3209 let other_selections = other.read(cx).selections.disjoint.to_vec();
3210 if other_selections.is_empty() {
3211 return;
3212 }
3213 this.selections.change_with(cx, |selections| {
3214 selections.select_anchors(other_selections);
3215 });
3216 }
3217 _ => {}
3218 });
3219
3220 let this_subscription =
3221 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3222 EditorEvent::SelectionsChanged { local: true } => {
3223 let these_selections = this.selections.disjoint.to_vec();
3224 if these_selections.is_empty() {
3225 return;
3226 }
3227 other.update(cx, |other_editor, cx| {
3228 other_editor.selections.change_with(cx, |selections| {
3229 selections.select_anchors(these_selections);
3230 })
3231 });
3232 }
3233 _ => {}
3234 });
3235
3236 Subscription::join(other_subscription, this_subscription)
3237 }
3238
3239 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3240 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3241 /// effects of selection change occur at the end of the transaction.
3242 pub fn change_selections<R>(
3243 &mut self,
3244 effects: SelectionEffects,
3245 window: &mut Window,
3246 cx: &mut Context<Self>,
3247 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3248 ) -> R {
3249 if let Some(state) = &mut self.deferred_selection_effects_state {
3250 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3251 state.effects.completions = effects.completions;
3252 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3253 let (changed, result) = self.selections.change_with(cx, change);
3254 state.changed |= changed;
3255 return result;
3256 }
3257 let mut state = DeferredSelectionEffectsState {
3258 changed: false,
3259 effects,
3260 old_cursor_position: self.selections.newest_anchor().head(),
3261 history_entry: SelectionHistoryEntry {
3262 selections: self.selections.disjoint_anchors(),
3263 select_next_state: self.select_next_state.clone(),
3264 select_prev_state: self.select_prev_state.clone(),
3265 add_selections_state: self.add_selections_state.clone(),
3266 },
3267 };
3268 let (changed, result) = self.selections.change_with(cx, change);
3269 state.changed = state.changed || changed;
3270 if self.defer_selection_effects {
3271 self.deferred_selection_effects_state = Some(state);
3272 } else {
3273 self.apply_selection_effects(state, window, cx);
3274 }
3275 result
3276 }
3277
3278 /// Defers the effects of selection change, so that the effects of multiple calls to
3279 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3280 /// to selection history and the state of popovers based on selection position aren't
3281 /// erroneously updated.
3282 pub fn with_selection_effects_deferred<R>(
3283 &mut self,
3284 window: &mut Window,
3285 cx: &mut Context<Self>,
3286 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3287 ) -> R {
3288 let already_deferred = self.defer_selection_effects;
3289 self.defer_selection_effects = true;
3290 let result = update(self, window, cx);
3291 if !already_deferred {
3292 self.defer_selection_effects = false;
3293 if let Some(state) = self.deferred_selection_effects_state.take() {
3294 self.apply_selection_effects(state, window, cx);
3295 }
3296 }
3297 result
3298 }
3299
3300 fn apply_selection_effects(
3301 &mut self,
3302 state: DeferredSelectionEffectsState,
3303 window: &mut Window,
3304 cx: &mut Context<Self>,
3305 ) {
3306 if state.changed {
3307 self.selection_history.push(state.history_entry);
3308
3309 if let Some(autoscroll) = state.effects.scroll {
3310 self.request_autoscroll(autoscroll, cx);
3311 }
3312
3313 let old_cursor_position = &state.old_cursor_position;
3314
3315 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3316
3317 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3318 self.show_signature_help(&ShowSignatureHelp, window, cx);
3319 }
3320 }
3321 }
3322
3323 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3324 where
3325 I: IntoIterator<Item = (Range<S>, T)>,
3326 S: ToOffset,
3327 T: Into<Arc<str>>,
3328 {
3329 if self.read_only(cx) {
3330 return;
3331 }
3332
3333 self.buffer
3334 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3335 }
3336
3337 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3338 where
3339 I: IntoIterator<Item = (Range<S>, T)>,
3340 S: ToOffset,
3341 T: Into<Arc<str>>,
3342 {
3343 if self.read_only(cx) {
3344 return;
3345 }
3346
3347 self.buffer.update(cx, |buffer, cx| {
3348 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3349 });
3350 }
3351
3352 pub fn edit_with_block_indent<I, S, T>(
3353 &mut self,
3354 edits: I,
3355 original_indent_columns: Vec<Option<u32>>,
3356 cx: &mut Context<Self>,
3357 ) where
3358 I: IntoIterator<Item = (Range<S>, T)>,
3359 S: ToOffset,
3360 T: Into<Arc<str>>,
3361 {
3362 if self.read_only(cx) {
3363 return;
3364 }
3365
3366 self.buffer.update(cx, |buffer, cx| {
3367 buffer.edit(
3368 edits,
3369 Some(AutoindentMode::Block {
3370 original_indent_columns,
3371 }),
3372 cx,
3373 )
3374 });
3375 }
3376
3377 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3378 self.hide_context_menu(window, cx);
3379
3380 match phase {
3381 SelectPhase::Begin {
3382 position,
3383 add,
3384 click_count,
3385 } => self.begin_selection(position, add, click_count, window, cx),
3386 SelectPhase::BeginColumnar {
3387 position,
3388 goal_column,
3389 reset,
3390 mode,
3391 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3392 SelectPhase::Extend {
3393 position,
3394 click_count,
3395 } => self.extend_selection(position, click_count, window, cx),
3396 SelectPhase::Update {
3397 position,
3398 goal_column,
3399 scroll_delta,
3400 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3401 SelectPhase::End => self.end_selection(window, cx),
3402 }
3403 }
3404
3405 fn extend_selection(
3406 &mut self,
3407 position: DisplayPoint,
3408 click_count: usize,
3409 window: &mut Window,
3410 cx: &mut Context<Self>,
3411 ) {
3412 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3413 let tail = self.selections.newest::<usize>(cx).tail();
3414 self.begin_selection(position, false, click_count, window, cx);
3415
3416 let position = position.to_offset(&display_map, Bias::Left);
3417 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3418
3419 let mut pending_selection = self
3420 .selections
3421 .pending_anchor()
3422 .expect("extend_selection not called with pending selection");
3423 if position >= tail {
3424 pending_selection.start = tail_anchor;
3425 } else {
3426 pending_selection.end = tail_anchor;
3427 pending_selection.reversed = true;
3428 }
3429
3430 let mut pending_mode = self.selections.pending_mode().unwrap();
3431 match &mut pending_mode {
3432 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3433 _ => {}
3434 }
3435
3436 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3437 SelectionEffects::scroll(Autoscroll::fit())
3438 } else {
3439 SelectionEffects::no_scroll()
3440 };
3441
3442 self.change_selections(effects, window, cx, |s| {
3443 s.set_pending(pending_selection, pending_mode)
3444 });
3445 }
3446
3447 fn begin_selection(
3448 &mut self,
3449 position: DisplayPoint,
3450 add: bool,
3451 click_count: usize,
3452 window: &mut Window,
3453 cx: &mut Context<Self>,
3454 ) {
3455 if !self.focus_handle.is_focused(window) {
3456 self.last_focused_descendant = None;
3457 window.focus(&self.focus_handle);
3458 }
3459
3460 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3461 let buffer = &display_map.buffer_snapshot;
3462 let position = display_map.clip_point(position, Bias::Left);
3463
3464 let start;
3465 let end;
3466 let mode;
3467 let mut auto_scroll;
3468 match click_count {
3469 1 => {
3470 start = buffer.anchor_before(position.to_point(&display_map));
3471 end = start;
3472 mode = SelectMode::Character;
3473 auto_scroll = true;
3474 }
3475 2 => {
3476 let position = display_map
3477 .clip_point(position, Bias::Left)
3478 .to_offset(&display_map, Bias::Left);
3479 let (range, _) = buffer.surrounding_word(position, false);
3480 start = buffer.anchor_before(range.start);
3481 end = buffer.anchor_before(range.end);
3482 mode = SelectMode::Word(start..end);
3483 auto_scroll = true;
3484 }
3485 3 => {
3486 let position = display_map
3487 .clip_point(position, Bias::Left)
3488 .to_point(&display_map);
3489 let line_start = display_map.prev_line_boundary(position).0;
3490 let next_line_start = buffer.clip_point(
3491 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3492 Bias::Left,
3493 );
3494 start = buffer.anchor_before(line_start);
3495 end = buffer.anchor_before(next_line_start);
3496 mode = SelectMode::Line(start..end);
3497 auto_scroll = true;
3498 }
3499 _ => {
3500 start = buffer.anchor_before(0);
3501 end = buffer.anchor_before(buffer.len());
3502 mode = SelectMode::All;
3503 auto_scroll = false;
3504 }
3505 }
3506 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3507
3508 let point_to_delete: Option<usize> = {
3509 let selected_points: Vec<Selection<Point>> =
3510 self.selections.disjoint_in_range(start..end, cx);
3511
3512 if !add || click_count > 1 {
3513 None
3514 } else if !selected_points.is_empty() {
3515 Some(selected_points[0].id)
3516 } else {
3517 let clicked_point_already_selected =
3518 self.selections.disjoint.iter().find(|selection| {
3519 selection.start.to_point(buffer) == start.to_point(buffer)
3520 || selection.end.to_point(buffer) == end.to_point(buffer)
3521 });
3522
3523 clicked_point_already_selected.map(|selection| selection.id)
3524 }
3525 };
3526
3527 let selections_count = self.selections.count();
3528 let effects = if auto_scroll {
3529 SelectionEffects::default()
3530 } else {
3531 SelectionEffects::no_scroll()
3532 };
3533
3534 self.change_selections(effects, window, cx, |s| {
3535 if let Some(point_to_delete) = point_to_delete {
3536 s.delete(point_to_delete);
3537
3538 if selections_count == 1 {
3539 s.set_pending_anchor_range(start..end, mode);
3540 }
3541 } else {
3542 if !add {
3543 s.clear_disjoint();
3544 }
3545
3546 s.set_pending_anchor_range(start..end, mode);
3547 }
3548 });
3549 }
3550
3551 fn begin_columnar_selection(
3552 &mut self,
3553 position: DisplayPoint,
3554 goal_column: u32,
3555 reset: bool,
3556 mode: ColumnarMode,
3557 window: &mut Window,
3558 cx: &mut Context<Self>,
3559 ) {
3560 if !self.focus_handle.is_focused(window) {
3561 self.last_focused_descendant = None;
3562 window.focus(&self.focus_handle);
3563 }
3564
3565 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3566
3567 if reset {
3568 let pointer_position = display_map
3569 .buffer_snapshot
3570 .anchor_before(position.to_point(&display_map));
3571
3572 self.change_selections(
3573 SelectionEffects::scroll(Autoscroll::newest()),
3574 window,
3575 cx,
3576 |s| {
3577 s.clear_disjoint();
3578 s.set_pending_anchor_range(
3579 pointer_position..pointer_position,
3580 SelectMode::Character,
3581 );
3582 },
3583 );
3584 };
3585
3586 let tail = self.selections.newest::<Point>(cx).tail();
3587 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3588 self.columnar_selection_state = match mode {
3589 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3590 selection_tail: selection_anchor,
3591 display_point: if reset {
3592 if position.column() != goal_column {
3593 Some(DisplayPoint::new(position.row(), goal_column))
3594 } else {
3595 None
3596 }
3597 } else {
3598 None
3599 },
3600 }),
3601 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3602 selection_tail: selection_anchor,
3603 }),
3604 };
3605
3606 if !reset {
3607 self.select_columns(position, goal_column, &display_map, window, cx);
3608 }
3609 }
3610
3611 fn update_selection(
3612 &mut self,
3613 position: DisplayPoint,
3614 goal_column: u32,
3615 scroll_delta: gpui::Point<f32>,
3616 window: &mut Window,
3617 cx: &mut Context<Self>,
3618 ) {
3619 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3620
3621 if self.columnar_selection_state.is_some() {
3622 self.select_columns(position, goal_column, &display_map, window, cx);
3623 } else if let Some(mut pending) = self.selections.pending_anchor() {
3624 let buffer = &display_map.buffer_snapshot;
3625 let head;
3626 let tail;
3627 let mode = self.selections.pending_mode().unwrap();
3628 match &mode {
3629 SelectMode::Character => {
3630 head = position.to_point(&display_map);
3631 tail = pending.tail().to_point(buffer);
3632 }
3633 SelectMode::Word(original_range) => {
3634 let offset = display_map
3635 .clip_point(position, Bias::Left)
3636 .to_offset(&display_map, Bias::Left);
3637 let original_range = original_range.to_offset(buffer);
3638
3639 let head_offset = if buffer.is_inside_word(offset, false)
3640 || original_range.contains(&offset)
3641 {
3642 let (word_range, _) = buffer.surrounding_word(offset, false);
3643 if word_range.start < original_range.start {
3644 word_range.start
3645 } else {
3646 word_range.end
3647 }
3648 } else {
3649 offset
3650 };
3651
3652 head = head_offset.to_point(buffer);
3653 if head_offset <= original_range.start {
3654 tail = original_range.end.to_point(buffer);
3655 } else {
3656 tail = original_range.start.to_point(buffer);
3657 }
3658 }
3659 SelectMode::Line(original_range) => {
3660 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3661
3662 let position = display_map
3663 .clip_point(position, Bias::Left)
3664 .to_point(&display_map);
3665 let line_start = display_map.prev_line_boundary(position).0;
3666 let next_line_start = buffer.clip_point(
3667 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3668 Bias::Left,
3669 );
3670
3671 if line_start < original_range.start {
3672 head = line_start
3673 } else {
3674 head = next_line_start
3675 }
3676
3677 if head <= original_range.start {
3678 tail = original_range.end;
3679 } else {
3680 tail = original_range.start;
3681 }
3682 }
3683 SelectMode::All => {
3684 return;
3685 }
3686 };
3687
3688 if head < tail {
3689 pending.start = buffer.anchor_before(head);
3690 pending.end = buffer.anchor_before(tail);
3691 pending.reversed = true;
3692 } else {
3693 pending.start = buffer.anchor_before(tail);
3694 pending.end = buffer.anchor_before(head);
3695 pending.reversed = false;
3696 }
3697
3698 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3699 s.set_pending(pending, mode);
3700 });
3701 } else {
3702 log::error!("update_selection dispatched with no pending selection");
3703 return;
3704 }
3705
3706 self.apply_scroll_delta(scroll_delta, window, cx);
3707 cx.notify();
3708 }
3709
3710 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3711 self.columnar_selection_state.take();
3712 if self.selections.pending_anchor().is_some() {
3713 let selections = self.selections.all::<usize>(cx);
3714 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3715 s.select(selections);
3716 s.clear_pending();
3717 });
3718 }
3719 }
3720
3721 fn select_columns(
3722 &mut self,
3723 head: DisplayPoint,
3724 goal_column: u32,
3725 display_map: &DisplaySnapshot,
3726 window: &mut Window,
3727 cx: &mut Context<Self>,
3728 ) {
3729 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3730 return;
3731 };
3732
3733 let tail = match columnar_state {
3734 ColumnarSelectionState::FromMouse {
3735 selection_tail,
3736 display_point,
3737 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3738 ColumnarSelectionState::FromSelection { selection_tail } => {
3739 selection_tail.to_display_point(&display_map)
3740 }
3741 };
3742
3743 let start_row = cmp::min(tail.row(), head.row());
3744 let end_row = cmp::max(tail.row(), head.row());
3745 let start_column = cmp::min(tail.column(), goal_column);
3746 let end_column = cmp::max(tail.column(), goal_column);
3747 let reversed = start_column < tail.column();
3748
3749 let selection_ranges = (start_row.0..=end_row.0)
3750 .map(DisplayRow)
3751 .filter_map(|row| {
3752 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3753 || start_column <= display_map.line_len(row))
3754 && !display_map.is_block_line(row)
3755 {
3756 let start = display_map
3757 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3758 .to_point(display_map);
3759 let end = display_map
3760 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3761 .to_point(display_map);
3762 if reversed {
3763 Some(end..start)
3764 } else {
3765 Some(start..end)
3766 }
3767 } else {
3768 None
3769 }
3770 })
3771 .collect::<Vec<_>>();
3772
3773 let ranges = match columnar_state {
3774 ColumnarSelectionState::FromMouse { .. } => {
3775 let mut non_empty_ranges = selection_ranges
3776 .iter()
3777 .filter(|selection_range| selection_range.start != selection_range.end)
3778 .peekable();
3779 if non_empty_ranges.peek().is_some() {
3780 non_empty_ranges.cloned().collect()
3781 } else {
3782 selection_ranges
3783 }
3784 }
3785 _ => selection_ranges,
3786 };
3787
3788 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3789 s.select_ranges(ranges);
3790 });
3791 cx.notify();
3792 }
3793
3794 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3795 self.selections
3796 .all_adjusted(cx)
3797 .iter()
3798 .any(|selection| !selection.is_empty())
3799 }
3800
3801 pub fn has_pending_nonempty_selection(&self) -> bool {
3802 let pending_nonempty_selection = match self.selections.pending_anchor() {
3803 Some(Selection { start, end, .. }) => start != end,
3804 None => false,
3805 };
3806
3807 pending_nonempty_selection
3808 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3809 }
3810
3811 pub fn has_pending_selection(&self) -> bool {
3812 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3813 }
3814
3815 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3816 self.selection_mark_mode = false;
3817 self.selection_drag_state = SelectionDragState::None;
3818
3819 if self.clear_expanded_diff_hunks(cx) {
3820 cx.notify();
3821 return;
3822 }
3823 if self.dismiss_menus_and_popups(true, window, cx) {
3824 return;
3825 }
3826
3827 if self.mode.is_full()
3828 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3829 {
3830 return;
3831 }
3832
3833 cx.propagate();
3834 }
3835
3836 pub fn dismiss_menus_and_popups(
3837 &mut self,
3838 is_user_requested: bool,
3839 window: &mut Window,
3840 cx: &mut Context<Self>,
3841 ) -> bool {
3842 if self.take_rename(false, window, cx).is_some() {
3843 return true;
3844 }
3845
3846 if hide_hover(self, cx) {
3847 return true;
3848 }
3849
3850 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3851 return true;
3852 }
3853
3854 if self.hide_context_menu(window, cx).is_some() {
3855 return true;
3856 }
3857
3858 if self.mouse_context_menu.take().is_some() {
3859 return true;
3860 }
3861
3862 if is_user_requested && self.discard_edit_prediction(true, cx) {
3863 return true;
3864 }
3865
3866 if self.snippet_stack.pop().is_some() {
3867 return true;
3868 }
3869
3870 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3871 self.dismiss_diagnostics(cx);
3872 return true;
3873 }
3874
3875 false
3876 }
3877
3878 fn linked_editing_ranges_for(
3879 &self,
3880 selection: Range<text::Anchor>,
3881 cx: &App,
3882 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3883 if self.linked_edit_ranges.is_empty() {
3884 return None;
3885 }
3886 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3887 selection.end.buffer_id.and_then(|end_buffer_id| {
3888 if selection.start.buffer_id != Some(end_buffer_id) {
3889 return None;
3890 }
3891 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3892 let snapshot = buffer.read(cx).snapshot();
3893 self.linked_edit_ranges
3894 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3895 .map(|ranges| (ranges, snapshot, buffer))
3896 })?;
3897 use text::ToOffset as TO;
3898 // find offset from the start of current range to current cursor position
3899 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3900
3901 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3902 let start_difference = start_offset - start_byte_offset;
3903 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3904 let end_difference = end_offset - start_byte_offset;
3905 // Current range has associated linked ranges.
3906 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3907 for range in linked_ranges.iter() {
3908 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3909 let end_offset = start_offset + end_difference;
3910 let start_offset = start_offset + start_difference;
3911 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3912 continue;
3913 }
3914 if self.selections.disjoint_anchor_ranges().any(|s| {
3915 if s.start.buffer_id != selection.start.buffer_id
3916 || s.end.buffer_id != selection.end.buffer_id
3917 {
3918 return false;
3919 }
3920 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3921 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3922 }) {
3923 continue;
3924 }
3925 let start = buffer_snapshot.anchor_after(start_offset);
3926 let end = buffer_snapshot.anchor_after(end_offset);
3927 linked_edits
3928 .entry(buffer.clone())
3929 .or_default()
3930 .push(start..end);
3931 }
3932 Some(linked_edits)
3933 }
3934
3935 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3936 let text: Arc<str> = text.into();
3937
3938 if self.read_only(cx) {
3939 return;
3940 }
3941
3942 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3943
3944 let selections = self.selections.all_adjusted(cx);
3945 let mut bracket_inserted = false;
3946 let mut edits = Vec::new();
3947 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3948 let mut new_selections = Vec::with_capacity(selections.len());
3949 let mut new_autoclose_regions = Vec::new();
3950 let snapshot = self.buffer.read(cx).read(cx);
3951 let mut clear_linked_edit_ranges = false;
3952
3953 for (selection, autoclose_region) in
3954 self.selections_with_autoclose_regions(selections, &snapshot)
3955 {
3956 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3957 // Determine if the inserted text matches the opening or closing
3958 // bracket of any of this language's bracket pairs.
3959 let mut bracket_pair = None;
3960 let mut is_bracket_pair_start = false;
3961 let mut is_bracket_pair_end = false;
3962 if !text.is_empty() {
3963 let mut bracket_pair_matching_end = None;
3964 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3965 // and they are removing the character that triggered IME popup.
3966 for (pair, enabled) in scope.brackets() {
3967 if !pair.close && !pair.surround {
3968 continue;
3969 }
3970
3971 if enabled && pair.start.ends_with(text.as_ref()) {
3972 let prefix_len = pair.start.len() - text.len();
3973 let preceding_text_matches_prefix = prefix_len == 0
3974 || (selection.start.column >= (prefix_len as u32)
3975 && snapshot.contains_str_at(
3976 Point::new(
3977 selection.start.row,
3978 selection.start.column - (prefix_len as u32),
3979 ),
3980 &pair.start[..prefix_len],
3981 ));
3982 if preceding_text_matches_prefix {
3983 bracket_pair = Some(pair.clone());
3984 is_bracket_pair_start = true;
3985 break;
3986 }
3987 }
3988 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3989 {
3990 // take first bracket pair matching end, but don't break in case a later bracket
3991 // pair matches start
3992 bracket_pair_matching_end = Some(pair.clone());
3993 }
3994 }
3995 if let Some(end) = bracket_pair_matching_end
3996 && bracket_pair.is_none()
3997 {
3998 bracket_pair = Some(end);
3999 is_bracket_pair_end = true;
4000 }
4001 }
4002
4003 if let Some(bracket_pair) = bracket_pair {
4004 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4005 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4006 let auto_surround =
4007 self.use_auto_surround && snapshot_settings.use_auto_surround;
4008 if selection.is_empty() {
4009 if is_bracket_pair_start {
4010 // If the inserted text is a suffix of an opening bracket and the
4011 // selection is preceded by the rest of the opening bracket, then
4012 // insert the closing bracket.
4013 let following_text_allows_autoclose = snapshot
4014 .chars_at(selection.start)
4015 .next()
4016 .map_or(true, |c| scope.should_autoclose_before(c));
4017
4018 let preceding_text_allows_autoclose = selection.start.column == 0
4019 || snapshot.reversed_chars_at(selection.start).next().map_or(
4020 true,
4021 |c| {
4022 bracket_pair.start != bracket_pair.end
4023 || !snapshot
4024 .char_classifier_at(selection.start)
4025 .is_word(c)
4026 },
4027 );
4028
4029 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4030 && bracket_pair.start.len() == 1
4031 {
4032 let target = bracket_pair.start.chars().next().unwrap();
4033 let current_line_count = snapshot
4034 .reversed_chars_at(selection.start)
4035 .take_while(|&c| c != '\n')
4036 .filter(|&c| c == target)
4037 .count();
4038 current_line_count % 2 == 1
4039 } else {
4040 false
4041 };
4042
4043 if autoclose
4044 && bracket_pair.close
4045 && following_text_allows_autoclose
4046 && preceding_text_allows_autoclose
4047 && !is_closing_quote
4048 {
4049 let anchor = snapshot.anchor_before(selection.end);
4050 new_selections.push((selection.map(|_| anchor), text.len()));
4051 new_autoclose_regions.push((
4052 anchor,
4053 text.len(),
4054 selection.id,
4055 bracket_pair.clone(),
4056 ));
4057 edits.push((
4058 selection.range(),
4059 format!("{}{}", text, bracket_pair.end).into(),
4060 ));
4061 bracket_inserted = true;
4062 continue;
4063 }
4064 }
4065
4066 if let Some(region) = autoclose_region {
4067 // If the selection is followed by an auto-inserted closing bracket,
4068 // then don't insert that closing bracket again; just move the selection
4069 // past the closing bracket.
4070 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4071 && text.as_ref() == region.pair.end.as_str()
4072 && snapshot.contains_str_at(region.range.end, text.as_ref());
4073 if should_skip {
4074 let anchor = snapshot.anchor_after(selection.end);
4075 new_selections
4076 .push((selection.map(|_| anchor), region.pair.end.len()));
4077 continue;
4078 }
4079 }
4080
4081 let always_treat_brackets_as_autoclosed = snapshot
4082 .language_settings_at(selection.start, cx)
4083 .always_treat_brackets_as_autoclosed;
4084 if always_treat_brackets_as_autoclosed
4085 && is_bracket_pair_end
4086 && snapshot.contains_str_at(selection.end, text.as_ref())
4087 {
4088 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4089 // and the inserted text is a closing bracket and the selection is followed
4090 // by the closing bracket then move the selection past the closing bracket.
4091 let anchor = snapshot.anchor_after(selection.end);
4092 new_selections.push((selection.map(|_| anchor), text.len()));
4093 continue;
4094 }
4095 }
4096 // If an opening bracket is 1 character long and is typed while
4097 // text is selected, then surround that text with the bracket pair.
4098 else if auto_surround
4099 && bracket_pair.surround
4100 && is_bracket_pair_start
4101 && bracket_pair.start.chars().count() == 1
4102 {
4103 edits.push((selection.start..selection.start, text.clone()));
4104 edits.push((
4105 selection.end..selection.end,
4106 bracket_pair.end.as_str().into(),
4107 ));
4108 bracket_inserted = true;
4109 new_selections.push((
4110 Selection {
4111 id: selection.id,
4112 start: snapshot.anchor_after(selection.start),
4113 end: snapshot.anchor_before(selection.end),
4114 reversed: selection.reversed,
4115 goal: selection.goal,
4116 },
4117 0,
4118 ));
4119 continue;
4120 }
4121 }
4122 }
4123
4124 if self.auto_replace_emoji_shortcode
4125 && selection.is_empty()
4126 && text.as_ref().ends_with(':')
4127 {
4128 if let Some(possible_emoji_short_code) =
4129 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4130 {
4131 if !possible_emoji_short_code.is_empty() {
4132 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4133 let emoji_shortcode_start = Point::new(
4134 selection.start.row,
4135 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4136 );
4137
4138 // Remove shortcode from buffer
4139 edits.push((
4140 emoji_shortcode_start..selection.start,
4141 "".to_string().into(),
4142 ));
4143 new_selections.push((
4144 Selection {
4145 id: selection.id,
4146 start: snapshot.anchor_after(emoji_shortcode_start),
4147 end: snapshot.anchor_before(selection.start),
4148 reversed: selection.reversed,
4149 goal: selection.goal,
4150 },
4151 0,
4152 ));
4153
4154 // Insert emoji
4155 let selection_start_anchor = snapshot.anchor_after(selection.start);
4156 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4157 edits.push((selection.start..selection.end, emoji.to_string().into()));
4158
4159 continue;
4160 }
4161 }
4162 }
4163 }
4164
4165 // If not handling any auto-close operation, then just replace the selected
4166 // text with the given input and move the selection to the end of the
4167 // newly inserted text.
4168 let anchor = snapshot.anchor_after(selection.end);
4169 if !self.linked_edit_ranges.is_empty() {
4170 let start_anchor = snapshot.anchor_before(selection.start);
4171
4172 let is_word_char = text.chars().next().map_or(true, |char| {
4173 let classifier = snapshot
4174 .char_classifier_at(start_anchor.to_offset(&snapshot))
4175 .ignore_punctuation(true);
4176 classifier.is_word(char)
4177 });
4178
4179 if is_word_char {
4180 if let Some(ranges) = self
4181 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4182 {
4183 for (buffer, edits) in ranges {
4184 linked_edits
4185 .entry(buffer.clone())
4186 .or_default()
4187 .extend(edits.into_iter().map(|range| (range, text.clone())));
4188 }
4189 }
4190 } else {
4191 clear_linked_edit_ranges = true;
4192 }
4193 }
4194
4195 new_selections.push((selection.map(|_| anchor), 0));
4196 edits.push((selection.start..selection.end, text.clone()));
4197 }
4198
4199 drop(snapshot);
4200
4201 self.transact(window, cx, |this, window, cx| {
4202 if clear_linked_edit_ranges {
4203 this.linked_edit_ranges.clear();
4204 }
4205 let initial_buffer_versions =
4206 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4207
4208 this.buffer.update(cx, |buffer, cx| {
4209 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4210 });
4211 for (buffer, edits) in linked_edits {
4212 buffer.update(cx, |buffer, cx| {
4213 let snapshot = buffer.snapshot();
4214 let edits = edits
4215 .into_iter()
4216 .map(|(range, text)| {
4217 use text::ToPoint as TP;
4218 let end_point = TP::to_point(&range.end, &snapshot);
4219 let start_point = TP::to_point(&range.start, &snapshot);
4220 (start_point..end_point, text)
4221 })
4222 .sorted_by_key(|(range, _)| range.start);
4223 buffer.edit(edits, None, cx);
4224 })
4225 }
4226 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4227 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4228 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4229 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4230 .zip(new_selection_deltas)
4231 .map(|(selection, delta)| Selection {
4232 id: selection.id,
4233 start: selection.start + delta,
4234 end: selection.end + delta,
4235 reversed: selection.reversed,
4236 goal: SelectionGoal::None,
4237 })
4238 .collect::<Vec<_>>();
4239
4240 let mut i = 0;
4241 for (position, delta, selection_id, pair) in new_autoclose_regions {
4242 let position = position.to_offset(&map.buffer_snapshot) + delta;
4243 let start = map.buffer_snapshot.anchor_before(position);
4244 let end = map.buffer_snapshot.anchor_after(position);
4245 while let Some(existing_state) = this.autoclose_regions.get(i) {
4246 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4247 Ordering::Less => i += 1,
4248 Ordering::Greater => break,
4249 Ordering::Equal => {
4250 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4251 Ordering::Less => i += 1,
4252 Ordering::Equal => break,
4253 Ordering::Greater => break,
4254 }
4255 }
4256 }
4257 }
4258 this.autoclose_regions.insert(
4259 i,
4260 AutocloseRegion {
4261 selection_id,
4262 range: start..end,
4263 pair,
4264 },
4265 );
4266 }
4267
4268 let had_active_edit_prediction = this.has_active_edit_prediction();
4269 this.change_selections(
4270 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4271 window,
4272 cx,
4273 |s| s.select(new_selections),
4274 );
4275
4276 if !bracket_inserted {
4277 if let Some(on_type_format_task) =
4278 this.trigger_on_type_formatting(text.to_string(), window, cx)
4279 {
4280 on_type_format_task.detach_and_log_err(cx);
4281 }
4282 }
4283
4284 let editor_settings = EditorSettings::get_global(cx);
4285 if bracket_inserted
4286 && (editor_settings.auto_signature_help
4287 || editor_settings.show_signature_help_after_edits)
4288 {
4289 this.show_signature_help(&ShowSignatureHelp, window, cx);
4290 }
4291
4292 let trigger_in_words =
4293 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4294 if this.hard_wrap.is_some() {
4295 let latest: Range<Point> = this.selections.newest(cx).range();
4296 if latest.is_empty()
4297 && this
4298 .buffer()
4299 .read(cx)
4300 .snapshot(cx)
4301 .line_len(MultiBufferRow(latest.start.row))
4302 == latest.start.column
4303 {
4304 this.rewrap_impl(
4305 RewrapOptions {
4306 override_language_settings: true,
4307 preserve_existing_whitespace: true,
4308 },
4309 cx,
4310 )
4311 }
4312 }
4313 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4314 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4315 this.refresh_edit_prediction(true, false, window, cx);
4316 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4317 });
4318 }
4319
4320 fn find_possible_emoji_shortcode_at_position(
4321 snapshot: &MultiBufferSnapshot,
4322 position: Point,
4323 ) -> Option<String> {
4324 let mut chars = Vec::new();
4325 let mut found_colon = false;
4326 for char in snapshot.reversed_chars_at(position).take(100) {
4327 // Found a possible emoji shortcode in the middle of the buffer
4328 if found_colon {
4329 if char.is_whitespace() {
4330 chars.reverse();
4331 return Some(chars.iter().collect());
4332 }
4333 // If the previous character is not a whitespace, we are in the middle of a word
4334 // and we only want to complete the shortcode if the word is made up of other emojis
4335 let mut containing_word = String::new();
4336 for ch in snapshot
4337 .reversed_chars_at(position)
4338 .skip(chars.len() + 1)
4339 .take(100)
4340 {
4341 if ch.is_whitespace() {
4342 break;
4343 }
4344 containing_word.push(ch);
4345 }
4346 let containing_word = containing_word.chars().rev().collect::<String>();
4347 if util::word_consists_of_emojis(containing_word.as_str()) {
4348 chars.reverse();
4349 return Some(chars.iter().collect());
4350 }
4351 }
4352
4353 if char.is_whitespace() || !char.is_ascii() {
4354 return None;
4355 }
4356 if char == ':' {
4357 found_colon = true;
4358 } else {
4359 chars.push(char);
4360 }
4361 }
4362 // Found a possible emoji shortcode at the beginning of the buffer
4363 chars.reverse();
4364 Some(chars.iter().collect())
4365 }
4366
4367 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4368 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4369 self.transact(window, cx, |this, window, cx| {
4370 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4371 let selections = this.selections.all::<usize>(cx);
4372 let multi_buffer = this.buffer.read(cx);
4373 let buffer = multi_buffer.snapshot(cx);
4374 selections
4375 .iter()
4376 .map(|selection| {
4377 let start_point = selection.start.to_point(&buffer);
4378 let mut existing_indent =
4379 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4380 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4381 let start = selection.start;
4382 let end = selection.end;
4383 let selection_is_empty = start == end;
4384 let language_scope = buffer.language_scope_at(start);
4385 let (
4386 comment_delimiter,
4387 doc_delimiter,
4388 insert_extra_newline,
4389 indent_on_newline,
4390 indent_on_extra_newline,
4391 ) = if let Some(language) = &language_scope {
4392 let mut insert_extra_newline =
4393 insert_extra_newline_brackets(&buffer, start..end, language)
4394 || insert_extra_newline_tree_sitter(&buffer, start..end);
4395
4396 // Comment extension on newline is allowed only for cursor selections
4397 let comment_delimiter = maybe!({
4398 if !selection_is_empty {
4399 return None;
4400 }
4401
4402 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4403 return None;
4404 }
4405
4406 let delimiters = language.line_comment_prefixes();
4407 let max_len_of_delimiter =
4408 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4409 let (snapshot, range) =
4410 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4411
4412 let num_of_whitespaces = snapshot
4413 .chars_for_range(range.clone())
4414 .take_while(|c| c.is_whitespace())
4415 .count();
4416 let comment_candidate = snapshot
4417 .chars_for_range(range.clone())
4418 .skip(num_of_whitespaces)
4419 .take(max_len_of_delimiter)
4420 .collect::<String>();
4421 let (delimiter, trimmed_len) = delimiters
4422 .iter()
4423 .filter_map(|delimiter| {
4424 let prefix = delimiter.trim_end();
4425 if comment_candidate.starts_with(prefix) {
4426 Some((delimiter, prefix.len()))
4427 } else {
4428 None
4429 }
4430 })
4431 .max_by_key(|(_, len)| *len)?;
4432
4433 if let Some(BlockCommentConfig {
4434 start: block_start, ..
4435 }) = language.block_comment()
4436 {
4437 let block_start_trimmed = block_start.trim_end();
4438 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4439 let line_content = snapshot
4440 .chars_for_range(range)
4441 .skip(num_of_whitespaces)
4442 .take(block_start_trimmed.len())
4443 .collect::<String>();
4444
4445 if line_content.starts_with(block_start_trimmed) {
4446 return None;
4447 }
4448 }
4449 }
4450
4451 let cursor_is_placed_after_comment_marker =
4452 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4453 if cursor_is_placed_after_comment_marker {
4454 Some(delimiter.clone())
4455 } else {
4456 None
4457 }
4458 });
4459
4460 let mut indent_on_newline = IndentSize::spaces(0);
4461 let mut indent_on_extra_newline = IndentSize::spaces(0);
4462
4463 let doc_delimiter = maybe!({
4464 if !selection_is_empty {
4465 return None;
4466 }
4467
4468 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4469 return None;
4470 }
4471
4472 let BlockCommentConfig {
4473 start: start_tag,
4474 end: end_tag,
4475 prefix: delimiter,
4476 tab_size: len,
4477 } = language.documentation_comment()?;
4478 let is_within_block_comment = buffer
4479 .language_scope_at(start_point)
4480 .is_some_and(|scope| scope.override_name() == Some("comment"));
4481 if !is_within_block_comment {
4482 return None;
4483 }
4484
4485 let (snapshot, range) =
4486 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4487
4488 let num_of_whitespaces = snapshot
4489 .chars_for_range(range.clone())
4490 .take_while(|c| c.is_whitespace())
4491 .count();
4492
4493 // 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.
4494 let column = start_point.column;
4495 let cursor_is_after_start_tag = {
4496 let start_tag_len = start_tag.len();
4497 let start_tag_line = snapshot
4498 .chars_for_range(range.clone())
4499 .skip(num_of_whitespaces)
4500 .take(start_tag_len)
4501 .collect::<String>();
4502 if start_tag_line.starts_with(start_tag.as_ref()) {
4503 num_of_whitespaces + start_tag_len <= column as usize
4504 } else {
4505 false
4506 }
4507 };
4508
4509 let cursor_is_after_delimiter = {
4510 let delimiter_trim = delimiter.trim_end();
4511 let delimiter_line = snapshot
4512 .chars_for_range(range.clone())
4513 .skip(num_of_whitespaces)
4514 .take(delimiter_trim.len())
4515 .collect::<String>();
4516 if delimiter_line.starts_with(delimiter_trim) {
4517 num_of_whitespaces + delimiter_trim.len() <= column as usize
4518 } else {
4519 false
4520 }
4521 };
4522
4523 let cursor_is_before_end_tag_if_exists = {
4524 let mut char_position = 0u32;
4525 let mut end_tag_offset = None;
4526
4527 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4528 if let Some(byte_pos) = chunk.find(&**end_tag) {
4529 let chars_before_match =
4530 chunk[..byte_pos].chars().count() as u32;
4531 end_tag_offset =
4532 Some(char_position + chars_before_match);
4533 break 'outer;
4534 }
4535 char_position += chunk.chars().count() as u32;
4536 }
4537
4538 if let Some(end_tag_offset) = end_tag_offset {
4539 let cursor_is_before_end_tag = column <= end_tag_offset;
4540 if cursor_is_after_start_tag {
4541 if cursor_is_before_end_tag {
4542 insert_extra_newline = true;
4543 }
4544 let cursor_is_at_start_of_end_tag =
4545 column == end_tag_offset;
4546 if cursor_is_at_start_of_end_tag {
4547 indent_on_extra_newline.len = *len;
4548 }
4549 }
4550 cursor_is_before_end_tag
4551 } else {
4552 true
4553 }
4554 };
4555
4556 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4557 && cursor_is_before_end_tag_if_exists
4558 {
4559 if cursor_is_after_start_tag {
4560 indent_on_newline.len = *len;
4561 }
4562 Some(delimiter.clone())
4563 } else {
4564 None
4565 }
4566 });
4567
4568 (
4569 comment_delimiter,
4570 doc_delimiter,
4571 insert_extra_newline,
4572 indent_on_newline,
4573 indent_on_extra_newline,
4574 )
4575 } else {
4576 (
4577 None,
4578 None,
4579 false,
4580 IndentSize::default(),
4581 IndentSize::default(),
4582 )
4583 };
4584
4585 let prevent_auto_indent = doc_delimiter.is_some();
4586 let delimiter = comment_delimiter.or(doc_delimiter);
4587
4588 let capacity_for_delimiter =
4589 delimiter.as_deref().map(str::len).unwrap_or_default();
4590 let mut new_text = String::with_capacity(
4591 1 + capacity_for_delimiter
4592 + existing_indent.len as usize
4593 + indent_on_newline.len as usize
4594 + indent_on_extra_newline.len as usize,
4595 );
4596 new_text.push('\n');
4597 new_text.extend(existing_indent.chars());
4598 new_text.extend(indent_on_newline.chars());
4599
4600 if let Some(delimiter) = &delimiter {
4601 new_text.push_str(delimiter);
4602 }
4603
4604 if insert_extra_newline {
4605 new_text.push('\n');
4606 new_text.extend(existing_indent.chars());
4607 new_text.extend(indent_on_extra_newline.chars());
4608 }
4609
4610 let anchor = buffer.anchor_after(end);
4611 let new_selection = selection.map(|_| anchor);
4612 (
4613 ((start..end, new_text), prevent_auto_indent),
4614 (insert_extra_newline, new_selection),
4615 )
4616 })
4617 .unzip()
4618 };
4619
4620 let mut auto_indent_edits = Vec::new();
4621 let mut edits = Vec::new();
4622 for (edit, prevent_auto_indent) in edits_with_flags {
4623 if prevent_auto_indent {
4624 edits.push(edit);
4625 } else {
4626 auto_indent_edits.push(edit);
4627 }
4628 }
4629 if !edits.is_empty() {
4630 this.edit(edits, cx);
4631 }
4632 if !auto_indent_edits.is_empty() {
4633 this.edit_with_autoindent(auto_indent_edits, cx);
4634 }
4635
4636 let buffer = this.buffer.read(cx).snapshot(cx);
4637 let new_selections = selection_info
4638 .into_iter()
4639 .map(|(extra_newline_inserted, new_selection)| {
4640 let mut cursor = new_selection.end.to_point(&buffer);
4641 if extra_newline_inserted {
4642 cursor.row -= 1;
4643 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4644 }
4645 new_selection.map(|_| cursor)
4646 })
4647 .collect();
4648
4649 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4650 this.refresh_edit_prediction(true, false, window, cx);
4651 });
4652 }
4653
4654 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4655 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4656
4657 let buffer = self.buffer.read(cx);
4658 let snapshot = buffer.snapshot(cx);
4659
4660 let mut edits = Vec::new();
4661 let mut rows = Vec::new();
4662
4663 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4664 let cursor = selection.head();
4665 let row = cursor.row;
4666
4667 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4668
4669 let newline = "\n".to_string();
4670 edits.push((start_of_line..start_of_line, newline));
4671
4672 rows.push(row + rows_inserted as u32);
4673 }
4674
4675 self.transact(window, cx, |editor, window, cx| {
4676 editor.edit(edits, cx);
4677
4678 editor.change_selections(Default::default(), window, cx, |s| {
4679 let mut index = 0;
4680 s.move_cursors_with(|map, _, _| {
4681 let row = rows[index];
4682 index += 1;
4683
4684 let point = Point::new(row, 0);
4685 let boundary = map.next_line_boundary(point).1;
4686 let clipped = map.clip_point(boundary, Bias::Left);
4687
4688 (clipped, SelectionGoal::None)
4689 });
4690 });
4691
4692 let mut indent_edits = Vec::new();
4693 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4694 for row in rows {
4695 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4696 for (row, indent) in indents {
4697 if indent.len == 0 {
4698 continue;
4699 }
4700
4701 let text = match indent.kind {
4702 IndentKind::Space => " ".repeat(indent.len as usize),
4703 IndentKind::Tab => "\t".repeat(indent.len as usize),
4704 };
4705 let point = Point::new(row.0, 0);
4706 indent_edits.push((point..point, text));
4707 }
4708 }
4709 editor.edit(indent_edits, cx);
4710 });
4711 }
4712
4713 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4714 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4715
4716 let buffer = self.buffer.read(cx);
4717 let snapshot = buffer.snapshot(cx);
4718
4719 let mut edits = Vec::new();
4720 let mut rows = Vec::new();
4721 let mut rows_inserted = 0;
4722
4723 for selection in self.selections.all_adjusted(cx) {
4724 let cursor = selection.head();
4725 let row = cursor.row;
4726
4727 let point = Point::new(row + 1, 0);
4728 let start_of_line = snapshot.clip_point(point, Bias::Left);
4729
4730 let newline = "\n".to_string();
4731 edits.push((start_of_line..start_of_line, newline));
4732
4733 rows_inserted += 1;
4734 rows.push(row + rows_inserted);
4735 }
4736
4737 self.transact(window, cx, |editor, window, cx| {
4738 editor.edit(edits, cx);
4739
4740 editor.change_selections(Default::default(), window, cx, |s| {
4741 let mut index = 0;
4742 s.move_cursors_with(|map, _, _| {
4743 let row = rows[index];
4744 index += 1;
4745
4746 let point = Point::new(row, 0);
4747 let boundary = map.next_line_boundary(point).1;
4748 let clipped = map.clip_point(boundary, Bias::Left);
4749
4750 (clipped, SelectionGoal::None)
4751 });
4752 });
4753
4754 let mut indent_edits = Vec::new();
4755 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4756 for row in rows {
4757 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4758 for (row, indent) in indents {
4759 if indent.len == 0 {
4760 continue;
4761 }
4762
4763 let text = match indent.kind {
4764 IndentKind::Space => " ".repeat(indent.len as usize),
4765 IndentKind::Tab => "\t".repeat(indent.len as usize),
4766 };
4767 let point = Point::new(row.0, 0);
4768 indent_edits.push((point..point, text));
4769 }
4770 }
4771 editor.edit(indent_edits, cx);
4772 });
4773 }
4774
4775 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4776 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4777 original_indent_columns: Vec::new(),
4778 });
4779 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4780 }
4781
4782 fn insert_with_autoindent_mode(
4783 &mut self,
4784 text: &str,
4785 autoindent_mode: Option<AutoindentMode>,
4786 window: &mut Window,
4787 cx: &mut Context<Self>,
4788 ) {
4789 if self.read_only(cx) {
4790 return;
4791 }
4792
4793 let text: Arc<str> = text.into();
4794 self.transact(window, cx, |this, window, cx| {
4795 let old_selections = this.selections.all_adjusted(cx);
4796 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4797 let anchors = {
4798 let snapshot = buffer.read(cx);
4799 old_selections
4800 .iter()
4801 .map(|s| {
4802 let anchor = snapshot.anchor_after(s.head());
4803 s.map(|_| anchor)
4804 })
4805 .collect::<Vec<_>>()
4806 };
4807 buffer.edit(
4808 old_selections
4809 .iter()
4810 .map(|s| (s.start..s.end, text.clone())),
4811 autoindent_mode,
4812 cx,
4813 );
4814 anchors
4815 });
4816
4817 this.change_selections(Default::default(), window, cx, |s| {
4818 s.select_anchors(selection_anchors);
4819 });
4820
4821 cx.notify();
4822 });
4823 }
4824
4825 fn trigger_completion_on_input(
4826 &mut self,
4827 text: &str,
4828 trigger_in_words: bool,
4829 window: &mut Window,
4830 cx: &mut Context<Self>,
4831 ) {
4832 let completions_source = self
4833 .context_menu
4834 .borrow()
4835 .as_ref()
4836 .and_then(|menu| match menu {
4837 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4838 CodeContextMenu::CodeActions(_) => None,
4839 });
4840
4841 match completions_source {
4842 Some(CompletionsMenuSource::Words) => {
4843 self.show_word_completions(&ShowWordCompletions, window, cx)
4844 }
4845 Some(CompletionsMenuSource::Normal)
4846 | Some(CompletionsMenuSource::SnippetChoices)
4847 | None
4848 if self.is_completion_trigger(
4849 text,
4850 trigger_in_words,
4851 completions_source.is_some(),
4852 cx,
4853 ) =>
4854 {
4855 self.show_completions(
4856 &ShowCompletions {
4857 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4858 },
4859 window,
4860 cx,
4861 )
4862 }
4863 _ => {
4864 self.hide_context_menu(window, cx);
4865 }
4866 }
4867 }
4868
4869 fn is_completion_trigger(
4870 &self,
4871 text: &str,
4872 trigger_in_words: bool,
4873 menu_is_open: bool,
4874 cx: &mut Context<Self>,
4875 ) -> bool {
4876 let position = self.selections.newest_anchor().head();
4877 let multibuffer = self.buffer.read(cx);
4878 let Some(buffer) = position
4879 .buffer_id
4880 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4881 else {
4882 return false;
4883 };
4884
4885 if let Some(completion_provider) = &self.completion_provider {
4886 completion_provider.is_completion_trigger(
4887 &buffer,
4888 position.text_anchor,
4889 text,
4890 trigger_in_words,
4891 menu_is_open,
4892 cx,
4893 )
4894 } else {
4895 false
4896 }
4897 }
4898
4899 /// If any empty selections is touching the start of its innermost containing autoclose
4900 /// region, expand it to select the brackets.
4901 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4902 let selections = self.selections.all::<usize>(cx);
4903 let buffer = self.buffer.read(cx).read(cx);
4904 let new_selections = self
4905 .selections_with_autoclose_regions(selections, &buffer)
4906 .map(|(mut selection, region)| {
4907 if !selection.is_empty() {
4908 return selection;
4909 }
4910
4911 if let Some(region) = region {
4912 let mut range = region.range.to_offset(&buffer);
4913 if selection.start == range.start && range.start >= region.pair.start.len() {
4914 range.start -= region.pair.start.len();
4915 if buffer.contains_str_at(range.start, ®ion.pair.start)
4916 && buffer.contains_str_at(range.end, ®ion.pair.end)
4917 {
4918 range.end += region.pair.end.len();
4919 selection.start = range.start;
4920 selection.end = range.end;
4921
4922 return selection;
4923 }
4924 }
4925 }
4926
4927 let always_treat_brackets_as_autoclosed = buffer
4928 .language_settings_at(selection.start, cx)
4929 .always_treat_brackets_as_autoclosed;
4930
4931 if !always_treat_brackets_as_autoclosed {
4932 return selection;
4933 }
4934
4935 if let Some(scope) = buffer.language_scope_at(selection.start) {
4936 for (pair, enabled) in scope.brackets() {
4937 if !enabled || !pair.close {
4938 continue;
4939 }
4940
4941 if buffer.contains_str_at(selection.start, &pair.end) {
4942 let pair_start_len = pair.start.len();
4943 if buffer.contains_str_at(
4944 selection.start.saturating_sub(pair_start_len),
4945 &pair.start,
4946 ) {
4947 selection.start -= pair_start_len;
4948 selection.end += pair.end.len();
4949
4950 return selection;
4951 }
4952 }
4953 }
4954 }
4955
4956 selection
4957 })
4958 .collect();
4959
4960 drop(buffer);
4961 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4962 selections.select(new_selections)
4963 });
4964 }
4965
4966 /// Iterate the given selections, and for each one, find the smallest surrounding
4967 /// autoclose region. This uses the ordering of the selections and the autoclose
4968 /// regions to avoid repeated comparisons.
4969 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4970 &'a self,
4971 selections: impl IntoIterator<Item = Selection<D>>,
4972 buffer: &'a MultiBufferSnapshot,
4973 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4974 let mut i = 0;
4975 let mut regions = self.autoclose_regions.as_slice();
4976 selections.into_iter().map(move |selection| {
4977 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4978
4979 let mut enclosing = None;
4980 while let Some(pair_state) = regions.get(i) {
4981 if pair_state.range.end.to_offset(buffer) < range.start {
4982 regions = ®ions[i + 1..];
4983 i = 0;
4984 } else if pair_state.range.start.to_offset(buffer) > range.end {
4985 break;
4986 } else {
4987 if pair_state.selection_id == selection.id {
4988 enclosing = Some(pair_state);
4989 }
4990 i += 1;
4991 }
4992 }
4993
4994 (selection, enclosing)
4995 })
4996 }
4997
4998 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
4999 fn invalidate_autoclose_regions(
5000 &mut self,
5001 mut selections: &[Selection<Anchor>],
5002 buffer: &MultiBufferSnapshot,
5003 ) {
5004 self.autoclose_regions.retain(|state| {
5005 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5006 return false;
5007 }
5008
5009 let mut i = 0;
5010 while let Some(selection) = selections.get(i) {
5011 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5012 selections = &selections[1..];
5013 continue;
5014 }
5015 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5016 break;
5017 }
5018 if selection.id == state.selection_id {
5019 return true;
5020 } else {
5021 i += 1;
5022 }
5023 }
5024 false
5025 });
5026 }
5027
5028 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5029 let offset = position.to_offset(buffer);
5030 let (word_range, kind) = buffer.surrounding_word(offset, true);
5031 if offset > word_range.start && kind == Some(CharKind::Word) {
5032 Some(
5033 buffer
5034 .text_for_range(word_range.start..offset)
5035 .collect::<String>(),
5036 )
5037 } else {
5038 None
5039 }
5040 }
5041
5042 pub fn toggle_inline_values(
5043 &mut self,
5044 _: &ToggleInlineValues,
5045 _: &mut Window,
5046 cx: &mut Context<Self>,
5047 ) {
5048 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5049
5050 self.refresh_inline_values(cx);
5051 }
5052
5053 pub fn toggle_inlay_hints(
5054 &mut self,
5055 _: &ToggleInlayHints,
5056 _: &mut Window,
5057 cx: &mut Context<Self>,
5058 ) {
5059 self.refresh_inlay_hints(
5060 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5061 cx,
5062 );
5063 }
5064
5065 pub fn inlay_hints_enabled(&self) -> bool {
5066 self.inlay_hint_cache.enabled
5067 }
5068
5069 pub fn inline_values_enabled(&self) -> bool {
5070 self.inline_value_cache.enabled
5071 }
5072
5073 #[cfg(any(test, feature = "test-support"))]
5074 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5075 self.display_map
5076 .read(cx)
5077 .current_inlays()
5078 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5079 .cloned()
5080 .collect()
5081 }
5082
5083 #[cfg(any(test, feature = "test-support"))]
5084 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5085 self.display_map
5086 .read(cx)
5087 .current_inlays()
5088 .cloned()
5089 .collect()
5090 }
5091
5092 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5093 if self.semantics_provider.is_none() || !self.mode.is_full() {
5094 return;
5095 }
5096
5097 let reason_description = reason.description();
5098 let ignore_debounce = matches!(
5099 reason,
5100 InlayHintRefreshReason::SettingsChange(_)
5101 | InlayHintRefreshReason::Toggle(_)
5102 | InlayHintRefreshReason::ExcerptsRemoved(_)
5103 | InlayHintRefreshReason::ModifiersChanged(_)
5104 );
5105 let (invalidate_cache, required_languages) = match reason {
5106 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5107 match self.inlay_hint_cache.modifiers_override(enabled) {
5108 Some(enabled) => {
5109 if enabled {
5110 (InvalidationStrategy::RefreshRequested, None)
5111 } else {
5112 self.splice_inlays(
5113 &self
5114 .visible_inlay_hints(cx)
5115 .iter()
5116 .map(|inlay| inlay.id)
5117 .collect::<Vec<InlayId>>(),
5118 Vec::new(),
5119 cx,
5120 );
5121 return;
5122 }
5123 }
5124 None => return,
5125 }
5126 }
5127 InlayHintRefreshReason::Toggle(enabled) => {
5128 if self.inlay_hint_cache.toggle(enabled) {
5129 if enabled {
5130 (InvalidationStrategy::RefreshRequested, None)
5131 } else {
5132 self.splice_inlays(
5133 &self
5134 .visible_inlay_hints(cx)
5135 .iter()
5136 .map(|inlay| inlay.id)
5137 .collect::<Vec<InlayId>>(),
5138 Vec::new(),
5139 cx,
5140 );
5141 return;
5142 }
5143 } else {
5144 return;
5145 }
5146 }
5147 InlayHintRefreshReason::SettingsChange(new_settings) => {
5148 match self.inlay_hint_cache.update_settings(
5149 &self.buffer,
5150 new_settings,
5151 self.visible_inlay_hints(cx),
5152 cx,
5153 ) {
5154 ControlFlow::Break(Some(InlaySplice {
5155 to_remove,
5156 to_insert,
5157 })) => {
5158 self.splice_inlays(&to_remove, to_insert, cx);
5159 return;
5160 }
5161 ControlFlow::Break(None) => return,
5162 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5163 }
5164 }
5165 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5166 if let Some(InlaySplice {
5167 to_remove,
5168 to_insert,
5169 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5170 {
5171 self.splice_inlays(&to_remove, to_insert, cx);
5172 }
5173 self.display_map.update(cx, |display_map, _| {
5174 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5175 });
5176 return;
5177 }
5178 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5179 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5180 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5181 }
5182 InlayHintRefreshReason::RefreshRequested => {
5183 (InvalidationStrategy::RefreshRequested, None)
5184 }
5185 };
5186
5187 if let Some(InlaySplice {
5188 to_remove,
5189 to_insert,
5190 }) = self.inlay_hint_cache.spawn_hint_refresh(
5191 reason_description,
5192 self.visible_excerpts(required_languages.as_ref(), cx),
5193 invalidate_cache,
5194 ignore_debounce,
5195 cx,
5196 ) {
5197 self.splice_inlays(&to_remove, to_insert, cx);
5198 }
5199 }
5200
5201 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5202 self.display_map
5203 .read(cx)
5204 .current_inlays()
5205 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5206 .cloned()
5207 .collect()
5208 }
5209
5210 pub fn visible_excerpts(
5211 &self,
5212 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5213 cx: &mut Context<Editor>,
5214 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5215 let Some(project) = self.project.as_ref() else {
5216 return HashMap::default();
5217 };
5218 let project = project.read(cx);
5219 let multi_buffer = self.buffer().read(cx);
5220 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5221 let multi_buffer_visible_start = self
5222 .scroll_manager
5223 .anchor()
5224 .anchor
5225 .to_point(&multi_buffer_snapshot);
5226 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5227 multi_buffer_visible_start
5228 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5229 Bias::Left,
5230 );
5231 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5232 multi_buffer_snapshot
5233 .range_to_buffer_ranges(multi_buffer_visible_range)
5234 .into_iter()
5235 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5236 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5237 let buffer_file = project::File::from_dyn(buffer.file())?;
5238 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5239 let worktree_entry = buffer_worktree
5240 .read(cx)
5241 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5242 if worktree_entry.is_ignored {
5243 return None;
5244 }
5245
5246 let language = buffer.language()?;
5247 if let Some(restrict_to_languages) = restrict_to_languages {
5248 if !restrict_to_languages.contains(language) {
5249 return None;
5250 }
5251 }
5252 Some((
5253 excerpt_id,
5254 (
5255 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5256 buffer.version().clone(),
5257 excerpt_visible_range,
5258 ),
5259 ))
5260 })
5261 .collect()
5262 }
5263
5264 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5265 TextLayoutDetails {
5266 text_system: window.text_system().clone(),
5267 editor_style: self.style.clone().unwrap(),
5268 rem_size: window.rem_size(),
5269 scroll_anchor: self.scroll_manager.anchor(),
5270 visible_rows: self.visible_line_count(),
5271 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5272 }
5273 }
5274
5275 pub fn splice_inlays(
5276 &self,
5277 to_remove: &[InlayId],
5278 to_insert: Vec<Inlay>,
5279 cx: &mut Context<Self>,
5280 ) {
5281 self.display_map.update(cx, |display_map, cx| {
5282 display_map.splice_inlays(to_remove, to_insert, cx)
5283 });
5284 cx.notify();
5285 }
5286
5287 fn trigger_on_type_formatting(
5288 &self,
5289 input: String,
5290 window: &mut Window,
5291 cx: &mut Context<Self>,
5292 ) -> Option<Task<Result<()>>> {
5293 if input.len() != 1 {
5294 return None;
5295 }
5296
5297 let project = self.project.as_ref()?;
5298 let position = self.selections.newest_anchor().head();
5299 let (buffer, buffer_position) = self
5300 .buffer
5301 .read(cx)
5302 .text_anchor_for_position(position, cx)?;
5303
5304 let settings = language_settings::language_settings(
5305 buffer
5306 .read(cx)
5307 .language_at(buffer_position)
5308 .map(|l| l.name()),
5309 buffer.read(cx).file(),
5310 cx,
5311 );
5312 if !settings.use_on_type_format {
5313 return None;
5314 }
5315
5316 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5317 // hence we do LSP request & edit on host side only — add formats to host's history.
5318 let push_to_lsp_host_history = true;
5319 // If this is not the host, append its history with new edits.
5320 let push_to_client_history = project.read(cx).is_via_collab();
5321
5322 let on_type_formatting = project.update(cx, |project, cx| {
5323 project.on_type_format(
5324 buffer.clone(),
5325 buffer_position,
5326 input,
5327 push_to_lsp_host_history,
5328 cx,
5329 )
5330 });
5331 Some(cx.spawn_in(window, async move |editor, cx| {
5332 if let Some(transaction) = on_type_formatting.await? {
5333 if push_to_client_history {
5334 buffer
5335 .update(cx, |buffer, _| {
5336 buffer.push_transaction(transaction, Instant::now());
5337 buffer.finalize_last_transaction();
5338 })
5339 .ok();
5340 }
5341 editor.update(cx, |editor, cx| {
5342 editor.refresh_document_highlights(cx);
5343 })?;
5344 }
5345 Ok(())
5346 }))
5347 }
5348
5349 pub fn show_word_completions(
5350 &mut self,
5351 _: &ShowWordCompletions,
5352 window: &mut Window,
5353 cx: &mut Context<Self>,
5354 ) {
5355 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5356 }
5357
5358 pub fn show_completions(
5359 &mut self,
5360 options: &ShowCompletions,
5361 window: &mut Window,
5362 cx: &mut Context<Self>,
5363 ) {
5364 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5365 }
5366
5367 fn open_or_update_completions_menu(
5368 &mut self,
5369 requested_source: Option<CompletionsMenuSource>,
5370 trigger: Option<&str>,
5371 window: &mut Window,
5372 cx: &mut Context<Self>,
5373 ) {
5374 if self.pending_rename.is_some() {
5375 return;
5376 }
5377
5378 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5379
5380 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5381 // inserted and selected. To handle that case, the start of the selection is used so that
5382 // the menu starts with all choices.
5383 let position = self
5384 .selections
5385 .newest_anchor()
5386 .start
5387 .bias_right(&multibuffer_snapshot);
5388 if position.diff_base_anchor.is_some() {
5389 return;
5390 }
5391 let (buffer, buffer_position) =
5392 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5393 output
5394 } else {
5395 return;
5396 };
5397 let buffer_snapshot = buffer.read(cx).snapshot();
5398
5399 let query: Option<Arc<String>> =
5400 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5401
5402 drop(multibuffer_snapshot);
5403
5404 let provider = match requested_source {
5405 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5406 Some(CompletionsMenuSource::Words) => None,
5407 Some(CompletionsMenuSource::SnippetChoices) => {
5408 log::error!("bug: SnippetChoices requested_source is not handled");
5409 None
5410 }
5411 };
5412
5413 let sort_completions = provider
5414 .as_ref()
5415 .map_or(false, |provider| provider.sort_completions());
5416
5417 let filter_completions = provider
5418 .as_ref()
5419 .map_or(true, |provider| provider.filter_completions());
5420
5421 let trigger_kind = match trigger {
5422 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5423 CompletionTriggerKind::TRIGGER_CHARACTER
5424 }
5425 _ => CompletionTriggerKind::INVOKED,
5426 };
5427 let completion_context = CompletionContext {
5428 trigger_character: trigger.and_then(|trigger| {
5429 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5430 Some(String::from(trigger))
5431 } else {
5432 None
5433 }
5434 }),
5435 trigger_kind,
5436 };
5437
5438 // Hide the current completions menu when a trigger char is typed. Without this, cached
5439 // completions from before the trigger char may be reused (#32774). Snippet choices could
5440 // involve trigger chars, so this is skipped in that case.
5441 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5442 {
5443 let menu_is_open = matches!(
5444 self.context_menu.borrow().as_ref(),
5445 Some(CodeContextMenu::Completions(_))
5446 );
5447 if menu_is_open {
5448 self.hide_context_menu(window, cx);
5449 }
5450 }
5451
5452 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5453 if filter_completions {
5454 menu.filter(query.clone(), provider.clone(), window, cx);
5455 }
5456 // When `is_incomplete` is false, no need to re-query completions when the current query
5457 // is a suffix of the initial query.
5458 if !menu.is_incomplete {
5459 // If the new query is a suffix of the old query (typing more characters) and
5460 // the previous result was complete, the existing completions can be filtered.
5461 //
5462 // Note that this is always true for snippet completions.
5463 let query_matches = match (&menu.initial_query, &query) {
5464 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5465 (None, _) => true,
5466 _ => false,
5467 };
5468 if query_matches {
5469 let position_matches = if menu.initial_position == position {
5470 true
5471 } else {
5472 let snapshot = self.buffer.read(cx).read(cx);
5473 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5474 };
5475 if position_matches {
5476 return;
5477 }
5478 }
5479 }
5480 };
5481
5482 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5483 buffer_snapshot.surrounding_word(buffer_position, false)
5484 {
5485 let word_to_exclude = buffer_snapshot
5486 .text_for_range(word_range.clone())
5487 .collect::<String>();
5488 (
5489 buffer_snapshot.anchor_before(word_range.start)
5490 ..buffer_snapshot.anchor_after(buffer_position),
5491 Some(word_to_exclude),
5492 )
5493 } else {
5494 (buffer_position..buffer_position, None)
5495 };
5496
5497 let language = buffer_snapshot
5498 .language_at(buffer_position)
5499 .map(|language| language.name());
5500
5501 let completion_settings =
5502 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5503
5504 let show_completion_documentation = buffer_snapshot
5505 .settings_at(buffer_position, cx)
5506 .show_completion_documentation;
5507
5508 // The document can be large, so stay in reasonable bounds when searching for words,
5509 // otherwise completion pop-up might be slow to appear.
5510 const WORD_LOOKUP_ROWS: u32 = 5_000;
5511 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5512 let min_word_search = buffer_snapshot.clip_point(
5513 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5514 Bias::Left,
5515 );
5516 let max_word_search = buffer_snapshot.clip_point(
5517 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5518 Bias::Right,
5519 );
5520 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5521 ..buffer_snapshot.point_to_offset(max_word_search);
5522
5523 let skip_digits = query
5524 .as_ref()
5525 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5526
5527 let (mut words, provider_responses) = match &provider {
5528 Some(provider) => {
5529 let provider_responses = provider.completions(
5530 position.excerpt_id,
5531 &buffer,
5532 buffer_position,
5533 completion_context,
5534 window,
5535 cx,
5536 );
5537
5538 let words = match completion_settings.words {
5539 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5540 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5541 .background_spawn(async move {
5542 buffer_snapshot.words_in_range(WordsQuery {
5543 fuzzy_contents: None,
5544 range: word_search_range,
5545 skip_digits,
5546 })
5547 }),
5548 };
5549
5550 (words, provider_responses)
5551 }
5552 None => (
5553 cx.background_spawn(async move {
5554 buffer_snapshot.words_in_range(WordsQuery {
5555 fuzzy_contents: None,
5556 range: word_search_range,
5557 skip_digits,
5558 })
5559 }),
5560 Task::ready(Ok(Vec::new())),
5561 ),
5562 };
5563
5564 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5565
5566 let id = post_inc(&mut self.next_completion_id);
5567 let task = cx.spawn_in(window, async move |editor, cx| {
5568 let Ok(()) = editor.update(cx, |this, _| {
5569 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5570 }) else {
5571 return;
5572 };
5573
5574 // TODO: Ideally completions from different sources would be selectively re-queried, so
5575 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5576 let mut completions = Vec::new();
5577 let mut is_incomplete = false;
5578 if let Some(provider_responses) = provider_responses.await.log_err() {
5579 if !provider_responses.is_empty() {
5580 for response in provider_responses {
5581 completions.extend(response.completions);
5582 is_incomplete = is_incomplete || response.is_incomplete;
5583 }
5584 if completion_settings.words == WordsCompletionMode::Fallback {
5585 words = Task::ready(BTreeMap::default());
5586 }
5587 }
5588 }
5589
5590 let mut words = words.await;
5591 if let Some(word_to_exclude) = &word_to_exclude {
5592 words.remove(word_to_exclude);
5593 }
5594 for lsp_completion in &completions {
5595 words.remove(&lsp_completion.new_text);
5596 }
5597 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5598 replace_range: word_replace_range.clone(),
5599 new_text: word.clone(),
5600 label: CodeLabel::plain(word, None),
5601 icon_path: None,
5602 documentation: None,
5603 source: CompletionSource::BufferWord {
5604 word_range,
5605 resolved: false,
5606 },
5607 insert_text_mode: Some(InsertTextMode::AS_IS),
5608 confirm: None,
5609 }));
5610
5611 let menu = if completions.is_empty() {
5612 None
5613 } else {
5614 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5615 let languages = editor
5616 .workspace
5617 .as_ref()
5618 .and_then(|(workspace, _)| workspace.upgrade())
5619 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5620 let menu = CompletionsMenu::new(
5621 id,
5622 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5623 sort_completions,
5624 show_completion_documentation,
5625 position,
5626 query.clone(),
5627 is_incomplete,
5628 buffer.clone(),
5629 completions.into(),
5630 snippet_sort_order,
5631 languages,
5632 language,
5633 cx,
5634 );
5635
5636 let query = if filter_completions { query } else { None };
5637 let matches_task = if let Some(query) = query {
5638 menu.do_async_filtering(query, cx)
5639 } else {
5640 Task::ready(menu.unfiltered_matches())
5641 };
5642 (menu, matches_task)
5643 }) else {
5644 return;
5645 };
5646
5647 let matches = matches_task.await;
5648
5649 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5650 // Newer menu already set, so exit.
5651 match editor.context_menu.borrow().as_ref() {
5652 Some(CodeContextMenu::Completions(prev_menu)) => {
5653 if prev_menu.id > id {
5654 return;
5655 }
5656 }
5657 _ => {}
5658 };
5659
5660 // Only valid to take prev_menu because it the new menu is immediately set
5661 // below, or the menu is hidden.
5662 match editor.context_menu.borrow_mut().take() {
5663 Some(CodeContextMenu::Completions(prev_menu)) => {
5664 let position_matches =
5665 if prev_menu.initial_position == menu.initial_position {
5666 true
5667 } else {
5668 let snapshot = editor.buffer.read(cx).read(cx);
5669 prev_menu.initial_position.to_offset(&snapshot)
5670 == menu.initial_position.to_offset(&snapshot)
5671 };
5672 if position_matches {
5673 // Preserve markdown cache before `set_filter_results` because it will
5674 // try to populate the documentation cache.
5675 menu.preserve_markdown_cache(prev_menu);
5676 }
5677 }
5678 _ => {}
5679 };
5680
5681 menu.set_filter_results(matches, provider, window, cx);
5682 }) else {
5683 return;
5684 };
5685
5686 menu.visible().then_some(menu)
5687 };
5688
5689 editor
5690 .update_in(cx, |editor, window, cx| {
5691 if editor.focus_handle.is_focused(window) {
5692 if let Some(menu) = menu {
5693 *editor.context_menu.borrow_mut() =
5694 Some(CodeContextMenu::Completions(menu));
5695
5696 crate::hover_popover::hide_hover(editor, cx);
5697 if editor.show_edit_predictions_in_menu() {
5698 editor.update_visible_edit_prediction(window, cx);
5699 } else {
5700 editor.discard_edit_prediction(false, cx);
5701 }
5702
5703 cx.notify();
5704 return;
5705 }
5706 }
5707
5708 if editor.completion_tasks.len() <= 1 {
5709 // If there are no more completion tasks and the last menu was empty, we should hide it.
5710 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5711 // If it was already hidden and we don't show edit predictions in the menu,
5712 // we should also show the edit prediction when available.
5713 if was_hidden && editor.show_edit_predictions_in_menu() {
5714 editor.update_visible_edit_prediction(window, cx);
5715 }
5716 }
5717 })
5718 .ok();
5719 });
5720
5721 self.completion_tasks.push((id, task));
5722 }
5723
5724 #[cfg(feature = "test-support")]
5725 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5726 let menu = self.context_menu.borrow();
5727 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5728 let completions = menu.completions.borrow();
5729 Some(completions.to_vec())
5730 } else {
5731 None
5732 }
5733 }
5734
5735 pub fn with_completions_menu_matching_id<R>(
5736 &self,
5737 id: CompletionId,
5738 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5739 ) -> R {
5740 let mut context_menu = self.context_menu.borrow_mut();
5741 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5742 return f(None);
5743 };
5744 if completions_menu.id != id {
5745 return f(None);
5746 }
5747 f(Some(completions_menu))
5748 }
5749
5750 pub fn confirm_completion(
5751 &mut self,
5752 action: &ConfirmCompletion,
5753 window: &mut Window,
5754 cx: &mut Context<Self>,
5755 ) -> Option<Task<Result<()>>> {
5756 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5757 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5758 }
5759
5760 pub fn confirm_completion_insert(
5761 &mut self,
5762 _: &ConfirmCompletionInsert,
5763 window: &mut Window,
5764 cx: &mut Context<Self>,
5765 ) -> Option<Task<Result<()>>> {
5766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5767 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5768 }
5769
5770 pub fn confirm_completion_replace(
5771 &mut self,
5772 _: &ConfirmCompletionReplace,
5773 window: &mut Window,
5774 cx: &mut Context<Self>,
5775 ) -> Option<Task<Result<()>>> {
5776 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5777 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5778 }
5779
5780 pub fn compose_completion(
5781 &mut self,
5782 action: &ComposeCompletion,
5783 window: &mut Window,
5784 cx: &mut Context<Self>,
5785 ) -> Option<Task<Result<()>>> {
5786 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5787 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5788 }
5789
5790 fn do_completion(
5791 &mut self,
5792 item_ix: Option<usize>,
5793 intent: CompletionIntent,
5794 window: &mut Window,
5795 cx: &mut Context<Editor>,
5796 ) -> Option<Task<Result<()>>> {
5797 use language::ToOffset as _;
5798
5799 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5800 else {
5801 return None;
5802 };
5803
5804 let candidate_id = {
5805 let entries = completions_menu.entries.borrow();
5806 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5807 if self.show_edit_predictions_in_menu() {
5808 self.discard_edit_prediction(true, cx);
5809 }
5810 mat.candidate_id
5811 };
5812
5813 let completion = completions_menu
5814 .completions
5815 .borrow()
5816 .get(candidate_id)?
5817 .clone();
5818 cx.stop_propagation();
5819
5820 let buffer_handle = completions_menu.buffer.clone();
5821
5822 let CompletionEdit {
5823 new_text,
5824 snippet,
5825 replace_range,
5826 } = process_completion_for_edit(
5827 &completion,
5828 intent,
5829 &buffer_handle,
5830 &completions_menu.initial_position.text_anchor,
5831 cx,
5832 );
5833
5834 let buffer = buffer_handle.read(cx);
5835 let snapshot = self.buffer.read(cx).snapshot(cx);
5836 let newest_anchor = self.selections.newest_anchor();
5837 let replace_range_multibuffer = {
5838 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5839 let multibuffer_anchor = snapshot
5840 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5841 .unwrap()
5842 ..snapshot
5843 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5844 .unwrap();
5845 multibuffer_anchor.start.to_offset(&snapshot)
5846 ..multibuffer_anchor.end.to_offset(&snapshot)
5847 };
5848 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5849 return None;
5850 }
5851
5852 let old_text = buffer
5853 .text_for_range(replace_range.clone())
5854 .collect::<String>();
5855 let lookbehind = newest_anchor
5856 .start
5857 .text_anchor
5858 .to_offset(buffer)
5859 .saturating_sub(replace_range.start);
5860 let lookahead = replace_range
5861 .end
5862 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5863 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5864 let suffix = &old_text[lookbehind.min(old_text.len())..];
5865
5866 let selections = self.selections.all::<usize>(cx);
5867 let mut ranges = Vec::new();
5868 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5869
5870 for selection in &selections {
5871 let range = if selection.id == newest_anchor.id {
5872 replace_range_multibuffer.clone()
5873 } else {
5874 let mut range = selection.range();
5875
5876 // if prefix is present, don't duplicate it
5877 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5878 range.start = range.start.saturating_sub(lookbehind);
5879
5880 // if suffix is also present, mimic the newest cursor and replace it
5881 if selection.id != newest_anchor.id
5882 && snapshot.contains_str_at(range.end, suffix)
5883 {
5884 range.end += lookahead;
5885 }
5886 }
5887 range
5888 };
5889
5890 ranges.push(range.clone());
5891
5892 if !self.linked_edit_ranges.is_empty() {
5893 let start_anchor = snapshot.anchor_before(range.start);
5894 let end_anchor = snapshot.anchor_after(range.end);
5895 if let Some(ranges) = self
5896 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5897 {
5898 for (buffer, edits) in ranges {
5899 linked_edits
5900 .entry(buffer.clone())
5901 .or_default()
5902 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5903 }
5904 }
5905 }
5906 }
5907
5908 let common_prefix_len = old_text
5909 .chars()
5910 .zip(new_text.chars())
5911 .take_while(|(a, b)| a == b)
5912 .map(|(a, _)| a.len_utf8())
5913 .sum::<usize>();
5914
5915 cx.emit(EditorEvent::InputHandled {
5916 utf16_range_to_replace: None,
5917 text: new_text[common_prefix_len..].into(),
5918 });
5919
5920 self.transact(window, cx, |editor, window, cx| {
5921 if let Some(mut snippet) = snippet {
5922 snippet.text = new_text.to_string();
5923 editor
5924 .insert_snippet(&ranges, snippet, window, cx)
5925 .log_err();
5926 } else {
5927 editor.buffer.update(cx, |multi_buffer, cx| {
5928 let auto_indent = match completion.insert_text_mode {
5929 Some(InsertTextMode::AS_IS) => None,
5930 _ => editor.autoindent_mode.clone(),
5931 };
5932 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5933 multi_buffer.edit(edits, auto_indent, cx);
5934 });
5935 }
5936 for (buffer, edits) in linked_edits {
5937 buffer.update(cx, |buffer, cx| {
5938 let snapshot = buffer.snapshot();
5939 let edits = edits
5940 .into_iter()
5941 .map(|(range, text)| {
5942 use text::ToPoint as TP;
5943 let end_point = TP::to_point(&range.end, &snapshot);
5944 let start_point = TP::to_point(&range.start, &snapshot);
5945 (start_point..end_point, text)
5946 })
5947 .sorted_by_key(|(range, _)| range.start);
5948 buffer.edit(edits, None, cx);
5949 })
5950 }
5951
5952 editor.refresh_edit_prediction(true, false, window, cx);
5953 });
5954 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5955
5956 let show_new_completions_on_confirm = completion
5957 .confirm
5958 .as_ref()
5959 .map_or(false, |confirm| confirm(intent, window, cx));
5960 if show_new_completions_on_confirm {
5961 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5962 }
5963
5964 let provider = self.completion_provider.as_ref()?;
5965 drop(completion);
5966 let apply_edits = provider.apply_additional_edits_for_completion(
5967 buffer_handle,
5968 completions_menu.completions.clone(),
5969 candidate_id,
5970 true,
5971 cx,
5972 );
5973
5974 let editor_settings = EditorSettings::get_global(cx);
5975 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5976 // After the code completion is finished, users often want to know what signatures are needed.
5977 // so we should automatically call signature_help
5978 self.show_signature_help(&ShowSignatureHelp, window, cx);
5979 }
5980
5981 Some(cx.foreground_executor().spawn(async move {
5982 apply_edits.await?;
5983 Ok(())
5984 }))
5985 }
5986
5987 pub fn toggle_code_actions(
5988 &mut self,
5989 action: &ToggleCodeActions,
5990 window: &mut Window,
5991 cx: &mut Context<Self>,
5992 ) {
5993 let quick_launch = action.quick_launch;
5994 let mut context_menu = self.context_menu.borrow_mut();
5995 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5996 if code_actions.deployed_from == action.deployed_from {
5997 // Toggle if we're selecting the same one
5998 *context_menu = None;
5999 cx.notify();
6000 return;
6001 } else {
6002 // Otherwise, clear it and start a new one
6003 *context_menu = None;
6004 cx.notify();
6005 }
6006 }
6007 drop(context_menu);
6008 let snapshot = self.snapshot(window, cx);
6009 let deployed_from = action.deployed_from.clone();
6010 let action = action.clone();
6011 self.completion_tasks.clear();
6012 self.discard_edit_prediction(false, cx);
6013
6014 let multibuffer_point = match &action.deployed_from {
6015 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6016 DisplayPoint::new(*row, 0).to_point(&snapshot)
6017 }
6018 _ => self.selections.newest::<Point>(cx).head(),
6019 };
6020 let Some((buffer, buffer_row)) = snapshot
6021 .buffer_snapshot
6022 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6023 .and_then(|(buffer_snapshot, range)| {
6024 self.buffer()
6025 .read(cx)
6026 .buffer(buffer_snapshot.remote_id())
6027 .map(|buffer| (buffer, range.start.row))
6028 })
6029 else {
6030 return;
6031 };
6032 let buffer_id = buffer.read(cx).remote_id();
6033 let tasks = self
6034 .tasks
6035 .get(&(buffer_id, buffer_row))
6036 .map(|t| Arc::new(t.to_owned()));
6037
6038 if !self.focus_handle.is_focused(window) {
6039 return;
6040 }
6041 let project = self.project.clone();
6042
6043 let code_actions_task = match deployed_from {
6044 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6045 _ => self.code_actions(buffer_row, window, cx),
6046 };
6047
6048 let runnable_task = match deployed_from {
6049 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6050 _ => {
6051 let mut task_context_task = Task::ready(None);
6052 if let Some(tasks) = &tasks {
6053 if let Some(project) = project {
6054 task_context_task =
6055 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6056 }
6057 }
6058
6059 cx.spawn_in(window, {
6060 let buffer = buffer.clone();
6061 async move |editor, cx| {
6062 let task_context = task_context_task.await;
6063
6064 let resolved_tasks =
6065 tasks
6066 .zip(task_context.clone())
6067 .map(|(tasks, task_context)| ResolvedTasks {
6068 templates: tasks.resolve(&task_context).collect(),
6069 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6070 multibuffer_point.row,
6071 tasks.column,
6072 )),
6073 });
6074 let debug_scenarios = editor
6075 .update(cx, |editor, cx| {
6076 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6077 })?
6078 .await;
6079 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6080 }
6081 })
6082 }
6083 };
6084
6085 cx.spawn_in(window, async move |editor, cx| {
6086 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6087 let code_actions = code_actions_task.await;
6088 let spawn_straight_away = quick_launch
6089 && resolved_tasks
6090 .as_ref()
6091 .map_or(false, |tasks| tasks.templates.len() == 1)
6092 && code_actions
6093 .as_ref()
6094 .map_or(true, |actions| actions.is_empty())
6095 && debug_scenarios.is_empty();
6096
6097 editor.update_in(cx, |editor, window, cx| {
6098 crate::hover_popover::hide_hover(editor, cx);
6099 let actions = CodeActionContents::new(
6100 resolved_tasks,
6101 code_actions,
6102 debug_scenarios,
6103 task_context.unwrap_or_default(),
6104 );
6105
6106 // Don't show the menu if there are no actions available
6107 if actions.is_empty() {
6108 cx.notify();
6109 return Task::ready(Ok(()));
6110 }
6111
6112 *editor.context_menu.borrow_mut() =
6113 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6114 buffer,
6115 actions,
6116 selected_item: Default::default(),
6117 scroll_handle: UniformListScrollHandle::default(),
6118 deployed_from,
6119 }));
6120 cx.notify();
6121 if spawn_straight_away {
6122 if let Some(task) = editor.confirm_code_action(
6123 &ConfirmCodeAction { item_ix: Some(0) },
6124 window,
6125 cx,
6126 ) {
6127 return task;
6128 }
6129 }
6130
6131 Task::ready(Ok(()))
6132 })
6133 })
6134 .detach_and_log_err(cx);
6135 }
6136
6137 fn debug_scenarios(
6138 &mut self,
6139 resolved_tasks: &Option<ResolvedTasks>,
6140 buffer: &Entity<Buffer>,
6141 cx: &mut App,
6142 ) -> Task<Vec<task::DebugScenario>> {
6143 maybe!({
6144 let project = self.project.as_ref()?;
6145 let dap_store = project.read(cx).dap_store();
6146 let mut scenarios = vec![];
6147 let resolved_tasks = resolved_tasks.as_ref()?;
6148 let buffer = buffer.read(cx);
6149 let language = buffer.language()?;
6150 let file = buffer.file();
6151 let debug_adapter = language_settings(language.name().into(), file, cx)
6152 .debuggers
6153 .first()
6154 .map(SharedString::from)
6155 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6156
6157 dap_store.update(cx, |dap_store, cx| {
6158 for (_, task) in &resolved_tasks.templates {
6159 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6160 task.original_task().clone(),
6161 debug_adapter.clone().into(),
6162 task.display_label().to_owned().into(),
6163 cx,
6164 );
6165 scenarios.push(maybe_scenario);
6166 }
6167 });
6168 Some(cx.background_spawn(async move {
6169 let scenarios = futures::future::join_all(scenarios)
6170 .await
6171 .into_iter()
6172 .flatten()
6173 .collect::<Vec<_>>();
6174 scenarios
6175 }))
6176 })
6177 .unwrap_or_else(|| Task::ready(vec![]))
6178 }
6179
6180 fn code_actions(
6181 &mut self,
6182 buffer_row: u32,
6183 window: &mut Window,
6184 cx: &mut Context<Self>,
6185 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6186 let mut task = self.code_actions_task.take();
6187 cx.spawn_in(window, async move |editor, cx| {
6188 while let Some(prev_task) = task {
6189 prev_task.await.log_err();
6190 task = editor
6191 .update(cx, |this, _| this.code_actions_task.take())
6192 .ok()?;
6193 }
6194
6195 editor
6196 .update(cx, |editor, cx| {
6197 editor
6198 .available_code_actions
6199 .clone()
6200 .and_then(|(location, code_actions)| {
6201 let snapshot = location.buffer.read(cx).snapshot();
6202 let point_range = location.range.to_point(&snapshot);
6203 let point_range = point_range.start.row..=point_range.end.row;
6204 if point_range.contains(&buffer_row) {
6205 Some(code_actions)
6206 } else {
6207 None
6208 }
6209 })
6210 })
6211 .ok()
6212 .flatten()
6213 })
6214 }
6215
6216 pub fn confirm_code_action(
6217 &mut self,
6218 action: &ConfirmCodeAction,
6219 window: &mut Window,
6220 cx: &mut Context<Self>,
6221 ) -> Option<Task<Result<()>>> {
6222 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6223
6224 let actions_menu =
6225 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6226 menu
6227 } else {
6228 return None;
6229 };
6230
6231 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6232 let action = actions_menu.actions.get(action_ix)?;
6233 let title = action.label();
6234 let buffer = actions_menu.buffer;
6235 let workspace = self.workspace()?;
6236
6237 match action {
6238 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6239 workspace.update(cx, |workspace, cx| {
6240 workspace.schedule_resolved_task(
6241 task_source_kind,
6242 resolved_task,
6243 false,
6244 window,
6245 cx,
6246 );
6247
6248 Some(Task::ready(Ok(())))
6249 })
6250 }
6251 CodeActionsItem::CodeAction {
6252 excerpt_id,
6253 action,
6254 provider,
6255 } => {
6256 let apply_code_action =
6257 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6258 let workspace = workspace.downgrade();
6259 Some(cx.spawn_in(window, async move |editor, cx| {
6260 let project_transaction = apply_code_action.await?;
6261 Self::open_project_transaction(
6262 &editor,
6263 workspace,
6264 project_transaction,
6265 title,
6266 cx,
6267 )
6268 .await
6269 }))
6270 }
6271 CodeActionsItem::DebugScenario(scenario) => {
6272 let context = actions_menu.actions.context.clone();
6273
6274 workspace.update(cx, |workspace, cx| {
6275 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6276 workspace.start_debug_session(
6277 scenario,
6278 context,
6279 Some(buffer),
6280 None,
6281 window,
6282 cx,
6283 );
6284 });
6285 Some(Task::ready(Ok(())))
6286 }
6287 }
6288 }
6289
6290 pub async fn open_project_transaction(
6291 this: &WeakEntity<Editor>,
6292 workspace: WeakEntity<Workspace>,
6293 transaction: ProjectTransaction,
6294 title: String,
6295 cx: &mut AsyncWindowContext,
6296 ) -> Result<()> {
6297 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6298 cx.update(|_, cx| {
6299 entries.sort_unstable_by_key(|(buffer, _)| {
6300 buffer.read(cx).file().map(|f| f.path().clone())
6301 });
6302 })?;
6303
6304 // If the project transaction's edits are all contained within this editor, then
6305 // avoid opening a new editor to display them.
6306
6307 if let Some((buffer, transaction)) = entries.first() {
6308 if entries.len() == 1 {
6309 let excerpt = this.update(cx, |editor, cx| {
6310 editor
6311 .buffer()
6312 .read(cx)
6313 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6314 })?;
6315 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6316 if excerpted_buffer == *buffer {
6317 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6318 let excerpt_range = excerpt_range.to_offset(buffer);
6319 buffer
6320 .edited_ranges_for_transaction::<usize>(transaction)
6321 .all(|range| {
6322 excerpt_range.start <= range.start
6323 && excerpt_range.end >= range.end
6324 })
6325 })?;
6326
6327 if all_edits_within_excerpt {
6328 return Ok(());
6329 }
6330 }
6331 }
6332 }
6333 } else {
6334 return Ok(());
6335 }
6336
6337 let mut ranges_to_highlight = Vec::new();
6338 let excerpt_buffer = cx.new(|cx| {
6339 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6340 for (buffer_handle, transaction) in &entries {
6341 let edited_ranges = buffer_handle
6342 .read(cx)
6343 .edited_ranges_for_transaction::<Point>(transaction)
6344 .collect::<Vec<_>>();
6345 let (ranges, _) = multibuffer.set_excerpts_for_path(
6346 PathKey::for_buffer(buffer_handle, cx),
6347 buffer_handle.clone(),
6348 edited_ranges,
6349 DEFAULT_MULTIBUFFER_CONTEXT,
6350 cx,
6351 );
6352
6353 ranges_to_highlight.extend(ranges);
6354 }
6355 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6356 multibuffer
6357 })?;
6358
6359 workspace.update_in(cx, |workspace, window, cx| {
6360 let project = workspace.project().clone();
6361 let editor =
6362 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6363 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6364 editor.update(cx, |editor, cx| {
6365 editor.highlight_background::<Self>(
6366 &ranges_to_highlight,
6367 |theme| theme.colors().editor_highlighted_line_background,
6368 cx,
6369 );
6370 });
6371 })?;
6372
6373 Ok(())
6374 }
6375
6376 pub fn clear_code_action_providers(&mut self) {
6377 self.code_action_providers.clear();
6378 self.available_code_actions.take();
6379 }
6380
6381 pub fn add_code_action_provider(
6382 &mut self,
6383 provider: Rc<dyn CodeActionProvider>,
6384 window: &mut Window,
6385 cx: &mut Context<Self>,
6386 ) {
6387 if self
6388 .code_action_providers
6389 .iter()
6390 .any(|existing_provider| existing_provider.id() == provider.id())
6391 {
6392 return;
6393 }
6394
6395 self.code_action_providers.push(provider);
6396 self.refresh_code_actions(window, cx);
6397 }
6398
6399 pub fn remove_code_action_provider(
6400 &mut self,
6401 id: Arc<str>,
6402 window: &mut Window,
6403 cx: &mut Context<Self>,
6404 ) {
6405 self.code_action_providers
6406 .retain(|provider| provider.id() != id);
6407 self.refresh_code_actions(window, cx);
6408 }
6409
6410 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6411 !self.code_action_providers.is_empty()
6412 && EditorSettings::get_global(cx).toolbar.code_actions
6413 }
6414
6415 pub fn has_available_code_actions(&self) -> bool {
6416 self.available_code_actions
6417 .as_ref()
6418 .is_some_and(|(_, actions)| !actions.is_empty())
6419 }
6420
6421 fn render_inline_code_actions(
6422 &self,
6423 icon_size: ui::IconSize,
6424 display_row: DisplayRow,
6425 is_active: bool,
6426 cx: &mut Context<Self>,
6427 ) -> AnyElement {
6428 let show_tooltip = !self.context_menu_visible();
6429 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6430 .icon_size(icon_size)
6431 .shape(ui::IconButtonShape::Square)
6432 .icon_color(ui::Color::Hidden)
6433 .toggle_state(is_active)
6434 .when(show_tooltip, |this| {
6435 this.tooltip({
6436 let focus_handle = self.focus_handle.clone();
6437 move |window, cx| {
6438 Tooltip::for_action_in(
6439 "Toggle Code Actions",
6440 &ToggleCodeActions {
6441 deployed_from: None,
6442 quick_launch: false,
6443 },
6444 &focus_handle,
6445 window,
6446 cx,
6447 )
6448 }
6449 })
6450 })
6451 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6452 window.focus(&editor.focus_handle(cx));
6453 editor.toggle_code_actions(
6454 &crate::actions::ToggleCodeActions {
6455 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6456 display_row,
6457 )),
6458 quick_launch: false,
6459 },
6460 window,
6461 cx,
6462 );
6463 }))
6464 .into_any_element()
6465 }
6466
6467 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6468 &self.context_menu
6469 }
6470
6471 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6472 let newest_selection = self.selections.newest_anchor().clone();
6473 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6474 let buffer = self.buffer.read(cx);
6475 if newest_selection.head().diff_base_anchor.is_some() {
6476 return None;
6477 }
6478 let (start_buffer, start) =
6479 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6480 let (end_buffer, end) =
6481 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6482 if start_buffer != end_buffer {
6483 return None;
6484 }
6485
6486 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6487 cx.background_executor()
6488 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6489 .await;
6490
6491 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6492 let providers = this.code_action_providers.clone();
6493 let tasks = this
6494 .code_action_providers
6495 .iter()
6496 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6497 .collect::<Vec<_>>();
6498 (providers, tasks)
6499 })?;
6500
6501 let mut actions = Vec::new();
6502 for (provider, provider_actions) in
6503 providers.into_iter().zip(future::join_all(tasks).await)
6504 {
6505 if let Some(provider_actions) = provider_actions.log_err() {
6506 actions.extend(provider_actions.into_iter().map(|action| {
6507 AvailableCodeAction {
6508 excerpt_id: newest_selection.start.excerpt_id,
6509 action,
6510 provider: provider.clone(),
6511 }
6512 }));
6513 }
6514 }
6515
6516 this.update(cx, |this, cx| {
6517 this.available_code_actions = if actions.is_empty() {
6518 None
6519 } else {
6520 Some((
6521 Location {
6522 buffer: start_buffer,
6523 range: start..end,
6524 },
6525 actions.into(),
6526 ))
6527 };
6528 cx.notify();
6529 })
6530 }));
6531 None
6532 }
6533
6534 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6535 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6536 self.show_git_blame_inline = false;
6537
6538 self.show_git_blame_inline_delay_task =
6539 Some(cx.spawn_in(window, async move |this, cx| {
6540 cx.background_executor().timer(delay).await;
6541
6542 this.update(cx, |this, cx| {
6543 this.show_git_blame_inline = true;
6544 cx.notify();
6545 })
6546 .log_err();
6547 }));
6548 }
6549 }
6550
6551 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6552 let snapshot = self.snapshot(window, cx);
6553 let cursor = self.selections.newest::<Point>(cx).head();
6554 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6555 else {
6556 return;
6557 };
6558
6559 let Some(blame) = self.blame.as_ref() else {
6560 return;
6561 };
6562
6563 let row_info = RowInfo {
6564 buffer_id: Some(buffer.remote_id()),
6565 buffer_row: Some(point.row),
6566 ..Default::default()
6567 };
6568 let Some(blame_entry) = blame
6569 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6570 .flatten()
6571 else {
6572 return;
6573 };
6574
6575 let anchor = self.selections.newest_anchor().head();
6576 let position = self.to_pixel_point(anchor, &snapshot, window);
6577 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6578 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6579 };
6580 }
6581
6582 fn show_blame_popover(
6583 &mut self,
6584 blame_entry: &BlameEntry,
6585 position: gpui::Point<Pixels>,
6586 ignore_timeout: bool,
6587 cx: &mut Context<Self>,
6588 ) {
6589 if let Some(state) = &mut self.inline_blame_popover {
6590 state.hide_task.take();
6591 } else {
6592 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6593 let blame_entry = blame_entry.clone();
6594 let show_task = cx.spawn(async move |editor, cx| {
6595 if !ignore_timeout {
6596 cx.background_executor()
6597 .timer(std::time::Duration::from_millis(blame_popover_delay))
6598 .await;
6599 }
6600 editor
6601 .update(cx, |editor, cx| {
6602 editor.inline_blame_popover_show_task.take();
6603 let Some(blame) = editor.blame.as_ref() else {
6604 return;
6605 };
6606 let blame = blame.read(cx);
6607 let details = blame.details_for_entry(&blame_entry);
6608 let markdown = cx.new(|cx| {
6609 Markdown::new(
6610 details
6611 .as_ref()
6612 .map(|message| message.message.clone())
6613 .unwrap_or_default(),
6614 None,
6615 None,
6616 cx,
6617 )
6618 });
6619 editor.inline_blame_popover = Some(InlineBlamePopover {
6620 position,
6621 hide_task: None,
6622 popover_bounds: None,
6623 popover_state: InlineBlamePopoverState {
6624 scroll_handle: ScrollHandle::new(),
6625 commit_message: details,
6626 markdown,
6627 },
6628 keyboard_grace: ignore_timeout,
6629 });
6630 cx.notify();
6631 })
6632 .ok();
6633 });
6634 self.inline_blame_popover_show_task = Some(show_task);
6635 }
6636 }
6637
6638 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6639 self.inline_blame_popover_show_task.take();
6640 if let Some(state) = &mut self.inline_blame_popover {
6641 let hide_task = cx.spawn(async move |editor, cx| {
6642 cx.background_executor()
6643 .timer(std::time::Duration::from_millis(100))
6644 .await;
6645 editor
6646 .update(cx, |editor, cx| {
6647 editor.inline_blame_popover.take();
6648 cx.notify();
6649 })
6650 .ok();
6651 });
6652 state.hide_task = Some(hide_task);
6653 }
6654 }
6655
6656 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6657 if self.pending_rename.is_some() {
6658 return None;
6659 }
6660
6661 let provider = self.semantics_provider.clone()?;
6662 let buffer = self.buffer.read(cx);
6663 let newest_selection = self.selections.newest_anchor().clone();
6664 let cursor_position = newest_selection.head();
6665 let (cursor_buffer, cursor_buffer_position) =
6666 buffer.text_anchor_for_position(cursor_position, cx)?;
6667 let (tail_buffer, tail_buffer_position) =
6668 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6669 if cursor_buffer != tail_buffer {
6670 return None;
6671 }
6672
6673 let snapshot = cursor_buffer.read(cx).snapshot();
6674 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6675 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6676 if start_word_range != end_word_range {
6677 self.document_highlights_task.take();
6678 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6679 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6680 return None;
6681 }
6682
6683 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6684 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6685 cx.background_executor()
6686 .timer(Duration::from_millis(debounce))
6687 .await;
6688
6689 let highlights = if let Some(highlights) = cx
6690 .update(|cx| {
6691 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6692 })
6693 .ok()
6694 .flatten()
6695 {
6696 highlights.await.log_err()
6697 } else {
6698 None
6699 };
6700
6701 if let Some(highlights) = highlights {
6702 this.update(cx, |this, cx| {
6703 if this.pending_rename.is_some() {
6704 return;
6705 }
6706
6707 let buffer_id = cursor_position.buffer_id;
6708 let buffer = this.buffer.read(cx);
6709 if !buffer
6710 .text_anchor_for_position(cursor_position, cx)
6711 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6712 {
6713 return;
6714 }
6715
6716 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6717 let mut write_ranges = Vec::new();
6718 let mut read_ranges = Vec::new();
6719 for highlight in highlights {
6720 for (excerpt_id, excerpt_range) in
6721 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6722 {
6723 let start = highlight
6724 .range
6725 .start
6726 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6727 let end = highlight
6728 .range
6729 .end
6730 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6731 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6732 continue;
6733 }
6734
6735 let range = Anchor {
6736 buffer_id,
6737 excerpt_id,
6738 text_anchor: start,
6739 diff_base_anchor: None,
6740 }..Anchor {
6741 buffer_id,
6742 excerpt_id,
6743 text_anchor: end,
6744 diff_base_anchor: None,
6745 };
6746 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6747 write_ranges.push(range);
6748 } else {
6749 read_ranges.push(range);
6750 }
6751 }
6752 }
6753
6754 this.highlight_background::<DocumentHighlightRead>(
6755 &read_ranges,
6756 |theme| theme.colors().editor_document_highlight_read_background,
6757 cx,
6758 );
6759 this.highlight_background::<DocumentHighlightWrite>(
6760 &write_ranges,
6761 |theme| theme.colors().editor_document_highlight_write_background,
6762 cx,
6763 );
6764 cx.notify();
6765 })
6766 .log_err();
6767 }
6768 }));
6769 None
6770 }
6771
6772 fn prepare_highlight_query_from_selection(
6773 &mut self,
6774 cx: &mut Context<Editor>,
6775 ) -> Option<(String, Range<Anchor>)> {
6776 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6777 return None;
6778 }
6779 if !EditorSettings::get_global(cx).selection_highlight {
6780 return None;
6781 }
6782 if self.selections.count() != 1 || self.selections.line_mode {
6783 return None;
6784 }
6785 let selection = self.selections.newest::<Point>(cx);
6786 if selection.is_empty() || selection.start.row != selection.end.row {
6787 return None;
6788 }
6789 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6790 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6791 let query = multi_buffer_snapshot
6792 .text_for_range(selection_anchor_range.clone())
6793 .collect::<String>();
6794 if query.trim().is_empty() {
6795 return None;
6796 }
6797 Some((query, selection_anchor_range))
6798 }
6799
6800 fn update_selection_occurrence_highlights(
6801 &mut self,
6802 query_text: String,
6803 query_range: Range<Anchor>,
6804 multi_buffer_range_to_query: Range<Point>,
6805 use_debounce: bool,
6806 window: &mut Window,
6807 cx: &mut Context<Editor>,
6808 ) -> Task<()> {
6809 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6810 cx.spawn_in(window, async move |editor, cx| {
6811 if use_debounce {
6812 cx.background_executor()
6813 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6814 .await;
6815 }
6816 let match_task = cx.background_spawn(async move {
6817 let buffer_ranges = multi_buffer_snapshot
6818 .range_to_buffer_ranges(multi_buffer_range_to_query)
6819 .into_iter()
6820 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6821 let mut match_ranges = Vec::new();
6822 let Ok(regex) = project::search::SearchQuery::text(
6823 query_text.clone(),
6824 false,
6825 false,
6826 false,
6827 Default::default(),
6828 Default::default(),
6829 false,
6830 None,
6831 ) else {
6832 return Vec::default();
6833 };
6834 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6835 match_ranges.extend(
6836 regex
6837 .search(&buffer_snapshot, Some(search_range.clone()))
6838 .await
6839 .into_iter()
6840 .filter_map(|match_range| {
6841 let match_start = buffer_snapshot
6842 .anchor_after(search_range.start + match_range.start);
6843 let match_end = buffer_snapshot
6844 .anchor_before(search_range.start + match_range.end);
6845 let match_anchor_range = Anchor::range_in_buffer(
6846 excerpt_id,
6847 buffer_snapshot.remote_id(),
6848 match_start..match_end,
6849 );
6850 (match_anchor_range != query_range).then_some(match_anchor_range)
6851 }),
6852 );
6853 }
6854 match_ranges
6855 });
6856 let match_ranges = match_task.await;
6857 editor
6858 .update_in(cx, |editor, _, cx| {
6859 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6860 if !match_ranges.is_empty() {
6861 editor.highlight_background::<SelectedTextHighlight>(
6862 &match_ranges,
6863 |theme| theme.colors().editor_document_highlight_bracket_background,
6864 cx,
6865 )
6866 }
6867 })
6868 .log_err();
6869 })
6870 }
6871
6872 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6873 struct NewlineFold;
6874 let type_id = std::any::TypeId::of::<NewlineFold>();
6875 if !self.mode.is_single_line() {
6876 return;
6877 }
6878 let snapshot = self.snapshot(window, cx);
6879 if snapshot.buffer_snapshot.max_point().row == 0 {
6880 return;
6881 }
6882 let task = cx.background_spawn(async move {
6883 let new_newlines = snapshot
6884 .buffer_chars_at(0)
6885 .filter_map(|(c, i)| {
6886 if c == '\n' {
6887 Some(
6888 snapshot.buffer_snapshot.anchor_after(i)
6889 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6890 )
6891 } else {
6892 None
6893 }
6894 })
6895 .collect::<Vec<_>>();
6896 let existing_newlines = snapshot
6897 .folds_in_range(0..snapshot.buffer_snapshot.len())
6898 .filter_map(|fold| {
6899 if fold.placeholder.type_tag == Some(type_id) {
6900 Some(fold.range.start..fold.range.end)
6901 } else {
6902 None
6903 }
6904 })
6905 .collect::<Vec<_>>();
6906
6907 (new_newlines, existing_newlines)
6908 });
6909 self.folding_newlines = cx.spawn(async move |this, cx| {
6910 let (new_newlines, existing_newlines) = task.await;
6911 if new_newlines == existing_newlines {
6912 return;
6913 }
6914 let placeholder = FoldPlaceholder {
6915 render: Arc::new(move |_, _, cx| {
6916 div()
6917 .bg(cx.theme().status().hint_background)
6918 .border_b_1()
6919 .size_full()
6920 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6921 .border_color(cx.theme().status().hint)
6922 .child("\\n")
6923 .into_any()
6924 }),
6925 constrain_width: false,
6926 merge_adjacent: false,
6927 type_tag: Some(type_id),
6928 };
6929 let creases = new_newlines
6930 .into_iter()
6931 .map(|range| Crease::simple(range, placeholder.clone()))
6932 .collect();
6933 this.update(cx, |this, cx| {
6934 this.display_map.update(cx, |display_map, cx| {
6935 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6936 display_map.fold(creases, cx);
6937 });
6938 })
6939 .ok();
6940 });
6941 }
6942
6943 fn refresh_selected_text_highlights(
6944 &mut self,
6945 on_buffer_edit: bool,
6946 window: &mut Window,
6947 cx: &mut Context<Editor>,
6948 ) {
6949 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6950 else {
6951 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6952 self.quick_selection_highlight_task.take();
6953 self.debounced_selection_highlight_task.take();
6954 return;
6955 };
6956 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6957 if on_buffer_edit
6958 || self
6959 .quick_selection_highlight_task
6960 .as_ref()
6961 .map_or(true, |(prev_anchor_range, _)| {
6962 prev_anchor_range != &query_range
6963 })
6964 {
6965 let multi_buffer_visible_start = self
6966 .scroll_manager
6967 .anchor()
6968 .anchor
6969 .to_point(&multi_buffer_snapshot);
6970 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6971 multi_buffer_visible_start
6972 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6973 Bias::Left,
6974 );
6975 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6976 self.quick_selection_highlight_task = Some((
6977 query_range.clone(),
6978 self.update_selection_occurrence_highlights(
6979 query_text.clone(),
6980 query_range.clone(),
6981 multi_buffer_visible_range,
6982 false,
6983 window,
6984 cx,
6985 ),
6986 ));
6987 }
6988 if on_buffer_edit
6989 || self
6990 .debounced_selection_highlight_task
6991 .as_ref()
6992 .map_or(true, |(prev_anchor_range, _)| {
6993 prev_anchor_range != &query_range
6994 })
6995 {
6996 let multi_buffer_start = multi_buffer_snapshot
6997 .anchor_before(0)
6998 .to_point(&multi_buffer_snapshot);
6999 let multi_buffer_end = multi_buffer_snapshot
7000 .anchor_after(multi_buffer_snapshot.len())
7001 .to_point(&multi_buffer_snapshot);
7002 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7003 self.debounced_selection_highlight_task = Some((
7004 query_range.clone(),
7005 self.update_selection_occurrence_highlights(
7006 query_text,
7007 query_range,
7008 multi_buffer_full_range,
7009 true,
7010 window,
7011 cx,
7012 ),
7013 ));
7014 }
7015 }
7016
7017 pub fn refresh_edit_prediction(
7018 &mut self,
7019 debounce: bool,
7020 user_requested: bool,
7021 window: &mut Window,
7022 cx: &mut Context<Self>,
7023 ) -> Option<()> {
7024 if DisableAiSettings::get_global(cx).disable_ai {
7025 return None;
7026 }
7027
7028 let provider = self.edit_prediction_provider()?;
7029 let cursor = self.selections.newest_anchor().head();
7030 let (buffer, cursor_buffer_position) =
7031 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7032
7033 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7034 self.discard_edit_prediction(false, cx);
7035 return None;
7036 }
7037
7038 if !user_requested
7039 && (!self.should_show_edit_predictions()
7040 || !self.is_focused(window)
7041 || buffer.read(cx).is_empty())
7042 {
7043 self.discard_edit_prediction(false, cx);
7044 return None;
7045 }
7046
7047 self.update_visible_edit_prediction(window, cx);
7048 provider.refresh(
7049 self.project.clone(),
7050 buffer,
7051 cursor_buffer_position,
7052 debounce,
7053 cx,
7054 );
7055 Some(())
7056 }
7057
7058 fn show_edit_predictions_in_menu(&self) -> bool {
7059 match self.edit_prediction_settings {
7060 EditPredictionSettings::Disabled => false,
7061 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7062 }
7063 }
7064
7065 pub fn edit_predictions_enabled(&self) -> bool {
7066 match self.edit_prediction_settings {
7067 EditPredictionSettings::Disabled => false,
7068 EditPredictionSettings::Enabled { .. } => true,
7069 }
7070 }
7071
7072 fn edit_prediction_requires_modifier(&self) -> bool {
7073 match self.edit_prediction_settings {
7074 EditPredictionSettings::Disabled => false,
7075 EditPredictionSettings::Enabled {
7076 preview_requires_modifier,
7077 ..
7078 } => preview_requires_modifier,
7079 }
7080 }
7081
7082 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7083 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7084 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7085 self.discard_edit_prediction(false, cx);
7086 } else {
7087 let selection = self.selections.newest_anchor();
7088 let cursor = selection.head();
7089
7090 if let Some((buffer, cursor_buffer_position)) =
7091 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7092 {
7093 self.edit_prediction_settings =
7094 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7095 }
7096 }
7097 }
7098
7099 fn edit_prediction_settings_at_position(
7100 &self,
7101 buffer: &Entity<Buffer>,
7102 buffer_position: language::Anchor,
7103 cx: &App,
7104 ) -> EditPredictionSettings {
7105 if !self.mode.is_full()
7106 || !self.show_edit_predictions_override.unwrap_or(true)
7107 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7108 {
7109 return EditPredictionSettings::Disabled;
7110 }
7111
7112 let buffer = buffer.read(cx);
7113
7114 let file = buffer.file();
7115
7116 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7117 return EditPredictionSettings::Disabled;
7118 };
7119
7120 let by_provider = matches!(
7121 self.menu_edit_predictions_policy,
7122 MenuEditPredictionsPolicy::ByProvider
7123 );
7124
7125 let show_in_menu = by_provider
7126 && self
7127 .edit_prediction_provider
7128 .as_ref()
7129 .map_or(false, |provider| {
7130 provider.provider.show_completions_in_menu()
7131 });
7132
7133 let preview_requires_modifier =
7134 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7135
7136 EditPredictionSettings::Enabled {
7137 show_in_menu,
7138 preview_requires_modifier,
7139 }
7140 }
7141
7142 fn should_show_edit_predictions(&self) -> bool {
7143 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7144 }
7145
7146 pub fn edit_prediction_preview_is_active(&self) -> bool {
7147 matches!(
7148 self.edit_prediction_preview,
7149 EditPredictionPreview::Active { .. }
7150 )
7151 }
7152
7153 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7154 let cursor = self.selections.newest_anchor().head();
7155 if let Some((buffer, cursor_position)) =
7156 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7157 {
7158 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7159 } else {
7160 false
7161 }
7162 }
7163
7164 pub fn supports_minimap(&self, cx: &App) -> bool {
7165 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7166 }
7167
7168 fn edit_predictions_enabled_in_buffer(
7169 &self,
7170 buffer: &Entity<Buffer>,
7171 buffer_position: language::Anchor,
7172 cx: &App,
7173 ) -> bool {
7174 maybe!({
7175 if self.read_only(cx) {
7176 return Some(false);
7177 }
7178 let provider = self.edit_prediction_provider()?;
7179 if !provider.is_enabled(&buffer, buffer_position, cx) {
7180 return Some(false);
7181 }
7182 let buffer = buffer.read(cx);
7183 let Some(file) = buffer.file() else {
7184 return Some(true);
7185 };
7186 let settings = all_language_settings(Some(file), cx);
7187 Some(settings.edit_predictions_enabled_for_file(file, cx))
7188 })
7189 .unwrap_or(false)
7190 }
7191
7192 fn cycle_edit_prediction(
7193 &mut self,
7194 direction: Direction,
7195 window: &mut Window,
7196 cx: &mut Context<Self>,
7197 ) -> Option<()> {
7198 let provider = self.edit_prediction_provider()?;
7199 let cursor = self.selections.newest_anchor().head();
7200 let (buffer, cursor_buffer_position) =
7201 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7202 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7203 return None;
7204 }
7205
7206 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7207 self.update_visible_edit_prediction(window, cx);
7208
7209 Some(())
7210 }
7211
7212 pub fn show_edit_prediction(
7213 &mut self,
7214 _: &ShowEditPrediction,
7215 window: &mut Window,
7216 cx: &mut Context<Self>,
7217 ) {
7218 if !self.has_active_edit_prediction() {
7219 self.refresh_edit_prediction(false, true, window, cx);
7220 return;
7221 }
7222
7223 self.update_visible_edit_prediction(window, cx);
7224 }
7225
7226 pub fn display_cursor_names(
7227 &mut self,
7228 _: &DisplayCursorNames,
7229 window: &mut Window,
7230 cx: &mut Context<Self>,
7231 ) {
7232 self.show_cursor_names(window, cx);
7233 }
7234
7235 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7236 self.show_cursor_names = true;
7237 cx.notify();
7238 cx.spawn_in(window, async move |this, cx| {
7239 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7240 this.update(cx, |this, cx| {
7241 this.show_cursor_names = false;
7242 cx.notify()
7243 })
7244 .ok()
7245 })
7246 .detach();
7247 }
7248
7249 pub fn next_edit_prediction(
7250 &mut self,
7251 _: &NextEditPrediction,
7252 window: &mut Window,
7253 cx: &mut Context<Self>,
7254 ) {
7255 if self.has_active_edit_prediction() {
7256 self.cycle_edit_prediction(Direction::Next, window, cx);
7257 } else {
7258 let is_copilot_disabled = self
7259 .refresh_edit_prediction(false, true, window, cx)
7260 .is_none();
7261 if is_copilot_disabled {
7262 cx.propagate();
7263 }
7264 }
7265 }
7266
7267 pub fn previous_edit_prediction(
7268 &mut self,
7269 _: &PreviousEditPrediction,
7270 window: &mut Window,
7271 cx: &mut Context<Self>,
7272 ) {
7273 if self.has_active_edit_prediction() {
7274 self.cycle_edit_prediction(Direction::Prev, window, cx);
7275 } else {
7276 let is_copilot_disabled = self
7277 .refresh_edit_prediction(false, true, window, cx)
7278 .is_none();
7279 if is_copilot_disabled {
7280 cx.propagate();
7281 }
7282 }
7283 }
7284
7285 pub fn accept_edit_prediction(
7286 &mut self,
7287 _: &AcceptEditPrediction,
7288 window: &mut Window,
7289 cx: &mut Context<Self>,
7290 ) {
7291 if self.show_edit_predictions_in_menu() {
7292 self.hide_context_menu(window, cx);
7293 }
7294
7295 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7296 return;
7297 };
7298
7299 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7300
7301 match &active_edit_prediction.completion {
7302 EditPrediction::Move { target, .. } => {
7303 let target = *target;
7304
7305 if let Some(position_map) = &self.last_position_map {
7306 if position_map
7307 .visible_row_range
7308 .contains(&target.to_display_point(&position_map.snapshot).row())
7309 || !self.edit_prediction_requires_modifier()
7310 {
7311 self.unfold_ranges(&[target..target], true, false, cx);
7312 // Note that this is also done in vim's handler of the Tab action.
7313 self.change_selections(
7314 SelectionEffects::scroll(Autoscroll::newest()),
7315 window,
7316 cx,
7317 |selections| {
7318 selections.select_anchor_ranges([target..target]);
7319 },
7320 );
7321 self.clear_row_highlights::<EditPredictionPreview>();
7322
7323 self.edit_prediction_preview
7324 .set_previous_scroll_position(None);
7325 } else {
7326 self.edit_prediction_preview
7327 .set_previous_scroll_position(Some(
7328 position_map.snapshot.scroll_anchor,
7329 ));
7330
7331 self.highlight_rows::<EditPredictionPreview>(
7332 target..target,
7333 cx.theme().colors().editor_highlighted_line_background,
7334 RowHighlightOptions {
7335 autoscroll: true,
7336 ..Default::default()
7337 },
7338 cx,
7339 );
7340 self.request_autoscroll(Autoscroll::fit(), cx);
7341 }
7342 }
7343 }
7344 EditPrediction::Edit { edits, .. } => {
7345 if let Some(provider) = self.edit_prediction_provider() {
7346 provider.accept(cx);
7347 }
7348
7349 // Store the transaction ID and selections before applying the edit
7350 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7351
7352 let snapshot = self.buffer.read(cx).snapshot(cx);
7353 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7354
7355 self.buffer.update(cx, |buffer, cx| {
7356 buffer.edit(edits.iter().cloned(), None, cx)
7357 });
7358
7359 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7360 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7361 });
7362
7363 let selections = self.selections.disjoint_anchors();
7364 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7365 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7366 if has_new_transaction {
7367 self.selection_history
7368 .insert_transaction(transaction_id_now, selections);
7369 }
7370 }
7371
7372 self.update_visible_edit_prediction(window, cx);
7373 if self.active_edit_prediction.is_none() {
7374 self.refresh_edit_prediction(true, true, window, cx);
7375 }
7376
7377 cx.notify();
7378 }
7379 }
7380
7381 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7382 }
7383
7384 pub fn accept_partial_edit_prediction(
7385 &mut self,
7386 _: &AcceptPartialEditPrediction,
7387 window: &mut Window,
7388 cx: &mut Context<Self>,
7389 ) {
7390 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7391 return;
7392 };
7393 if self.selections.count() != 1 {
7394 return;
7395 }
7396
7397 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7398
7399 match &active_edit_prediction.completion {
7400 EditPrediction::Move { target, .. } => {
7401 let target = *target;
7402 self.change_selections(
7403 SelectionEffects::scroll(Autoscroll::newest()),
7404 window,
7405 cx,
7406 |selections| {
7407 selections.select_anchor_ranges([target..target]);
7408 },
7409 );
7410 }
7411 EditPrediction::Edit { edits, .. } => {
7412 // Find an insertion that starts at the cursor position.
7413 let snapshot = self.buffer.read(cx).snapshot(cx);
7414 let cursor_offset = self.selections.newest::<usize>(cx).head();
7415 let insertion = edits.iter().find_map(|(range, text)| {
7416 let range = range.to_offset(&snapshot);
7417 if range.is_empty() && range.start == cursor_offset {
7418 Some(text)
7419 } else {
7420 None
7421 }
7422 });
7423
7424 if let Some(text) = insertion {
7425 let mut partial_completion = text
7426 .chars()
7427 .by_ref()
7428 .take_while(|c| c.is_alphabetic())
7429 .collect::<String>();
7430 if partial_completion.is_empty() {
7431 partial_completion = text
7432 .chars()
7433 .by_ref()
7434 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7435 .collect::<String>();
7436 }
7437
7438 cx.emit(EditorEvent::InputHandled {
7439 utf16_range_to_replace: None,
7440 text: partial_completion.clone().into(),
7441 });
7442
7443 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7444
7445 self.refresh_edit_prediction(true, true, window, cx);
7446 cx.notify();
7447 } else {
7448 self.accept_edit_prediction(&Default::default(), window, cx);
7449 }
7450 }
7451 }
7452 }
7453
7454 fn discard_edit_prediction(
7455 &mut self,
7456 should_report_edit_prediction_event: bool,
7457 cx: &mut Context<Self>,
7458 ) -> bool {
7459 if should_report_edit_prediction_event {
7460 let completion_id = self
7461 .active_edit_prediction
7462 .as_ref()
7463 .and_then(|active_completion| active_completion.completion_id.clone());
7464
7465 self.report_edit_prediction_event(completion_id, false, cx);
7466 }
7467
7468 if let Some(provider) = self.edit_prediction_provider() {
7469 provider.discard(cx);
7470 }
7471
7472 self.take_active_edit_prediction(cx)
7473 }
7474
7475 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7476 let Some(provider) = self.edit_prediction_provider() else {
7477 return;
7478 };
7479
7480 let Some((_, buffer, _)) = self
7481 .buffer
7482 .read(cx)
7483 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7484 else {
7485 return;
7486 };
7487
7488 let extension = buffer
7489 .read(cx)
7490 .file()
7491 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7492
7493 let event_type = match accepted {
7494 true => "Edit Prediction Accepted",
7495 false => "Edit Prediction Discarded",
7496 };
7497 telemetry::event!(
7498 event_type,
7499 provider = provider.name(),
7500 prediction_id = id,
7501 suggestion_accepted = accepted,
7502 file_extension = extension,
7503 );
7504 }
7505
7506 pub fn has_active_edit_prediction(&self) -> bool {
7507 self.active_edit_prediction.is_some()
7508 }
7509
7510 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7511 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7512 return false;
7513 };
7514
7515 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7516 self.clear_highlights::<EditPredictionHighlight>(cx);
7517 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7518 true
7519 }
7520
7521 /// Returns true when we're displaying the edit prediction popover below the cursor
7522 /// like we are not previewing and the LSP autocomplete menu is visible
7523 /// or we are in `when_holding_modifier` mode.
7524 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7525 if self.edit_prediction_preview_is_active()
7526 || !self.show_edit_predictions_in_menu()
7527 || !self.edit_predictions_enabled()
7528 {
7529 return false;
7530 }
7531
7532 if self.has_visible_completions_menu() {
7533 return true;
7534 }
7535
7536 has_completion && self.edit_prediction_requires_modifier()
7537 }
7538
7539 fn handle_modifiers_changed(
7540 &mut self,
7541 modifiers: Modifiers,
7542 position_map: &PositionMap,
7543 window: &mut Window,
7544 cx: &mut Context<Self>,
7545 ) {
7546 if self.show_edit_predictions_in_menu() {
7547 self.update_edit_prediction_preview(&modifiers, window, cx);
7548 }
7549
7550 self.update_selection_mode(&modifiers, position_map, window, cx);
7551
7552 let mouse_position = window.mouse_position();
7553 if !position_map.text_hitbox.is_hovered(window) {
7554 return;
7555 }
7556
7557 self.update_hovered_link(
7558 position_map.point_for_position(mouse_position),
7559 &position_map.snapshot,
7560 modifiers,
7561 window,
7562 cx,
7563 )
7564 }
7565
7566 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7567 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7568 if invert {
7569 match multi_cursor_setting {
7570 MultiCursorModifier::Alt => modifiers.alt,
7571 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7572 }
7573 } else {
7574 match multi_cursor_setting {
7575 MultiCursorModifier::Alt => modifiers.secondary(),
7576 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7577 }
7578 }
7579 }
7580
7581 fn columnar_selection_mode(
7582 modifiers: &Modifiers,
7583 cx: &mut Context<Self>,
7584 ) -> Option<ColumnarMode> {
7585 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7586 if Self::multi_cursor_modifier(false, modifiers, cx) {
7587 Some(ColumnarMode::FromMouse)
7588 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7589 Some(ColumnarMode::FromSelection)
7590 } else {
7591 None
7592 }
7593 } else {
7594 None
7595 }
7596 }
7597
7598 fn update_selection_mode(
7599 &mut self,
7600 modifiers: &Modifiers,
7601 position_map: &PositionMap,
7602 window: &mut Window,
7603 cx: &mut Context<Self>,
7604 ) {
7605 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7606 return;
7607 };
7608 if self.selections.pending.is_none() {
7609 return;
7610 }
7611
7612 let mouse_position = window.mouse_position();
7613 let point_for_position = position_map.point_for_position(mouse_position);
7614 let position = point_for_position.previous_valid;
7615
7616 self.select(
7617 SelectPhase::BeginColumnar {
7618 position,
7619 reset: false,
7620 mode,
7621 goal_column: point_for_position.exact_unclipped.column(),
7622 },
7623 window,
7624 cx,
7625 );
7626 }
7627
7628 fn update_edit_prediction_preview(
7629 &mut self,
7630 modifiers: &Modifiers,
7631 window: &mut Window,
7632 cx: &mut Context<Self>,
7633 ) {
7634 let mut modifiers_held = false;
7635 if let Some(accept_keystroke) = self
7636 .accept_edit_prediction_keybind(false, window, cx)
7637 .keystroke()
7638 {
7639 modifiers_held = modifiers_held
7640 || (&accept_keystroke.modifiers == modifiers
7641 && accept_keystroke.modifiers.modified());
7642 };
7643 if let Some(accept_partial_keystroke) = self
7644 .accept_edit_prediction_keybind(true, window, cx)
7645 .keystroke()
7646 {
7647 modifiers_held = modifiers_held
7648 || (&accept_partial_keystroke.modifiers == modifiers
7649 && accept_partial_keystroke.modifiers.modified());
7650 }
7651
7652 if modifiers_held {
7653 if matches!(
7654 self.edit_prediction_preview,
7655 EditPredictionPreview::Inactive { .. }
7656 ) {
7657 self.edit_prediction_preview = EditPredictionPreview::Active {
7658 previous_scroll_position: None,
7659 since: Instant::now(),
7660 };
7661
7662 self.update_visible_edit_prediction(window, cx);
7663 cx.notify();
7664 }
7665 } else if let EditPredictionPreview::Active {
7666 previous_scroll_position,
7667 since,
7668 } = self.edit_prediction_preview
7669 {
7670 if let (Some(previous_scroll_position), Some(position_map)) =
7671 (previous_scroll_position, self.last_position_map.as_ref())
7672 {
7673 self.set_scroll_position(
7674 previous_scroll_position
7675 .scroll_position(&position_map.snapshot.display_snapshot),
7676 window,
7677 cx,
7678 );
7679 }
7680
7681 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7682 released_too_fast: since.elapsed() < Duration::from_millis(200),
7683 };
7684 self.clear_row_highlights::<EditPredictionPreview>();
7685 self.update_visible_edit_prediction(window, cx);
7686 cx.notify();
7687 }
7688 }
7689
7690 fn update_visible_edit_prediction(
7691 &mut self,
7692 _window: &mut Window,
7693 cx: &mut Context<Self>,
7694 ) -> Option<()> {
7695 if DisableAiSettings::get_global(cx).disable_ai {
7696 return None;
7697 }
7698
7699 let selection = self.selections.newest_anchor();
7700 let cursor = selection.head();
7701 let multibuffer = self.buffer.read(cx).snapshot(cx);
7702 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7703 let excerpt_id = cursor.excerpt_id;
7704
7705 let show_in_menu = self.show_edit_predictions_in_menu();
7706 let completions_menu_has_precedence = !show_in_menu
7707 && (self.context_menu.borrow().is_some()
7708 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7709
7710 if completions_menu_has_precedence
7711 || !offset_selection.is_empty()
7712 || self
7713 .active_edit_prediction
7714 .as_ref()
7715 .map_or(false, |completion| {
7716 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7717 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7718 !invalidation_range.contains(&offset_selection.head())
7719 })
7720 {
7721 self.discard_edit_prediction(false, cx);
7722 return None;
7723 }
7724
7725 self.take_active_edit_prediction(cx);
7726 let Some(provider) = self.edit_prediction_provider() else {
7727 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7728 return None;
7729 };
7730
7731 let (buffer, cursor_buffer_position) =
7732 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7733
7734 self.edit_prediction_settings =
7735 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7736
7737 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7738
7739 if self.edit_prediction_indent_conflict {
7740 let cursor_point = cursor.to_point(&multibuffer);
7741
7742 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7743
7744 if let Some((_, indent)) = indents.iter().next() {
7745 if indent.len == cursor_point.column {
7746 self.edit_prediction_indent_conflict = false;
7747 }
7748 }
7749 }
7750
7751 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7752 let edits = edit_prediction
7753 .edits
7754 .into_iter()
7755 .flat_map(|(range, new_text)| {
7756 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7757 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7758 Some((start..end, new_text))
7759 })
7760 .collect::<Vec<_>>();
7761 if edits.is_empty() {
7762 return None;
7763 }
7764
7765 let first_edit_start = edits.first().unwrap().0.start;
7766 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7767 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7768
7769 let last_edit_end = edits.last().unwrap().0.end;
7770 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7771 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7772
7773 let cursor_row = cursor.to_point(&multibuffer).row;
7774
7775 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7776
7777 let mut inlay_ids = Vec::new();
7778 let invalidation_row_range;
7779 let move_invalidation_row_range = if cursor_row < edit_start_row {
7780 Some(cursor_row..edit_end_row)
7781 } else if cursor_row > edit_end_row {
7782 Some(edit_start_row..cursor_row)
7783 } else {
7784 None
7785 };
7786 let supports_jump = self
7787 .edit_prediction_provider
7788 .as_ref()
7789 .map(|provider| provider.provider.supports_jump_to_edit())
7790 .unwrap_or(true);
7791
7792 let is_move = supports_jump
7793 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7794 let completion = if is_move {
7795 invalidation_row_range =
7796 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7797 let target = first_edit_start;
7798 EditPrediction::Move { target, snapshot }
7799 } else {
7800 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7801 && !self.edit_predictions_hidden_for_vim_mode;
7802
7803 if show_completions_in_buffer {
7804 if edits
7805 .iter()
7806 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7807 {
7808 let mut inlays = Vec::new();
7809 for (range, new_text) in &edits {
7810 let inlay = Inlay::edit_prediction(
7811 post_inc(&mut self.next_inlay_id),
7812 range.start,
7813 new_text.as_str(),
7814 );
7815 inlay_ids.push(inlay.id);
7816 inlays.push(inlay);
7817 }
7818
7819 self.splice_inlays(&[], inlays, cx);
7820 } else {
7821 let background_color = cx.theme().status().deleted_background;
7822 self.highlight_text::<EditPredictionHighlight>(
7823 edits.iter().map(|(range, _)| range.clone()).collect(),
7824 HighlightStyle {
7825 background_color: Some(background_color),
7826 ..Default::default()
7827 },
7828 cx,
7829 );
7830 }
7831 }
7832
7833 invalidation_row_range = edit_start_row..edit_end_row;
7834
7835 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7836 if provider.show_tab_accept_marker() {
7837 EditDisplayMode::TabAccept
7838 } else {
7839 EditDisplayMode::Inline
7840 }
7841 } else {
7842 EditDisplayMode::DiffPopover
7843 };
7844
7845 EditPrediction::Edit {
7846 edits,
7847 edit_preview: edit_prediction.edit_preview,
7848 display_mode,
7849 snapshot,
7850 }
7851 };
7852
7853 let invalidation_range = multibuffer
7854 .anchor_before(Point::new(invalidation_row_range.start, 0))
7855 ..multibuffer.anchor_after(Point::new(
7856 invalidation_row_range.end,
7857 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7858 ));
7859
7860 self.stale_edit_prediction_in_menu = None;
7861 self.active_edit_prediction = Some(EditPredictionState {
7862 inlay_ids,
7863 completion,
7864 completion_id: edit_prediction.id,
7865 invalidation_range,
7866 });
7867
7868 cx.notify();
7869
7870 Some(())
7871 }
7872
7873 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7874 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7875 }
7876
7877 fn clear_tasks(&mut self) {
7878 self.tasks.clear()
7879 }
7880
7881 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7882 if self.tasks.insert(key, value).is_some() {
7883 // This case should hopefully be rare, but just in case...
7884 log::error!(
7885 "multiple different run targets found on a single line, only the last target will be rendered"
7886 )
7887 }
7888 }
7889
7890 /// Get all display points of breakpoints that will be rendered within editor
7891 ///
7892 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7893 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7894 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7895 fn active_breakpoints(
7896 &self,
7897 range: Range<DisplayRow>,
7898 window: &mut Window,
7899 cx: &mut Context<Self>,
7900 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7901 let mut breakpoint_display_points = HashMap::default();
7902
7903 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7904 return breakpoint_display_points;
7905 };
7906
7907 let snapshot = self.snapshot(window, cx);
7908
7909 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7910 let Some(project) = self.project.as_ref() else {
7911 return breakpoint_display_points;
7912 };
7913
7914 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7915 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7916
7917 for (buffer_snapshot, range, excerpt_id) in
7918 multi_buffer_snapshot.range_to_buffer_ranges(range)
7919 {
7920 let Some(buffer) = project
7921 .read(cx)
7922 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7923 else {
7924 continue;
7925 };
7926 let breakpoints = breakpoint_store.read(cx).breakpoints(
7927 &buffer,
7928 Some(
7929 buffer_snapshot.anchor_before(range.start)
7930 ..buffer_snapshot.anchor_after(range.end),
7931 ),
7932 buffer_snapshot,
7933 cx,
7934 );
7935 for (breakpoint, state) in breakpoints {
7936 let multi_buffer_anchor =
7937 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7938 let position = multi_buffer_anchor
7939 .to_point(&multi_buffer_snapshot)
7940 .to_display_point(&snapshot);
7941
7942 breakpoint_display_points.insert(
7943 position.row(),
7944 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7945 );
7946 }
7947 }
7948
7949 breakpoint_display_points
7950 }
7951
7952 fn breakpoint_context_menu(
7953 &self,
7954 anchor: Anchor,
7955 window: &mut Window,
7956 cx: &mut Context<Self>,
7957 ) -> Entity<ui::ContextMenu> {
7958 let weak_editor = cx.weak_entity();
7959 let focus_handle = self.focus_handle(cx);
7960
7961 let row = self
7962 .buffer
7963 .read(cx)
7964 .snapshot(cx)
7965 .summary_for_anchor::<Point>(&anchor)
7966 .row;
7967
7968 let breakpoint = self
7969 .breakpoint_at_row(row, window, cx)
7970 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7971
7972 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7973 "Edit Log Breakpoint"
7974 } else {
7975 "Set Log Breakpoint"
7976 };
7977
7978 let condition_breakpoint_msg = if breakpoint
7979 .as_ref()
7980 .is_some_and(|bp| bp.1.condition.is_some())
7981 {
7982 "Edit Condition Breakpoint"
7983 } else {
7984 "Set Condition Breakpoint"
7985 };
7986
7987 let hit_condition_breakpoint_msg = if breakpoint
7988 .as_ref()
7989 .is_some_and(|bp| bp.1.hit_condition.is_some())
7990 {
7991 "Edit Hit Condition Breakpoint"
7992 } else {
7993 "Set Hit Condition Breakpoint"
7994 };
7995
7996 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7997 "Unset Breakpoint"
7998 } else {
7999 "Set Breakpoint"
8000 };
8001
8002 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8003
8004 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8005 BreakpointState::Enabled => Some("Disable"),
8006 BreakpointState::Disabled => Some("Enable"),
8007 });
8008
8009 let (anchor, breakpoint) =
8010 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8011
8012 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8013 menu.on_blur_subscription(Subscription::new(|| {}))
8014 .context(focus_handle)
8015 .when(run_to_cursor, |this| {
8016 let weak_editor = weak_editor.clone();
8017 this.entry("Run to cursor", None, move |window, cx| {
8018 weak_editor
8019 .update(cx, |editor, cx| {
8020 editor.change_selections(
8021 SelectionEffects::no_scroll(),
8022 window,
8023 cx,
8024 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8025 );
8026 })
8027 .ok();
8028
8029 window.dispatch_action(Box::new(RunToCursor), cx);
8030 })
8031 .separator()
8032 })
8033 .when_some(toggle_state_msg, |this, msg| {
8034 this.entry(msg, None, {
8035 let weak_editor = weak_editor.clone();
8036 let breakpoint = breakpoint.clone();
8037 move |_window, cx| {
8038 weak_editor
8039 .update(cx, |this, cx| {
8040 this.edit_breakpoint_at_anchor(
8041 anchor,
8042 breakpoint.as_ref().clone(),
8043 BreakpointEditAction::InvertState,
8044 cx,
8045 );
8046 })
8047 .log_err();
8048 }
8049 })
8050 })
8051 .entry(set_breakpoint_msg, None, {
8052 let weak_editor = weak_editor.clone();
8053 let breakpoint = breakpoint.clone();
8054 move |_window, cx| {
8055 weak_editor
8056 .update(cx, |this, cx| {
8057 this.edit_breakpoint_at_anchor(
8058 anchor,
8059 breakpoint.as_ref().clone(),
8060 BreakpointEditAction::Toggle,
8061 cx,
8062 );
8063 })
8064 .log_err();
8065 }
8066 })
8067 .entry(log_breakpoint_msg, None, {
8068 let breakpoint = breakpoint.clone();
8069 let weak_editor = weak_editor.clone();
8070 move |window, cx| {
8071 weak_editor
8072 .update(cx, |this, cx| {
8073 this.add_edit_breakpoint_block(
8074 anchor,
8075 breakpoint.as_ref(),
8076 BreakpointPromptEditAction::Log,
8077 window,
8078 cx,
8079 );
8080 })
8081 .log_err();
8082 }
8083 })
8084 .entry(condition_breakpoint_msg, None, {
8085 let breakpoint = breakpoint.clone();
8086 let weak_editor = weak_editor.clone();
8087 move |window, cx| {
8088 weak_editor
8089 .update(cx, |this, cx| {
8090 this.add_edit_breakpoint_block(
8091 anchor,
8092 breakpoint.as_ref(),
8093 BreakpointPromptEditAction::Condition,
8094 window,
8095 cx,
8096 );
8097 })
8098 .log_err();
8099 }
8100 })
8101 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8102 weak_editor
8103 .update(cx, |this, cx| {
8104 this.add_edit_breakpoint_block(
8105 anchor,
8106 breakpoint.as_ref(),
8107 BreakpointPromptEditAction::HitCondition,
8108 window,
8109 cx,
8110 );
8111 })
8112 .log_err();
8113 })
8114 })
8115 }
8116
8117 fn render_breakpoint(
8118 &self,
8119 position: Anchor,
8120 row: DisplayRow,
8121 breakpoint: &Breakpoint,
8122 state: Option<BreakpointSessionState>,
8123 cx: &mut Context<Self>,
8124 ) -> IconButton {
8125 let is_rejected = state.is_some_and(|s| !s.verified);
8126 // Is it a breakpoint that shows up when hovering over gutter?
8127 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8128 (false, false),
8129 |PhantomBreakpointIndicator {
8130 is_active,
8131 display_row,
8132 collides_with_existing_breakpoint,
8133 }| {
8134 (
8135 is_active && display_row == row,
8136 collides_with_existing_breakpoint,
8137 )
8138 },
8139 );
8140
8141 let (color, icon) = {
8142 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8143 (false, false) => ui::IconName::DebugBreakpoint,
8144 (true, false) => ui::IconName::DebugLogBreakpoint,
8145 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8146 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8147 };
8148
8149 let color = if is_phantom {
8150 Color::Hint
8151 } else if is_rejected {
8152 Color::Disabled
8153 } else {
8154 Color::Debugger
8155 };
8156
8157 (color, icon)
8158 };
8159
8160 let breakpoint = Arc::from(breakpoint.clone());
8161
8162 let alt_as_text = gpui::Keystroke {
8163 modifiers: Modifiers::secondary_key(),
8164 ..Default::default()
8165 };
8166 let primary_action_text = if breakpoint.is_disabled() {
8167 "Enable breakpoint"
8168 } else if is_phantom && !collides_with_existing {
8169 "Set breakpoint"
8170 } else {
8171 "Unset breakpoint"
8172 };
8173 let focus_handle = self.focus_handle.clone();
8174
8175 let meta = if is_rejected {
8176 SharedString::from("No executable code is associated with this line.")
8177 } else if collides_with_existing && !breakpoint.is_disabled() {
8178 SharedString::from(format!(
8179 "{alt_as_text}-click to disable,\nright-click for more options."
8180 ))
8181 } else {
8182 SharedString::from("Right-click for more options.")
8183 };
8184 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8185 .icon_size(IconSize::XSmall)
8186 .size(ui::ButtonSize::None)
8187 .when(is_rejected, |this| {
8188 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8189 })
8190 .icon_color(color)
8191 .style(ButtonStyle::Transparent)
8192 .on_click(cx.listener({
8193 let breakpoint = breakpoint.clone();
8194
8195 move |editor, event: &ClickEvent, window, cx| {
8196 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8197 BreakpointEditAction::InvertState
8198 } else {
8199 BreakpointEditAction::Toggle
8200 };
8201
8202 window.focus(&editor.focus_handle(cx));
8203 editor.edit_breakpoint_at_anchor(
8204 position,
8205 breakpoint.as_ref().clone(),
8206 edit_action,
8207 cx,
8208 );
8209 }
8210 }))
8211 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8212 editor.set_breakpoint_context_menu(
8213 row,
8214 Some(position),
8215 event.position(),
8216 window,
8217 cx,
8218 );
8219 }))
8220 .tooltip(move |window, cx| {
8221 Tooltip::with_meta_in(
8222 primary_action_text,
8223 Some(&ToggleBreakpoint),
8224 meta.clone(),
8225 &focus_handle,
8226 window,
8227 cx,
8228 )
8229 })
8230 }
8231
8232 fn build_tasks_context(
8233 project: &Entity<Project>,
8234 buffer: &Entity<Buffer>,
8235 buffer_row: u32,
8236 tasks: &Arc<RunnableTasks>,
8237 cx: &mut Context<Self>,
8238 ) -> Task<Option<task::TaskContext>> {
8239 let position = Point::new(buffer_row, tasks.column);
8240 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8241 let location = Location {
8242 buffer: buffer.clone(),
8243 range: range_start..range_start,
8244 };
8245 // Fill in the environmental variables from the tree-sitter captures
8246 let mut captured_task_variables = TaskVariables::default();
8247 for (capture_name, value) in tasks.extra_variables.clone() {
8248 captured_task_variables.insert(
8249 task::VariableName::Custom(capture_name.into()),
8250 value.clone(),
8251 );
8252 }
8253 project.update(cx, |project, cx| {
8254 project.task_store().update(cx, |task_store, cx| {
8255 task_store.task_context_for_location(captured_task_variables, location, cx)
8256 })
8257 })
8258 }
8259
8260 pub fn spawn_nearest_task(
8261 &mut self,
8262 action: &SpawnNearestTask,
8263 window: &mut Window,
8264 cx: &mut Context<Self>,
8265 ) {
8266 let Some((workspace, _)) = self.workspace.clone() else {
8267 return;
8268 };
8269 let Some(project) = self.project.clone() else {
8270 return;
8271 };
8272
8273 // Try to find a closest, enclosing node using tree-sitter that has a task
8274 let Some((buffer, buffer_row, tasks)) = self
8275 .find_enclosing_node_task(cx)
8276 // Or find the task that's closest in row-distance.
8277 .or_else(|| self.find_closest_task(cx))
8278 else {
8279 return;
8280 };
8281
8282 let reveal_strategy = action.reveal;
8283 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8284 cx.spawn_in(window, async move |_, cx| {
8285 let context = task_context.await?;
8286 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8287
8288 let resolved = &mut resolved_task.resolved;
8289 resolved.reveal = reveal_strategy;
8290
8291 workspace
8292 .update_in(cx, |workspace, window, cx| {
8293 workspace.schedule_resolved_task(
8294 task_source_kind,
8295 resolved_task,
8296 false,
8297 window,
8298 cx,
8299 );
8300 })
8301 .ok()
8302 })
8303 .detach();
8304 }
8305
8306 fn find_closest_task(
8307 &mut self,
8308 cx: &mut Context<Self>,
8309 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8310 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8311
8312 let ((buffer_id, row), tasks) = self
8313 .tasks
8314 .iter()
8315 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8316
8317 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8318 let tasks = Arc::new(tasks.to_owned());
8319 Some((buffer, *row, tasks))
8320 }
8321
8322 fn find_enclosing_node_task(
8323 &mut self,
8324 cx: &mut Context<Self>,
8325 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8326 let snapshot = self.buffer.read(cx).snapshot(cx);
8327 let offset = self.selections.newest::<usize>(cx).head();
8328 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8329 let buffer_id = excerpt.buffer().remote_id();
8330
8331 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8332 let mut cursor = layer.node().walk();
8333
8334 while cursor.goto_first_child_for_byte(offset).is_some() {
8335 if cursor.node().end_byte() == offset {
8336 cursor.goto_next_sibling();
8337 }
8338 }
8339
8340 // Ascend to the smallest ancestor that contains the range and has a task.
8341 loop {
8342 let node = cursor.node();
8343 let node_range = node.byte_range();
8344 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8345
8346 // Check if this node contains our offset
8347 if node_range.start <= offset && node_range.end >= offset {
8348 // If it contains offset, check for task
8349 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8350 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8351 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8352 }
8353 }
8354
8355 if !cursor.goto_parent() {
8356 break;
8357 }
8358 }
8359 None
8360 }
8361
8362 fn render_run_indicator(
8363 &self,
8364 _style: &EditorStyle,
8365 is_active: bool,
8366 row: DisplayRow,
8367 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8368 cx: &mut Context<Self>,
8369 ) -> IconButton {
8370 let color = Color::Muted;
8371 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8372
8373 IconButton::new(
8374 ("run_indicator", row.0 as usize),
8375 ui::IconName::PlayOutlined,
8376 )
8377 .shape(ui::IconButtonShape::Square)
8378 .icon_size(IconSize::XSmall)
8379 .icon_color(color)
8380 .toggle_state(is_active)
8381 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8382 let quick_launch = match e {
8383 ClickEvent::Keyboard(_) => true,
8384 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8385 };
8386
8387 window.focus(&editor.focus_handle(cx));
8388 editor.toggle_code_actions(
8389 &ToggleCodeActions {
8390 deployed_from: Some(CodeActionSource::RunMenu(row)),
8391 quick_launch,
8392 },
8393 window,
8394 cx,
8395 );
8396 }))
8397 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8398 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8399 }))
8400 }
8401
8402 pub fn context_menu_visible(&self) -> bool {
8403 !self.edit_prediction_preview_is_active()
8404 && self
8405 .context_menu
8406 .borrow()
8407 .as_ref()
8408 .map_or(false, |menu| menu.visible())
8409 }
8410
8411 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8412 self.context_menu
8413 .borrow()
8414 .as_ref()
8415 .map(|menu| menu.origin())
8416 }
8417
8418 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8419 self.context_menu_options = Some(options);
8420 }
8421
8422 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8423 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8424
8425 fn render_edit_prediction_popover(
8426 &mut self,
8427 text_bounds: &Bounds<Pixels>,
8428 content_origin: gpui::Point<Pixels>,
8429 right_margin: Pixels,
8430 editor_snapshot: &EditorSnapshot,
8431 visible_row_range: Range<DisplayRow>,
8432 scroll_top: f32,
8433 scroll_bottom: f32,
8434 line_layouts: &[LineWithInvisibles],
8435 line_height: Pixels,
8436 scroll_pixel_position: gpui::Point<Pixels>,
8437 newest_selection_head: Option<DisplayPoint>,
8438 editor_width: Pixels,
8439 style: &EditorStyle,
8440 window: &mut Window,
8441 cx: &mut App,
8442 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8443 if self.mode().is_minimap() {
8444 return None;
8445 }
8446 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8447
8448 if self.edit_prediction_visible_in_cursor_popover(true) {
8449 return None;
8450 }
8451
8452 match &active_edit_prediction.completion {
8453 EditPrediction::Move { target, .. } => {
8454 let target_display_point = target.to_display_point(editor_snapshot);
8455
8456 if self.edit_prediction_requires_modifier() {
8457 if !self.edit_prediction_preview_is_active() {
8458 return None;
8459 }
8460
8461 self.render_edit_prediction_modifier_jump_popover(
8462 text_bounds,
8463 content_origin,
8464 visible_row_range,
8465 line_layouts,
8466 line_height,
8467 scroll_pixel_position,
8468 newest_selection_head,
8469 target_display_point,
8470 window,
8471 cx,
8472 )
8473 } else {
8474 self.render_edit_prediction_eager_jump_popover(
8475 text_bounds,
8476 content_origin,
8477 editor_snapshot,
8478 visible_row_range,
8479 scroll_top,
8480 scroll_bottom,
8481 line_height,
8482 scroll_pixel_position,
8483 target_display_point,
8484 editor_width,
8485 window,
8486 cx,
8487 )
8488 }
8489 }
8490 EditPrediction::Edit {
8491 display_mode: EditDisplayMode::Inline,
8492 ..
8493 } => None,
8494 EditPrediction::Edit {
8495 display_mode: EditDisplayMode::TabAccept,
8496 edits,
8497 ..
8498 } => {
8499 let range = &edits.first()?.0;
8500 let target_display_point = range.end.to_display_point(editor_snapshot);
8501
8502 self.render_edit_prediction_end_of_line_popover(
8503 "Accept",
8504 editor_snapshot,
8505 visible_row_range,
8506 target_display_point,
8507 line_height,
8508 scroll_pixel_position,
8509 content_origin,
8510 editor_width,
8511 window,
8512 cx,
8513 )
8514 }
8515 EditPrediction::Edit {
8516 edits,
8517 edit_preview,
8518 display_mode: EditDisplayMode::DiffPopover,
8519 snapshot,
8520 } => self.render_edit_prediction_diff_popover(
8521 text_bounds,
8522 content_origin,
8523 right_margin,
8524 editor_snapshot,
8525 visible_row_range,
8526 line_layouts,
8527 line_height,
8528 scroll_pixel_position,
8529 newest_selection_head,
8530 editor_width,
8531 style,
8532 edits,
8533 edit_preview,
8534 snapshot,
8535 window,
8536 cx,
8537 ),
8538 }
8539 }
8540
8541 fn render_edit_prediction_modifier_jump_popover(
8542 &mut self,
8543 text_bounds: &Bounds<Pixels>,
8544 content_origin: gpui::Point<Pixels>,
8545 visible_row_range: Range<DisplayRow>,
8546 line_layouts: &[LineWithInvisibles],
8547 line_height: Pixels,
8548 scroll_pixel_position: gpui::Point<Pixels>,
8549 newest_selection_head: Option<DisplayPoint>,
8550 target_display_point: DisplayPoint,
8551 window: &mut Window,
8552 cx: &mut App,
8553 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8554 let scrolled_content_origin =
8555 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8556
8557 const SCROLL_PADDING_Y: Pixels = px(12.);
8558
8559 if target_display_point.row() < visible_row_range.start {
8560 return self.render_edit_prediction_scroll_popover(
8561 |_| SCROLL_PADDING_Y,
8562 IconName::ArrowUp,
8563 visible_row_range,
8564 line_layouts,
8565 newest_selection_head,
8566 scrolled_content_origin,
8567 window,
8568 cx,
8569 );
8570 } else if target_display_point.row() >= visible_row_range.end {
8571 return self.render_edit_prediction_scroll_popover(
8572 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8573 IconName::ArrowDown,
8574 visible_row_range,
8575 line_layouts,
8576 newest_selection_head,
8577 scrolled_content_origin,
8578 window,
8579 cx,
8580 );
8581 }
8582
8583 const POLE_WIDTH: Pixels = px(2.);
8584
8585 let line_layout =
8586 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8587 let target_column = target_display_point.column() as usize;
8588
8589 let target_x = line_layout.x_for_index(target_column);
8590 let target_y =
8591 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8592
8593 let flag_on_right = target_x < text_bounds.size.width / 2.;
8594
8595 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8596 border_color.l += 0.001;
8597
8598 let mut element = v_flex()
8599 .items_end()
8600 .when(flag_on_right, |el| el.items_start())
8601 .child(if flag_on_right {
8602 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8603 .rounded_bl(px(0.))
8604 .rounded_tl(px(0.))
8605 .border_l_2()
8606 .border_color(border_color)
8607 } else {
8608 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8609 .rounded_br(px(0.))
8610 .rounded_tr(px(0.))
8611 .border_r_2()
8612 .border_color(border_color)
8613 })
8614 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8615 .into_any();
8616
8617 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8618
8619 let mut origin = scrolled_content_origin + point(target_x, target_y)
8620 - point(
8621 if flag_on_right {
8622 POLE_WIDTH
8623 } else {
8624 size.width - POLE_WIDTH
8625 },
8626 size.height - line_height,
8627 );
8628
8629 origin.x = origin.x.max(content_origin.x);
8630
8631 element.prepaint_at(origin, window, cx);
8632
8633 Some((element, origin))
8634 }
8635
8636 fn render_edit_prediction_scroll_popover(
8637 &mut self,
8638 to_y: impl Fn(Size<Pixels>) -> Pixels,
8639 scroll_icon: IconName,
8640 visible_row_range: Range<DisplayRow>,
8641 line_layouts: &[LineWithInvisibles],
8642 newest_selection_head: Option<DisplayPoint>,
8643 scrolled_content_origin: gpui::Point<Pixels>,
8644 window: &mut Window,
8645 cx: &mut App,
8646 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8647 let mut element = self
8648 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8649 .into_any();
8650
8651 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8652
8653 let cursor = newest_selection_head?;
8654 let cursor_row_layout =
8655 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8656 let cursor_column = cursor.column() as usize;
8657
8658 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8659
8660 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8661
8662 element.prepaint_at(origin, window, cx);
8663 Some((element, origin))
8664 }
8665
8666 fn render_edit_prediction_eager_jump_popover(
8667 &mut self,
8668 text_bounds: &Bounds<Pixels>,
8669 content_origin: gpui::Point<Pixels>,
8670 editor_snapshot: &EditorSnapshot,
8671 visible_row_range: Range<DisplayRow>,
8672 scroll_top: f32,
8673 scroll_bottom: f32,
8674 line_height: Pixels,
8675 scroll_pixel_position: gpui::Point<Pixels>,
8676 target_display_point: DisplayPoint,
8677 editor_width: Pixels,
8678 window: &mut Window,
8679 cx: &mut App,
8680 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8681 if target_display_point.row().as_f32() < scroll_top {
8682 let mut element = self
8683 .render_edit_prediction_line_popover(
8684 "Jump to Edit",
8685 Some(IconName::ArrowUp),
8686 window,
8687 cx,
8688 )?
8689 .into_any();
8690
8691 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8692 let offset = point(
8693 (text_bounds.size.width - size.width) / 2.,
8694 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8695 );
8696
8697 let origin = text_bounds.origin + offset;
8698 element.prepaint_at(origin, window, cx);
8699 Some((element, origin))
8700 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8701 let mut element = self
8702 .render_edit_prediction_line_popover(
8703 "Jump to Edit",
8704 Some(IconName::ArrowDown),
8705 window,
8706 cx,
8707 )?
8708 .into_any();
8709
8710 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8711 let offset = point(
8712 (text_bounds.size.width - size.width) / 2.,
8713 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8714 );
8715
8716 let origin = text_bounds.origin + offset;
8717 element.prepaint_at(origin, window, cx);
8718 Some((element, origin))
8719 } else {
8720 self.render_edit_prediction_end_of_line_popover(
8721 "Jump to Edit",
8722 editor_snapshot,
8723 visible_row_range,
8724 target_display_point,
8725 line_height,
8726 scroll_pixel_position,
8727 content_origin,
8728 editor_width,
8729 window,
8730 cx,
8731 )
8732 }
8733 }
8734
8735 fn render_edit_prediction_end_of_line_popover(
8736 self: &mut Editor,
8737 label: &'static str,
8738 editor_snapshot: &EditorSnapshot,
8739 visible_row_range: Range<DisplayRow>,
8740 target_display_point: DisplayPoint,
8741 line_height: Pixels,
8742 scroll_pixel_position: gpui::Point<Pixels>,
8743 content_origin: gpui::Point<Pixels>,
8744 editor_width: Pixels,
8745 window: &mut Window,
8746 cx: &mut App,
8747 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8748 let target_line_end = DisplayPoint::new(
8749 target_display_point.row(),
8750 editor_snapshot.line_len(target_display_point.row()),
8751 );
8752
8753 let mut element = self
8754 .render_edit_prediction_line_popover(label, None, window, cx)?
8755 .into_any();
8756
8757 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8758
8759 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8760
8761 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8762 let mut origin = start_point
8763 + line_origin
8764 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8765 origin.x = origin.x.max(content_origin.x);
8766
8767 let max_x = content_origin.x + editor_width - size.width;
8768
8769 if origin.x > max_x {
8770 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8771
8772 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8773 origin.y += offset;
8774 IconName::ArrowUp
8775 } else {
8776 origin.y -= offset;
8777 IconName::ArrowDown
8778 };
8779
8780 element = self
8781 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8782 .into_any();
8783
8784 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8785
8786 origin.x = content_origin.x + editor_width - size.width - px(2.);
8787 }
8788
8789 element.prepaint_at(origin, window, cx);
8790 Some((element, origin))
8791 }
8792
8793 fn render_edit_prediction_diff_popover(
8794 self: &Editor,
8795 text_bounds: &Bounds<Pixels>,
8796 content_origin: gpui::Point<Pixels>,
8797 right_margin: Pixels,
8798 editor_snapshot: &EditorSnapshot,
8799 visible_row_range: Range<DisplayRow>,
8800 line_layouts: &[LineWithInvisibles],
8801 line_height: Pixels,
8802 scroll_pixel_position: gpui::Point<Pixels>,
8803 newest_selection_head: Option<DisplayPoint>,
8804 editor_width: Pixels,
8805 style: &EditorStyle,
8806 edits: &Vec<(Range<Anchor>, String)>,
8807 edit_preview: &Option<language::EditPreview>,
8808 snapshot: &language::BufferSnapshot,
8809 window: &mut Window,
8810 cx: &mut App,
8811 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8812 let edit_start = edits
8813 .first()
8814 .unwrap()
8815 .0
8816 .start
8817 .to_display_point(editor_snapshot);
8818 let edit_end = edits
8819 .last()
8820 .unwrap()
8821 .0
8822 .end
8823 .to_display_point(editor_snapshot);
8824
8825 let is_visible = visible_row_range.contains(&edit_start.row())
8826 || visible_row_range.contains(&edit_end.row());
8827 if !is_visible {
8828 return None;
8829 }
8830
8831 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8832 crate::edit_prediction_edit_text(&snapshot, edits, edit_preview, false, cx)
8833 } else {
8834 // Fallback for providers without edit_preview
8835 crate::edit_prediction_fallback_text(edits, cx)
8836 };
8837
8838 let styled_text = highlighted_edits.to_styled_text(&style.text);
8839 let line_count = highlighted_edits.text.lines().count();
8840
8841 const BORDER_WIDTH: Pixels = px(1.);
8842
8843 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8844 let has_keybind = keybind.is_some();
8845
8846 let mut element = h_flex()
8847 .items_start()
8848 .child(
8849 h_flex()
8850 .bg(cx.theme().colors().editor_background)
8851 .border(BORDER_WIDTH)
8852 .shadow_xs()
8853 .border_color(cx.theme().colors().border)
8854 .rounded_l_lg()
8855 .when(line_count > 1, |el| el.rounded_br_lg())
8856 .pr_1()
8857 .child(styled_text),
8858 )
8859 .child(
8860 h_flex()
8861 .h(line_height + BORDER_WIDTH * 2.)
8862 .px_1p5()
8863 .gap_1()
8864 // Workaround: For some reason, there's a gap if we don't do this
8865 .ml(-BORDER_WIDTH)
8866 .shadow(vec![gpui::BoxShadow {
8867 color: gpui::black().opacity(0.05),
8868 offset: point(px(1.), px(1.)),
8869 blur_radius: px(2.),
8870 spread_radius: px(0.),
8871 }])
8872 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8873 .border(BORDER_WIDTH)
8874 .border_color(cx.theme().colors().border)
8875 .rounded_r_lg()
8876 .id("edit_prediction_diff_popover_keybind")
8877 .when(!has_keybind, |el| {
8878 let status_colors = cx.theme().status();
8879
8880 el.bg(status_colors.error_background)
8881 .border_color(status_colors.error.opacity(0.6))
8882 .child(Icon::new(IconName::Info).color(Color::Error))
8883 .cursor_default()
8884 .hoverable_tooltip(move |_window, cx| {
8885 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8886 })
8887 })
8888 .children(keybind),
8889 )
8890 .into_any();
8891
8892 let longest_row =
8893 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8894 let longest_line_width = if visible_row_range.contains(&longest_row) {
8895 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8896 } else {
8897 layout_line(
8898 longest_row,
8899 editor_snapshot,
8900 style,
8901 editor_width,
8902 |_| false,
8903 window,
8904 cx,
8905 )
8906 .width
8907 };
8908
8909 let viewport_bounds =
8910 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8911 right: -right_margin,
8912 ..Default::default()
8913 });
8914
8915 let x_after_longest =
8916 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8917 - scroll_pixel_position.x;
8918
8919 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8920
8921 // Fully visible if it can be displayed within the window (allow overlapping other
8922 // panes). However, this is only allowed if the popover starts within text_bounds.
8923 let can_position_to_the_right = x_after_longest < text_bounds.right()
8924 && x_after_longest + element_bounds.width < viewport_bounds.right();
8925
8926 let mut origin = if can_position_to_the_right {
8927 point(
8928 x_after_longest,
8929 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8930 - scroll_pixel_position.y,
8931 )
8932 } else {
8933 let cursor_row = newest_selection_head.map(|head| head.row());
8934 let above_edit = edit_start
8935 .row()
8936 .0
8937 .checked_sub(line_count as u32)
8938 .map(DisplayRow);
8939 let below_edit = Some(edit_end.row() + 1);
8940 let above_cursor =
8941 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8942 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8943
8944 // Place the edit popover adjacent to the edit if there is a location
8945 // available that is onscreen and does not obscure the cursor. Otherwise,
8946 // place it adjacent to the cursor.
8947 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8948 .into_iter()
8949 .flatten()
8950 .find(|&start_row| {
8951 let end_row = start_row + line_count as u32;
8952 visible_row_range.contains(&start_row)
8953 && visible_row_range.contains(&end_row)
8954 && cursor_row.map_or(true, |cursor_row| {
8955 !((start_row..end_row).contains(&cursor_row))
8956 })
8957 })?;
8958
8959 content_origin
8960 + point(
8961 -scroll_pixel_position.x,
8962 row_target.as_f32() * line_height - scroll_pixel_position.y,
8963 )
8964 };
8965
8966 origin.x -= BORDER_WIDTH;
8967
8968 window.defer_draw(element, origin, 1);
8969
8970 // Do not return an element, since it will already be drawn due to defer_draw.
8971 None
8972 }
8973
8974 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8975 px(30.)
8976 }
8977
8978 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8979 if self.read_only(cx) {
8980 cx.theme().players().read_only()
8981 } else {
8982 self.style.as_ref().unwrap().local_player
8983 }
8984 }
8985
8986 fn render_edit_prediction_accept_keybind(
8987 &self,
8988 window: &mut Window,
8989 cx: &App,
8990 ) -> Option<AnyElement> {
8991 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8992 let accept_keystroke = accept_binding.keystroke()?;
8993
8994 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8995
8996 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8997 Color::Accent
8998 } else {
8999 Color::Muted
9000 };
9001
9002 h_flex()
9003 .px_0p5()
9004 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9005 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9006 .text_size(TextSize::XSmall.rems(cx))
9007 .child(h_flex().children(ui::render_modifiers(
9008 &accept_keystroke.modifiers,
9009 PlatformStyle::platform(),
9010 Some(modifiers_color),
9011 Some(IconSize::XSmall.rems().into()),
9012 true,
9013 )))
9014 .when(is_platform_style_mac, |parent| {
9015 parent.child(accept_keystroke.key.clone())
9016 })
9017 .when(!is_platform_style_mac, |parent| {
9018 parent.child(
9019 Key::new(
9020 util::capitalize(&accept_keystroke.key),
9021 Some(Color::Default),
9022 )
9023 .size(Some(IconSize::XSmall.rems().into())),
9024 )
9025 })
9026 .into_any()
9027 .into()
9028 }
9029
9030 fn render_edit_prediction_line_popover(
9031 &self,
9032 label: impl Into<SharedString>,
9033 icon: Option<IconName>,
9034 window: &mut Window,
9035 cx: &App,
9036 ) -> Option<Stateful<Div>> {
9037 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9038
9039 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9040 let has_keybind = keybind.is_some();
9041
9042 let result = h_flex()
9043 .id("ep-line-popover")
9044 .py_0p5()
9045 .pl_1()
9046 .pr(padding_right)
9047 .gap_1()
9048 .rounded_md()
9049 .border_1()
9050 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9051 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9052 .shadow_xs()
9053 .when(!has_keybind, |el| {
9054 let status_colors = cx.theme().status();
9055
9056 el.bg(status_colors.error_background)
9057 .border_color(status_colors.error.opacity(0.6))
9058 .pl_2()
9059 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9060 .cursor_default()
9061 .hoverable_tooltip(move |_window, cx| {
9062 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9063 })
9064 })
9065 .children(keybind)
9066 .child(
9067 Label::new(label)
9068 .size(LabelSize::Small)
9069 .when(!has_keybind, |el| {
9070 el.color(cx.theme().status().error.into()).strikethrough()
9071 }),
9072 )
9073 .when(!has_keybind, |el| {
9074 el.child(
9075 h_flex().ml_1().child(
9076 Icon::new(IconName::Info)
9077 .size(IconSize::Small)
9078 .color(cx.theme().status().error.into()),
9079 ),
9080 )
9081 })
9082 .when_some(icon, |element, icon| {
9083 element.child(
9084 div()
9085 .mt(px(1.5))
9086 .child(Icon::new(icon).size(IconSize::Small)),
9087 )
9088 });
9089
9090 Some(result)
9091 }
9092
9093 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9094 let accent_color = cx.theme().colors().text_accent;
9095 let editor_bg_color = cx.theme().colors().editor_background;
9096 editor_bg_color.blend(accent_color.opacity(0.1))
9097 }
9098
9099 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9100 let accent_color = cx.theme().colors().text_accent;
9101 let editor_bg_color = cx.theme().colors().editor_background;
9102 editor_bg_color.blend(accent_color.opacity(0.6))
9103 }
9104 fn get_prediction_provider_icon_name(
9105 provider: &Option<RegisteredEditPredictionProvider>,
9106 ) -> IconName {
9107 match provider {
9108 Some(provider) => match provider.provider.name() {
9109 "copilot" => IconName::Copilot,
9110 "supermaven" => IconName::Supermaven,
9111 _ => IconName::ZedPredict,
9112 },
9113 None => IconName::ZedPredict,
9114 }
9115 }
9116
9117 fn render_edit_prediction_cursor_popover(
9118 &self,
9119 min_width: Pixels,
9120 max_width: Pixels,
9121 cursor_point: Point,
9122 style: &EditorStyle,
9123 accept_keystroke: Option<&gpui::Keystroke>,
9124 _window: &Window,
9125 cx: &mut Context<Editor>,
9126 ) -> Option<AnyElement> {
9127 let provider = self.edit_prediction_provider.as_ref()?;
9128 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9129
9130 if provider.provider.needs_terms_acceptance(cx) {
9131 return Some(
9132 h_flex()
9133 .min_w(min_width)
9134 .flex_1()
9135 .px_2()
9136 .py_1()
9137 .gap_3()
9138 .elevation_2(cx)
9139 .hover(|style| style.bg(cx.theme().colors().element_hover))
9140 .id("accept-terms")
9141 .cursor_pointer()
9142 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9143 .on_click(cx.listener(|this, _event, window, cx| {
9144 cx.stop_propagation();
9145 this.report_editor_event(ReportEditorEvent::ZetaTosClicked, None, cx);
9146 window.dispatch_action(
9147 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9148 cx,
9149 );
9150 }))
9151 .child(
9152 h_flex()
9153 .flex_1()
9154 .gap_2()
9155 .child(Icon::new(provider_icon))
9156 .child(Label::new("Accept Terms of Service"))
9157 .child(div().w_full())
9158 .child(
9159 Icon::new(IconName::ArrowUpRight)
9160 .color(Color::Muted)
9161 .size(IconSize::Small),
9162 )
9163 .into_any_element(),
9164 )
9165 .into_any(),
9166 );
9167 }
9168
9169 let is_refreshing = provider.provider.is_refreshing(cx);
9170
9171 fn pending_completion_container(icon: IconName) -> Div {
9172 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9173 }
9174
9175 let completion = match &self.active_edit_prediction {
9176 Some(prediction) => {
9177 if !self.has_visible_completions_menu() {
9178 const RADIUS: Pixels = px(6.);
9179 const BORDER_WIDTH: Pixels = px(1.);
9180
9181 return Some(
9182 h_flex()
9183 .elevation_2(cx)
9184 .border(BORDER_WIDTH)
9185 .border_color(cx.theme().colors().border)
9186 .when(accept_keystroke.is_none(), |el| {
9187 el.border_color(cx.theme().status().error)
9188 })
9189 .rounded(RADIUS)
9190 .rounded_tl(px(0.))
9191 .overflow_hidden()
9192 .child(div().px_1p5().child(match &prediction.completion {
9193 EditPrediction::Move { target, snapshot } => {
9194 use text::ToPoint as _;
9195 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9196 {
9197 Icon::new(IconName::ZedPredictDown)
9198 } else {
9199 Icon::new(IconName::ZedPredictUp)
9200 }
9201 }
9202 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9203 }))
9204 .child(
9205 h_flex()
9206 .gap_1()
9207 .py_1()
9208 .px_2()
9209 .rounded_r(RADIUS - BORDER_WIDTH)
9210 .border_l_1()
9211 .border_color(cx.theme().colors().border)
9212 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9213 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9214 el.child(
9215 Label::new("Hold")
9216 .size(LabelSize::Small)
9217 .when(accept_keystroke.is_none(), |el| {
9218 el.strikethrough()
9219 })
9220 .line_height_style(LineHeightStyle::UiLabel),
9221 )
9222 })
9223 .id("edit_prediction_cursor_popover_keybind")
9224 .when(accept_keystroke.is_none(), |el| {
9225 let status_colors = cx.theme().status();
9226
9227 el.bg(status_colors.error_background)
9228 .border_color(status_colors.error.opacity(0.6))
9229 .child(Icon::new(IconName::Info).color(Color::Error))
9230 .cursor_default()
9231 .hoverable_tooltip(move |_window, cx| {
9232 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9233 .into()
9234 })
9235 })
9236 .when_some(
9237 accept_keystroke.as_ref(),
9238 |el, accept_keystroke| {
9239 el.child(h_flex().children(ui::render_modifiers(
9240 &accept_keystroke.modifiers,
9241 PlatformStyle::platform(),
9242 Some(Color::Default),
9243 Some(IconSize::XSmall.rems().into()),
9244 false,
9245 )))
9246 },
9247 ),
9248 )
9249 .into_any(),
9250 );
9251 }
9252
9253 self.render_edit_prediction_cursor_popover_preview(
9254 prediction,
9255 cursor_point,
9256 style,
9257 cx,
9258 )?
9259 }
9260
9261 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9262 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9263 stale_completion,
9264 cursor_point,
9265 style,
9266 cx,
9267 )?,
9268
9269 None => pending_completion_container(provider_icon)
9270 .child(Label::new("...").size(LabelSize::Small)),
9271 },
9272
9273 None => pending_completion_container(provider_icon)
9274 .child(Label::new("...").size(LabelSize::Small)),
9275 };
9276
9277 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9278 completion
9279 .with_animation(
9280 "loading-completion",
9281 Animation::new(Duration::from_secs(2))
9282 .repeat()
9283 .with_easing(pulsating_between(0.4, 0.8)),
9284 |label, delta| label.opacity(delta),
9285 )
9286 .into_any_element()
9287 } else {
9288 completion.into_any_element()
9289 };
9290
9291 let has_completion = self.active_edit_prediction.is_some();
9292
9293 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9294 Some(
9295 h_flex()
9296 .min_w(min_width)
9297 .max_w(max_width)
9298 .flex_1()
9299 .elevation_2(cx)
9300 .border_color(cx.theme().colors().border)
9301 .child(
9302 div()
9303 .flex_1()
9304 .py_1()
9305 .px_2()
9306 .overflow_hidden()
9307 .child(completion),
9308 )
9309 .when_some(accept_keystroke, |el, accept_keystroke| {
9310 if !accept_keystroke.modifiers.modified() {
9311 return el;
9312 }
9313
9314 el.child(
9315 h_flex()
9316 .h_full()
9317 .border_l_1()
9318 .rounded_r_lg()
9319 .border_color(cx.theme().colors().border)
9320 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9321 .gap_1()
9322 .py_1()
9323 .px_2()
9324 .child(
9325 h_flex()
9326 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9327 .when(is_platform_style_mac, |parent| parent.gap_1())
9328 .child(h_flex().children(ui::render_modifiers(
9329 &accept_keystroke.modifiers,
9330 PlatformStyle::platform(),
9331 Some(if !has_completion {
9332 Color::Muted
9333 } else {
9334 Color::Default
9335 }),
9336 None,
9337 false,
9338 ))),
9339 )
9340 .child(Label::new("Preview").into_any_element())
9341 .opacity(if has_completion { 1.0 } else { 0.4 }),
9342 )
9343 })
9344 .into_any(),
9345 )
9346 }
9347
9348 fn render_edit_prediction_cursor_popover_preview(
9349 &self,
9350 completion: &EditPredictionState,
9351 cursor_point: Point,
9352 style: &EditorStyle,
9353 cx: &mut Context<Editor>,
9354 ) -> Option<Div> {
9355 use text::ToPoint as _;
9356
9357 fn render_relative_row_jump(
9358 prefix: impl Into<String>,
9359 current_row: u32,
9360 target_row: u32,
9361 ) -> Div {
9362 let (row_diff, arrow) = if target_row < current_row {
9363 (current_row - target_row, IconName::ArrowUp)
9364 } else {
9365 (target_row - current_row, IconName::ArrowDown)
9366 };
9367
9368 h_flex()
9369 .child(
9370 Label::new(format!("{}{}", prefix.into(), row_diff))
9371 .color(Color::Muted)
9372 .size(LabelSize::Small),
9373 )
9374 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9375 }
9376
9377 let supports_jump = self
9378 .edit_prediction_provider
9379 .as_ref()
9380 .map(|provider| provider.provider.supports_jump_to_edit())
9381 .unwrap_or(true);
9382
9383 match &completion.completion {
9384 EditPrediction::Move {
9385 target, snapshot, ..
9386 } => {
9387 if !supports_jump {
9388 return None;
9389 }
9390
9391 Some(
9392 h_flex()
9393 .px_2()
9394 .gap_2()
9395 .flex_1()
9396 .child(
9397 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9398 Icon::new(IconName::ZedPredictDown)
9399 } else {
9400 Icon::new(IconName::ZedPredictUp)
9401 },
9402 )
9403 .child(Label::new("Jump to Edit")),
9404 )
9405 }
9406
9407 EditPrediction::Edit {
9408 edits,
9409 edit_preview,
9410 snapshot,
9411 display_mode: _,
9412 } => {
9413 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9414
9415 let (highlighted_edits, has_more_lines) =
9416 if let Some(edit_preview) = edit_preview.as_ref() {
9417 crate::edit_prediction_edit_text(&snapshot, &edits, edit_preview, true, cx)
9418 .first_line_preview()
9419 } else {
9420 crate::edit_prediction_fallback_text(&edits, cx).first_line_preview()
9421 };
9422
9423 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9424 .with_default_highlights(&style.text, highlighted_edits.highlights);
9425
9426 let preview = h_flex()
9427 .gap_1()
9428 .min_w_16()
9429 .child(styled_text)
9430 .when(has_more_lines, |parent| parent.child("…"));
9431
9432 let left = if supports_jump && first_edit_row != cursor_point.row {
9433 render_relative_row_jump("", cursor_point.row, first_edit_row)
9434 .into_any_element()
9435 } else {
9436 let icon_name =
9437 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9438 Icon::new(icon_name).into_any_element()
9439 };
9440
9441 Some(
9442 h_flex()
9443 .h_full()
9444 .flex_1()
9445 .gap_2()
9446 .pr_1()
9447 .overflow_x_hidden()
9448 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9449 .child(left)
9450 .child(preview),
9451 )
9452 }
9453 }
9454 }
9455
9456 pub fn render_context_menu(
9457 &self,
9458 style: &EditorStyle,
9459 max_height_in_lines: u32,
9460 window: &mut Window,
9461 cx: &mut Context<Editor>,
9462 ) -> Option<AnyElement> {
9463 let menu = self.context_menu.borrow();
9464 let menu = menu.as_ref()?;
9465 if !menu.visible() {
9466 return None;
9467 };
9468 Some(menu.render(style, max_height_in_lines, window, cx))
9469 }
9470
9471 fn render_context_menu_aside(
9472 &mut self,
9473 max_size: Size<Pixels>,
9474 window: &mut Window,
9475 cx: &mut Context<Editor>,
9476 ) -> Option<AnyElement> {
9477 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9478 if menu.visible() {
9479 menu.render_aside(max_size, window, cx)
9480 } else {
9481 None
9482 }
9483 })
9484 }
9485
9486 fn hide_context_menu(
9487 &mut self,
9488 window: &mut Window,
9489 cx: &mut Context<Self>,
9490 ) -> Option<CodeContextMenu> {
9491 cx.notify();
9492 self.completion_tasks.clear();
9493 let context_menu = self.context_menu.borrow_mut().take();
9494 self.stale_edit_prediction_in_menu.take();
9495 self.update_visible_edit_prediction(window, cx);
9496 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9497 if let Some(completion_provider) = &self.completion_provider {
9498 completion_provider.selection_changed(None, window, cx);
9499 }
9500 }
9501 context_menu
9502 }
9503
9504 fn show_snippet_choices(
9505 &mut self,
9506 choices: &Vec<String>,
9507 selection: Range<Anchor>,
9508 cx: &mut Context<Self>,
9509 ) {
9510 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9511 (Some(a), Some(b)) if a == b => a,
9512 _ => {
9513 log::error!("expected anchor range to have matching buffer IDs");
9514 return;
9515 }
9516 };
9517 let multi_buffer = self.buffer().read(cx);
9518 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9519 return;
9520 };
9521
9522 let id = post_inc(&mut self.next_completion_id);
9523 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9524 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9525 CompletionsMenu::new_snippet_choices(
9526 id,
9527 true,
9528 choices,
9529 selection,
9530 buffer,
9531 snippet_sort_order,
9532 ),
9533 ));
9534 }
9535
9536 pub fn insert_snippet(
9537 &mut self,
9538 insertion_ranges: &[Range<usize>],
9539 snippet: Snippet,
9540 window: &mut Window,
9541 cx: &mut Context<Self>,
9542 ) -> Result<()> {
9543 struct Tabstop<T> {
9544 is_end_tabstop: bool,
9545 ranges: Vec<Range<T>>,
9546 choices: Option<Vec<String>>,
9547 }
9548
9549 let tabstops = self.buffer.update(cx, |buffer, cx| {
9550 let snippet_text: Arc<str> = snippet.text.clone().into();
9551 let edits = insertion_ranges
9552 .iter()
9553 .cloned()
9554 .map(|range| (range, snippet_text.clone()));
9555 let autoindent_mode = AutoindentMode::Block {
9556 original_indent_columns: Vec::new(),
9557 };
9558 buffer.edit(edits, Some(autoindent_mode), cx);
9559
9560 let snapshot = &*buffer.read(cx);
9561 let snippet = &snippet;
9562 snippet
9563 .tabstops
9564 .iter()
9565 .map(|tabstop| {
9566 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9567 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9568 });
9569 let mut tabstop_ranges = tabstop
9570 .ranges
9571 .iter()
9572 .flat_map(|tabstop_range| {
9573 let mut delta = 0_isize;
9574 insertion_ranges.iter().map(move |insertion_range| {
9575 let insertion_start = insertion_range.start as isize + delta;
9576 delta +=
9577 snippet.text.len() as isize - insertion_range.len() as isize;
9578
9579 let start = ((insertion_start + tabstop_range.start) as usize)
9580 .min(snapshot.len());
9581 let end = ((insertion_start + tabstop_range.end) as usize)
9582 .min(snapshot.len());
9583 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9584 })
9585 })
9586 .collect::<Vec<_>>();
9587 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9588
9589 Tabstop {
9590 is_end_tabstop,
9591 ranges: tabstop_ranges,
9592 choices: tabstop.choices.clone(),
9593 }
9594 })
9595 .collect::<Vec<_>>()
9596 });
9597 if let Some(tabstop) = tabstops.first() {
9598 self.change_selections(Default::default(), window, cx, |s| {
9599 // Reverse order so that the first range is the newest created selection.
9600 // Completions will use it and autoscroll will prioritize it.
9601 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9602 });
9603
9604 if let Some(choices) = &tabstop.choices {
9605 if let Some(selection) = tabstop.ranges.first() {
9606 self.show_snippet_choices(choices, selection.clone(), cx)
9607 }
9608 }
9609
9610 // If we're already at the last tabstop and it's at the end of the snippet,
9611 // we're done, we don't need to keep the state around.
9612 if !tabstop.is_end_tabstop {
9613 let choices = tabstops
9614 .iter()
9615 .map(|tabstop| tabstop.choices.clone())
9616 .collect();
9617
9618 let ranges = tabstops
9619 .into_iter()
9620 .map(|tabstop| tabstop.ranges)
9621 .collect::<Vec<_>>();
9622
9623 self.snippet_stack.push(SnippetState {
9624 active_index: 0,
9625 ranges,
9626 choices,
9627 });
9628 }
9629
9630 // Check whether the just-entered snippet ends with an auto-closable bracket.
9631 if self.autoclose_regions.is_empty() {
9632 let snapshot = self.buffer.read(cx).snapshot(cx);
9633 let mut all_selections = self.selections.all::<Point>(cx);
9634 for selection in &mut all_selections {
9635 let selection_head = selection.head();
9636 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9637 continue;
9638 };
9639
9640 let mut bracket_pair = None;
9641 let max_lookup_length = scope
9642 .brackets()
9643 .map(|(pair, _)| {
9644 pair.start
9645 .as_str()
9646 .chars()
9647 .count()
9648 .max(pair.end.as_str().chars().count())
9649 })
9650 .max();
9651 if let Some(max_lookup_length) = max_lookup_length {
9652 let next_text = snapshot
9653 .chars_at(selection_head)
9654 .take(max_lookup_length)
9655 .collect::<String>();
9656 let prev_text = snapshot
9657 .reversed_chars_at(selection_head)
9658 .take(max_lookup_length)
9659 .collect::<String>();
9660
9661 for (pair, enabled) in scope.brackets() {
9662 if enabled
9663 && pair.close
9664 && prev_text.starts_with(pair.start.as_str())
9665 && next_text.starts_with(pair.end.as_str())
9666 {
9667 bracket_pair = Some(pair.clone());
9668 break;
9669 }
9670 }
9671 }
9672
9673 if let Some(pair) = bracket_pair {
9674 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9675 let autoclose_enabled =
9676 self.use_autoclose && snapshot_settings.use_autoclose;
9677 if autoclose_enabled {
9678 let start = snapshot.anchor_after(selection_head);
9679 let end = snapshot.anchor_after(selection_head);
9680 self.autoclose_regions.push(AutocloseRegion {
9681 selection_id: selection.id,
9682 range: start..end,
9683 pair,
9684 });
9685 }
9686 }
9687 }
9688 }
9689 }
9690 Ok(())
9691 }
9692
9693 pub fn move_to_next_snippet_tabstop(
9694 &mut self,
9695 window: &mut Window,
9696 cx: &mut Context<Self>,
9697 ) -> bool {
9698 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9699 }
9700
9701 pub fn move_to_prev_snippet_tabstop(
9702 &mut self,
9703 window: &mut Window,
9704 cx: &mut Context<Self>,
9705 ) -> bool {
9706 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9707 }
9708
9709 pub fn move_to_snippet_tabstop(
9710 &mut self,
9711 bias: Bias,
9712 window: &mut Window,
9713 cx: &mut Context<Self>,
9714 ) -> bool {
9715 if let Some(mut snippet) = self.snippet_stack.pop() {
9716 match bias {
9717 Bias::Left => {
9718 if snippet.active_index > 0 {
9719 snippet.active_index -= 1;
9720 } else {
9721 self.snippet_stack.push(snippet);
9722 return false;
9723 }
9724 }
9725 Bias::Right => {
9726 if snippet.active_index + 1 < snippet.ranges.len() {
9727 snippet.active_index += 1;
9728 } else {
9729 self.snippet_stack.push(snippet);
9730 return false;
9731 }
9732 }
9733 }
9734 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9735 self.change_selections(Default::default(), window, cx, |s| {
9736 // Reverse order so that the first range is the newest created selection.
9737 // Completions will use it and autoscroll will prioritize it.
9738 s.select_ranges(current_ranges.iter().rev().cloned())
9739 });
9740
9741 if let Some(choices) = &snippet.choices[snippet.active_index] {
9742 if let Some(selection) = current_ranges.first() {
9743 self.show_snippet_choices(&choices, selection.clone(), cx);
9744 }
9745 }
9746
9747 // If snippet state is not at the last tabstop, push it back on the stack
9748 if snippet.active_index + 1 < snippet.ranges.len() {
9749 self.snippet_stack.push(snippet);
9750 }
9751 return true;
9752 }
9753 }
9754
9755 false
9756 }
9757
9758 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9759 self.transact(window, cx, |this, window, cx| {
9760 this.select_all(&SelectAll, window, cx);
9761 this.insert("", window, cx);
9762 });
9763 }
9764
9765 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9767 self.transact(window, cx, |this, window, cx| {
9768 this.select_autoclose_pair(window, cx);
9769 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9770 if !this.linked_edit_ranges.is_empty() {
9771 let selections = this.selections.all::<MultiBufferPoint>(cx);
9772 let snapshot = this.buffer.read(cx).snapshot(cx);
9773
9774 for selection in selections.iter() {
9775 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9776 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9777 if selection_start.buffer_id != selection_end.buffer_id {
9778 continue;
9779 }
9780 if let Some(ranges) =
9781 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9782 {
9783 for (buffer, entries) in ranges {
9784 linked_ranges.entry(buffer).or_default().extend(entries);
9785 }
9786 }
9787 }
9788 }
9789
9790 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9791 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9792 for selection in &mut selections {
9793 if selection.is_empty() {
9794 let old_head = selection.head();
9795 let mut new_head =
9796 movement::left(&display_map, old_head.to_display_point(&display_map))
9797 .to_point(&display_map);
9798 if let Some((buffer, line_buffer_range)) = display_map
9799 .buffer_snapshot
9800 .buffer_line_for_row(MultiBufferRow(old_head.row))
9801 {
9802 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9803 let indent_len = match indent_size.kind {
9804 IndentKind::Space => {
9805 buffer.settings_at(line_buffer_range.start, cx).tab_size
9806 }
9807 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9808 };
9809 if old_head.column <= indent_size.len && old_head.column > 0 {
9810 let indent_len = indent_len.get();
9811 new_head = cmp::min(
9812 new_head,
9813 MultiBufferPoint::new(
9814 old_head.row,
9815 ((old_head.column - 1) / indent_len) * indent_len,
9816 ),
9817 );
9818 }
9819 }
9820
9821 selection.set_head(new_head, SelectionGoal::None);
9822 }
9823 }
9824
9825 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9826 this.insert("", window, cx);
9827 let empty_str: Arc<str> = Arc::from("");
9828 for (buffer, edits) in linked_ranges {
9829 let snapshot = buffer.read(cx).snapshot();
9830 use text::ToPoint as TP;
9831
9832 let edits = edits
9833 .into_iter()
9834 .map(|range| {
9835 let end_point = TP::to_point(&range.end, &snapshot);
9836 let mut start_point = TP::to_point(&range.start, &snapshot);
9837
9838 if end_point == start_point {
9839 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9840 .saturating_sub(1);
9841 start_point =
9842 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9843 };
9844
9845 (start_point..end_point, empty_str.clone())
9846 })
9847 .sorted_by_key(|(range, _)| range.start)
9848 .collect::<Vec<_>>();
9849 buffer.update(cx, |this, cx| {
9850 this.edit(edits, None, cx);
9851 })
9852 }
9853 this.refresh_edit_prediction(true, false, window, cx);
9854 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9855 });
9856 }
9857
9858 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9859 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9860 self.transact(window, cx, |this, window, cx| {
9861 this.change_selections(Default::default(), window, cx, |s| {
9862 s.move_with(|map, selection| {
9863 if selection.is_empty() {
9864 let cursor = movement::right(map, selection.head());
9865 selection.end = cursor;
9866 selection.reversed = true;
9867 selection.goal = SelectionGoal::None;
9868 }
9869 })
9870 });
9871 this.insert("", window, cx);
9872 this.refresh_edit_prediction(true, false, window, cx);
9873 });
9874 }
9875
9876 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9877 if self.mode.is_single_line() {
9878 cx.propagate();
9879 return;
9880 }
9881
9882 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9883 if self.move_to_prev_snippet_tabstop(window, cx) {
9884 return;
9885 }
9886 self.outdent(&Outdent, window, cx);
9887 }
9888
9889 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9890 if self.mode.is_single_line() {
9891 cx.propagate();
9892 return;
9893 }
9894
9895 if self.move_to_next_snippet_tabstop(window, cx) {
9896 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9897 return;
9898 }
9899 if self.read_only(cx) {
9900 return;
9901 }
9902 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9903 let mut selections = self.selections.all_adjusted(cx);
9904 let buffer = self.buffer.read(cx);
9905 let snapshot = buffer.snapshot(cx);
9906 let rows_iter = selections.iter().map(|s| s.head().row);
9907 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9908
9909 let has_some_cursor_in_whitespace = selections
9910 .iter()
9911 .filter(|selection| selection.is_empty())
9912 .any(|selection| {
9913 let cursor = selection.head();
9914 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9915 cursor.column < current_indent.len
9916 });
9917
9918 let mut edits = Vec::new();
9919 let mut prev_edited_row = 0;
9920 let mut row_delta = 0;
9921 for selection in &mut selections {
9922 if selection.start.row != prev_edited_row {
9923 row_delta = 0;
9924 }
9925 prev_edited_row = selection.end.row;
9926
9927 // If the selection is non-empty, then increase the indentation of the selected lines.
9928 if !selection.is_empty() {
9929 row_delta =
9930 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9931 continue;
9932 }
9933
9934 let cursor = selection.head();
9935 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9936 if let Some(suggested_indent) =
9937 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9938 {
9939 // Don't do anything if already at suggested indent
9940 // and there is any other cursor which is not
9941 if has_some_cursor_in_whitespace
9942 && cursor.column == current_indent.len
9943 && current_indent.len == suggested_indent.len
9944 {
9945 continue;
9946 }
9947
9948 // Adjust line and move cursor to suggested indent
9949 // if cursor is not at suggested indent
9950 if cursor.column < suggested_indent.len
9951 && cursor.column <= current_indent.len
9952 && current_indent.len <= suggested_indent.len
9953 {
9954 selection.start = Point::new(cursor.row, suggested_indent.len);
9955 selection.end = selection.start;
9956 if row_delta == 0 {
9957 edits.extend(Buffer::edit_for_indent_size_adjustment(
9958 cursor.row,
9959 current_indent,
9960 suggested_indent,
9961 ));
9962 row_delta = suggested_indent.len - current_indent.len;
9963 }
9964 continue;
9965 }
9966
9967 // If current indent is more than suggested indent
9968 // only move cursor to current indent and skip indent
9969 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9970 selection.start = Point::new(cursor.row, current_indent.len);
9971 selection.end = selection.start;
9972 continue;
9973 }
9974 }
9975
9976 // Otherwise, insert a hard or soft tab.
9977 let settings = buffer.language_settings_at(cursor, cx);
9978 let tab_size = if settings.hard_tabs {
9979 IndentSize::tab()
9980 } else {
9981 let tab_size = settings.tab_size.get();
9982 let indent_remainder = snapshot
9983 .text_for_range(Point::new(cursor.row, 0)..cursor)
9984 .flat_map(str::chars)
9985 .fold(row_delta % tab_size, |counter: u32, c| {
9986 if c == '\t' {
9987 0
9988 } else {
9989 (counter + 1) % tab_size
9990 }
9991 });
9992
9993 let chars_to_next_tab_stop = tab_size - indent_remainder;
9994 IndentSize::spaces(chars_to_next_tab_stop)
9995 };
9996 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9997 selection.end = selection.start;
9998 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9999 row_delta += tab_size.len;
10000 }
10001
10002 self.transact(window, cx, |this, window, cx| {
10003 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10004 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10005 this.refresh_edit_prediction(true, false, window, cx);
10006 });
10007 }
10008
10009 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10010 if self.read_only(cx) {
10011 return;
10012 }
10013 if self.mode.is_single_line() {
10014 cx.propagate();
10015 return;
10016 }
10017
10018 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10019 let mut selections = self.selections.all::<Point>(cx);
10020 let mut prev_edited_row = 0;
10021 let mut row_delta = 0;
10022 let mut edits = Vec::new();
10023 let buffer = self.buffer.read(cx);
10024 let snapshot = buffer.snapshot(cx);
10025 for selection in &mut selections {
10026 if selection.start.row != prev_edited_row {
10027 row_delta = 0;
10028 }
10029 prev_edited_row = selection.end.row;
10030
10031 row_delta =
10032 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10033 }
10034
10035 self.transact(window, cx, |this, window, cx| {
10036 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10037 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10038 });
10039 }
10040
10041 fn indent_selection(
10042 buffer: &MultiBuffer,
10043 snapshot: &MultiBufferSnapshot,
10044 selection: &mut Selection<Point>,
10045 edits: &mut Vec<(Range<Point>, String)>,
10046 delta_for_start_row: u32,
10047 cx: &App,
10048 ) -> u32 {
10049 let settings = buffer.language_settings_at(selection.start, cx);
10050 let tab_size = settings.tab_size.get();
10051 let indent_kind = if settings.hard_tabs {
10052 IndentKind::Tab
10053 } else {
10054 IndentKind::Space
10055 };
10056 let mut start_row = selection.start.row;
10057 let mut end_row = selection.end.row + 1;
10058
10059 // If a selection ends at the beginning of a line, don't indent
10060 // that last line.
10061 if selection.end.column == 0 && selection.end.row > selection.start.row {
10062 end_row -= 1;
10063 }
10064
10065 // Avoid re-indenting a row that has already been indented by a
10066 // previous selection, but still update this selection's column
10067 // to reflect that indentation.
10068 if delta_for_start_row > 0 {
10069 start_row += 1;
10070 selection.start.column += delta_for_start_row;
10071 if selection.end.row == selection.start.row {
10072 selection.end.column += delta_for_start_row;
10073 }
10074 }
10075
10076 let mut delta_for_end_row = 0;
10077 let has_multiple_rows = start_row + 1 != end_row;
10078 for row in start_row..end_row {
10079 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10080 let indent_delta = match (current_indent.kind, indent_kind) {
10081 (IndentKind::Space, IndentKind::Space) => {
10082 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10083 IndentSize::spaces(columns_to_next_tab_stop)
10084 }
10085 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10086 (_, IndentKind::Tab) => IndentSize::tab(),
10087 };
10088
10089 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10090 0
10091 } else {
10092 selection.start.column
10093 };
10094 let row_start = Point::new(row, start);
10095 edits.push((
10096 row_start..row_start,
10097 indent_delta.chars().collect::<String>(),
10098 ));
10099
10100 // Update this selection's endpoints to reflect the indentation.
10101 if row == selection.start.row {
10102 selection.start.column += indent_delta.len;
10103 }
10104 if row == selection.end.row {
10105 selection.end.column += indent_delta.len;
10106 delta_for_end_row = indent_delta.len;
10107 }
10108 }
10109
10110 if selection.start.row == selection.end.row {
10111 delta_for_start_row + delta_for_end_row
10112 } else {
10113 delta_for_end_row
10114 }
10115 }
10116
10117 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10118 if self.read_only(cx) {
10119 return;
10120 }
10121 if self.mode.is_single_line() {
10122 cx.propagate();
10123 return;
10124 }
10125
10126 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10127 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10128 let selections = self.selections.all::<Point>(cx);
10129 let mut deletion_ranges = Vec::new();
10130 let mut last_outdent = None;
10131 {
10132 let buffer = self.buffer.read(cx);
10133 let snapshot = buffer.snapshot(cx);
10134 for selection in &selections {
10135 let settings = buffer.language_settings_at(selection.start, cx);
10136 let tab_size = settings.tab_size.get();
10137 let mut rows = selection.spanned_rows(false, &display_map);
10138
10139 // Avoid re-outdenting a row that has already been outdented by a
10140 // previous selection.
10141 if let Some(last_row) = last_outdent {
10142 if last_row == rows.start {
10143 rows.start = rows.start.next_row();
10144 }
10145 }
10146 let has_multiple_rows = rows.len() > 1;
10147 for row in rows.iter_rows() {
10148 let indent_size = snapshot.indent_size_for_line(row);
10149 if indent_size.len > 0 {
10150 let deletion_len = match indent_size.kind {
10151 IndentKind::Space => {
10152 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10153 if columns_to_prev_tab_stop == 0 {
10154 tab_size
10155 } else {
10156 columns_to_prev_tab_stop
10157 }
10158 }
10159 IndentKind::Tab => 1,
10160 };
10161 let start = if has_multiple_rows
10162 || deletion_len > selection.start.column
10163 || indent_size.len < selection.start.column
10164 {
10165 0
10166 } else {
10167 selection.start.column - deletion_len
10168 };
10169 deletion_ranges.push(
10170 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10171 );
10172 last_outdent = Some(row);
10173 }
10174 }
10175 }
10176 }
10177
10178 self.transact(window, cx, |this, window, cx| {
10179 this.buffer.update(cx, |buffer, cx| {
10180 let empty_str: Arc<str> = Arc::default();
10181 buffer.edit(
10182 deletion_ranges
10183 .into_iter()
10184 .map(|range| (range, empty_str.clone())),
10185 None,
10186 cx,
10187 );
10188 });
10189 let selections = this.selections.all::<usize>(cx);
10190 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10191 });
10192 }
10193
10194 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10195 if self.read_only(cx) {
10196 return;
10197 }
10198 if self.mode.is_single_line() {
10199 cx.propagate();
10200 return;
10201 }
10202
10203 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10204 let selections = self
10205 .selections
10206 .all::<usize>(cx)
10207 .into_iter()
10208 .map(|s| s.range());
10209
10210 self.transact(window, cx, |this, window, cx| {
10211 this.buffer.update(cx, |buffer, cx| {
10212 buffer.autoindent_ranges(selections, cx);
10213 });
10214 let selections = this.selections.all::<usize>(cx);
10215 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10216 });
10217 }
10218
10219 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10220 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10221 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10222 let selections = self.selections.all::<Point>(cx);
10223
10224 let mut new_cursors = Vec::new();
10225 let mut edit_ranges = Vec::new();
10226 let mut selections = selections.iter().peekable();
10227 while let Some(selection) = selections.next() {
10228 let mut rows = selection.spanned_rows(false, &display_map);
10229 let goal_display_column = selection.head().to_display_point(&display_map).column();
10230
10231 // Accumulate contiguous regions of rows that we want to delete.
10232 while let Some(next_selection) = selections.peek() {
10233 let next_rows = next_selection.spanned_rows(false, &display_map);
10234 if next_rows.start <= rows.end {
10235 rows.end = next_rows.end;
10236 selections.next().unwrap();
10237 } else {
10238 break;
10239 }
10240 }
10241
10242 let buffer = &display_map.buffer_snapshot;
10243 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10244 let edit_end;
10245 let cursor_buffer_row;
10246 if buffer.max_point().row >= rows.end.0 {
10247 // If there's a line after the range, delete the \n from the end of the row range
10248 // and position the cursor on the next line.
10249 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10250 cursor_buffer_row = rows.end;
10251 } else {
10252 // If there isn't a line after the range, delete the \n from the line before the
10253 // start of the row range and position the cursor there.
10254 edit_start = edit_start.saturating_sub(1);
10255 edit_end = buffer.len();
10256 cursor_buffer_row = rows.start.previous_row();
10257 }
10258
10259 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10260 *cursor.column_mut() =
10261 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10262
10263 new_cursors.push((
10264 selection.id,
10265 buffer.anchor_after(cursor.to_point(&display_map)),
10266 ));
10267 edit_ranges.push(edit_start..edit_end);
10268 }
10269
10270 self.transact(window, cx, |this, window, cx| {
10271 let buffer = this.buffer.update(cx, |buffer, cx| {
10272 let empty_str: Arc<str> = Arc::default();
10273 buffer.edit(
10274 edit_ranges
10275 .into_iter()
10276 .map(|range| (range, empty_str.clone())),
10277 None,
10278 cx,
10279 );
10280 buffer.snapshot(cx)
10281 });
10282 let new_selections = new_cursors
10283 .into_iter()
10284 .map(|(id, cursor)| {
10285 let cursor = cursor.to_point(&buffer);
10286 Selection {
10287 id,
10288 start: cursor,
10289 end: cursor,
10290 reversed: false,
10291 goal: SelectionGoal::None,
10292 }
10293 })
10294 .collect();
10295
10296 this.change_selections(Default::default(), window, cx, |s| {
10297 s.select(new_selections);
10298 });
10299 });
10300 }
10301
10302 pub fn join_lines_impl(
10303 &mut self,
10304 insert_whitespace: bool,
10305 window: &mut Window,
10306 cx: &mut Context<Self>,
10307 ) {
10308 if self.read_only(cx) {
10309 return;
10310 }
10311 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10312 for selection in self.selections.all::<Point>(cx) {
10313 let start = MultiBufferRow(selection.start.row);
10314 // Treat single line selections as if they include the next line. Otherwise this action
10315 // would do nothing for single line selections individual cursors.
10316 let end = if selection.start.row == selection.end.row {
10317 MultiBufferRow(selection.start.row + 1)
10318 } else {
10319 MultiBufferRow(selection.end.row)
10320 };
10321
10322 if let Some(last_row_range) = row_ranges.last_mut() {
10323 if start <= last_row_range.end {
10324 last_row_range.end = end;
10325 continue;
10326 }
10327 }
10328 row_ranges.push(start..end);
10329 }
10330
10331 let snapshot = self.buffer.read(cx).snapshot(cx);
10332 let mut cursor_positions = Vec::new();
10333 for row_range in &row_ranges {
10334 let anchor = snapshot.anchor_before(Point::new(
10335 row_range.end.previous_row().0,
10336 snapshot.line_len(row_range.end.previous_row()),
10337 ));
10338 cursor_positions.push(anchor..anchor);
10339 }
10340
10341 self.transact(window, cx, |this, window, cx| {
10342 for row_range in row_ranges.into_iter().rev() {
10343 for row in row_range.iter_rows().rev() {
10344 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10345 let next_line_row = row.next_row();
10346 let indent = snapshot.indent_size_for_line(next_line_row);
10347 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10348
10349 let replace =
10350 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10351 " "
10352 } else {
10353 ""
10354 };
10355
10356 this.buffer.update(cx, |buffer, cx| {
10357 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10358 });
10359 }
10360 }
10361
10362 this.change_selections(Default::default(), window, cx, |s| {
10363 s.select_anchor_ranges(cursor_positions)
10364 });
10365 });
10366 }
10367
10368 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10369 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10370 self.join_lines_impl(true, window, cx);
10371 }
10372
10373 pub fn sort_lines_case_sensitive(
10374 &mut self,
10375 _: &SortLinesCaseSensitive,
10376 window: &mut Window,
10377 cx: &mut Context<Self>,
10378 ) {
10379 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10380 }
10381
10382 pub fn sort_lines_by_length(
10383 &mut self,
10384 _: &SortLinesByLength,
10385 window: &mut Window,
10386 cx: &mut Context<Self>,
10387 ) {
10388 self.manipulate_immutable_lines(window, cx, |lines| {
10389 lines.sort_by_key(|&line| line.chars().count())
10390 })
10391 }
10392
10393 pub fn sort_lines_case_insensitive(
10394 &mut self,
10395 _: &SortLinesCaseInsensitive,
10396 window: &mut Window,
10397 cx: &mut Context<Self>,
10398 ) {
10399 self.manipulate_immutable_lines(window, cx, |lines| {
10400 lines.sort_by_key(|line| line.to_lowercase())
10401 })
10402 }
10403
10404 pub fn unique_lines_case_insensitive(
10405 &mut self,
10406 _: &UniqueLinesCaseInsensitive,
10407 window: &mut Window,
10408 cx: &mut Context<Self>,
10409 ) {
10410 self.manipulate_immutable_lines(window, cx, |lines| {
10411 let mut seen = HashSet::default();
10412 lines.retain(|line| seen.insert(line.to_lowercase()));
10413 })
10414 }
10415
10416 pub fn unique_lines_case_sensitive(
10417 &mut self,
10418 _: &UniqueLinesCaseSensitive,
10419 window: &mut Window,
10420 cx: &mut Context<Self>,
10421 ) {
10422 self.manipulate_immutable_lines(window, cx, |lines| {
10423 let mut seen = HashSet::default();
10424 lines.retain(|line| seen.insert(*line));
10425 })
10426 }
10427
10428 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10429 let Some(project) = self.project.clone() else {
10430 return;
10431 };
10432 self.reload(project, window, cx)
10433 .detach_and_notify_err(window, cx);
10434 }
10435
10436 pub fn restore_file(
10437 &mut self,
10438 _: &::git::RestoreFile,
10439 window: &mut Window,
10440 cx: &mut Context<Self>,
10441 ) {
10442 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10443 let mut buffer_ids = HashSet::default();
10444 let snapshot = self.buffer().read(cx).snapshot(cx);
10445 for selection in self.selections.all::<usize>(cx) {
10446 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10447 }
10448
10449 let buffer = self.buffer().read(cx);
10450 let ranges = buffer_ids
10451 .into_iter()
10452 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10453 .collect::<Vec<_>>();
10454
10455 self.restore_hunks_in_ranges(ranges, window, cx);
10456 }
10457
10458 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10459 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10460 let selections = self
10461 .selections
10462 .all(cx)
10463 .into_iter()
10464 .map(|s| s.range())
10465 .collect();
10466 self.restore_hunks_in_ranges(selections, window, cx);
10467 }
10468
10469 pub fn restore_hunks_in_ranges(
10470 &mut self,
10471 ranges: Vec<Range<Point>>,
10472 window: &mut Window,
10473 cx: &mut Context<Editor>,
10474 ) {
10475 let mut revert_changes = HashMap::default();
10476 let chunk_by = self
10477 .snapshot(window, cx)
10478 .hunks_for_ranges(ranges)
10479 .into_iter()
10480 .chunk_by(|hunk| hunk.buffer_id);
10481 for (buffer_id, hunks) in &chunk_by {
10482 let hunks = hunks.collect::<Vec<_>>();
10483 for hunk in &hunks {
10484 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10485 }
10486 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10487 }
10488 drop(chunk_by);
10489 if !revert_changes.is_empty() {
10490 self.transact(window, cx, |editor, window, cx| {
10491 editor.restore(revert_changes, window, cx);
10492 });
10493 }
10494 }
10495
10496 pub fn open_active_item_in_terminal(
10497 &mut self,
10498 _: &OpenInTerminal,
10499 window: &mut Window,
10500 cx: &mut Context<Self>,
10501 ) {
10502 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10503 let project_path = buffer.read(cx).project_path(cx)?;
10504 let project = self.project.as_ref()?.read(cx);
10505 let entry = project.entry_for_path(&project_path, cx)?;
10506 let parent = match &entry.canonical_path {
10507 Some(canonical_path) => canonical_path.to_path_buf(),
10508 None => project.absolute_path(&project_path, cx)?,
10509 }
10510 .parent()?
10511 .to_path_buf();
10512 Some(parent)
10513 }) {
10514 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10515 }
10516 }
10517
10518 fn set_breakpoint_context_menu(
10519 &mut self,
10520 display_row: DisplayRow,
10521 position: Option<Anchor>,
10522 clicked_point: gpui::Point<Pixels>,
10523 window: &mut Window,
10524 cx: &mut Context<Self>,
10525 ) {
10526 let source = self
10527 .buffer
10528 .read(cx)
10529 .snapshot(cx)
10530 .anchor_before(Point::new(display_row.0, 0u32));
10531
10532 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10533
10534 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10535 self,
10536 source,
10537 clicked_point,
10538 context_menu,
10539 window,
10540 cx,
10541 );
10542 }
10543
10544 fn add_edit_breakpoint_block(
10545 &mut self,
10546 anchor: Anchor,
10547 breakpoint: &Breakpoint,
10548 edit_action: BreakpointPromptEditAction,
10549 window: &mut Window,
10550 cx: &mut Context<Self>,
10551 ) {
10552 let weak_editor = cx.weak_entity();
10553 let bp_prompt = cx.new(|cx| {
10554 BreakpointPromptEditor::new(
10555 weak_editor,
10556 anchor,
10557 breakpoint.clone(),
10558 edit_action,
10559 window,
10560 cx,
10561 )
10562 });
10563
10564 let height = bp_prompt.update(cx, |this, cx| {
10565 this.prompt
10566 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10567 });
10568 let cloned_prompt = bp_prompt.clone();
10569 let blocks = vec![BlockProperties {
10570 style: BlockStyle::Sticky,
10571 placement: BlockPlacement::Above(anchor),
10572 height: Some(height),
10573 render: Arc::new(move |cx| {
10574 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10575 cloned_prompt.clone().into_any_element()
10576 }),
10577 priority: 0,
10578 }];
10579
10580 let focus_handle = bp_prompt.focus_handle(cx);
10581 window.focus(&focus_handle);
10582
10583 let block_ids = self.insert_blocks(blocks, None, cx);
10584 bp_prompt.update(cx, |prompt, _| {
10585 prompt.add_block_ids(block_ids);
10586 });
10587 }
10588
10589 pub(crate) fn breakpoint_at_row(
10590 &self,
10591 row: u32,
10592 window: &mut Window,
10593 cx: &mut Context<Self>,
10594 ) -> Option<(Anchor, Breakpoint)> {
10595 let snapshot = self.snapshot(window, cx);
10596 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10597
10598 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10599 }
10600
10601 pub(crate) fn breakpoint_at_anchor(
10602 &self,
10603 breakpoint_position: Anchor,
10604 snapshot: &EditorSnapshot,
10605 cx: &mut Context<Self>,
10606 ) -> Option<(Anchor, Breakpoint)> {
10607 let project = self.project.clone()?;
10608
10609 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10610 snapshot
10611 .buffer_snapshot
10612 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10613 })?;
10614
10615 let enclosing_excerpt = breakpoint_position.excerpt_id;
10616 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10617 let buffer_snapshot = buffer.read(cx).snapshot();
10618
10619 let row = buffer_snapshot
10620 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10621 .row;
10622
10623 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10624 let anchor_end = snapshot
10625 .buffer_snapshot
10626 .anchor_after(Point::new(row, line_len));
10627
10628 let bp = self
10629 .breakpoint_store
10630 .as_ref()?
10631 .read_with(cx, |breakpoint_store, cx| {
10632 breakpoint_store
10633 .breakpoints(
10634 &buffer,
10635 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10636 &buffer_snapshot,
10637 cx,
10638 )
10639 .next()
10640 .and_then(|(bp, _)| {
10641 let breakpoint_row = buffer_snapshot
10642 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10643 .row;
10644
10645 if breakpoint_row == row {
10646 snapshot
10647 .buffer_snapshot
10648 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10649 .map(|position| (position, bp.bp.clone()))
10650 } else {
10651 None
10652 }
10653 })
10654 });
10655 bp
10656 }
10657
10658 pub fn edit_log_breakpoint(
10659 &mut self,
10660 _: &EditLogBreakpoint,
10661 window: &mut Window,
10662 cx: &mut Context<Self>,
10663 ) {
10664 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10665 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10666 message: None,
10667 state: BreakpointState::Enabled,
10668 condition: None,
10669 hit_condition: None,
10670 });
10671
10672 self.add_edit_breakpoint_block(
10673 anchor,
10674 &breakpoint,
10675 BreakpointPromptEditAction::Log,
10676 window,
10677 cx,
10678 );
10679 }
10680 }
10681
10682 fn breakpoints_at_cursors(
10683 &self,
10684 window: &mut Window,
10685 cx: &mut Context<Self>,
10686 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10687 let snapshot = self.snapshot(window, cx);
10688 let cursors = self
10689 .selections
10690 .disjoint_anchors()
10691 .into_iter()
10692 .map(|selection| {
10693 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10694
10695 let breakpoint_position = self
10696 .breakpoint_at_row(cursor_position.row, window, cx)
10697 .map(|bp| bp.0)
10698 .unwrap_or_else(|| {
10699 snapshot
10700 .display_snapshot
10701 .buffer_snapshot
10702 .anchor_after(Point::new(cursor_position.row, 0))
10703 });
10704
10705 let breakpoint = self
10706 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10707 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10708
10709 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10710 })
10711 // 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.
10712 .collect::<HashMap<Anchor, _>>();
10713
10714 cursors.into_iter().collect()
10715 }
10716
10717 pub fn enable_breakpoint(
10718 &mut self,
10719 _: &crate::actions::EnableBreakpoint,
10720 window: &mut Window,
10721 cx: &mut Context<Self>,
10722 ) {
10723 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10724 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10725 continue;
10726 };
10727 self.edit_breakpoint_at_anchor(
10728 anchor,
10729 breakpoint,
10730 BreakpointEditAction::InvertState,
10731 cx,
10732 );
10733 }
10734 }
10735
10736 pub fn disable_breakpoint(
10737 &mut self,
10738 _: &crate::actions::DisableBreakpoint,
10739 window: &mut Window,
10740 cx: &mut Context<Self>,
10741 ) {
10742 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10743 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10744 continue;
10745 };
10746 self.edit_breakpoint_at_anchor(
10747 anchor,
10748 breakpoint,
10749 BreakpointEditAction::InvertState,
10750 cx,
10751 );
10752 }
10753 }
10754
10755 pub fn toggle_breakpoint(
10756 &mut self,
10757 _: &crate::actions::ToggleBreakpoint,
10758 window: &mut Window,
10759 cx: &mut Context<Self>,
10760 ) {
10761 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10762 if let Some(breakpoint) = breakpoint {
10763 self.edit_breakpoint_at_anchor(
10764 anchor,
10765 breakpoint,
10766 BreakpointEditAction::Toggle,
10767 cx,
10768 );
10769 } else {
10770 self.edit_breakpoint_at_anchor(
10771 anchor,
10772 Breakpoint::new_standard(),
10773 BreakpointEditAction::Toggle,
10774 cx,
10775 );
10776 }
10777 }
10778 }
10779
10780 pub fn edit_breakpoint_at_anchor(
10781 &mut self,
10782 breakpoint_position: Anchor,
10783 breakpoint: Breakpoint,
10784 edit_action: BreakpointEditAction,
10785 cx: &mut Context<Self>,
10786 ) {
10787 let Some(breakpoint_store) = &self.breakpoint_store else {
10788 return;
10789 };
10790
10791 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10792 if breakpoint_position == Anchor::min() {
10793 self.buffer()
10794 .read(cx)
10795 .excerpt_buffer_ids()
10796 .into_iter()
10797 .next()
10798 } else {
10799 None
10800 }
10801 }) else {
10802 return;
10803 };
10804
10805 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10806 return;
10807 };
10808
10809 breakpoint_store.update(cx, |breakpoint_store, cx| {
10810 breakpoint_store.toggle_breakpoint(
10811 buffer,
10812 BreakpointWithPosition {
10813 position: breakpoint_position.text_anchor,
10814 bp: breakpoint,
10815 },
10816 edit_action,
10817 cx,
10818 );
10819 });
10820
10821 cx.notify();
10822 }
10823
10824 #[cfg(any(test, feature = "test-support"))]
10825 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10826 self.breakpoint_store.clone()
10827 }
10828
10829 pub fn prepare_restore_change(
10830 &self,
10831 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10832 hunk: &MultiBufferDiffHunk,
10833 cx: &mut App,
10834 ) -> Option<()> {
10835 if hunk.is_created_file() {
10836 return None;
10837 }
10838 let buffer = self.buffer.read(cx);
10839 let diff = buffer.diff_for(hunk.buffer_id)?;
10840 let buffer = buffer.buffer(hunk.buffer_id)?;
10841 let buffer = buffer.read(cx);
10842 let original_text = diff
10843 .read(cx)
10844 .base_text()
10845 .as_rope()
10846 .slice(hunk.diff_base_byte_range.clone());
10847 let buffer_snapshot = buffer.snapshot();
10848 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10849 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10850 probe
10851 .0
10852 .start
10853 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10854 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10855 }) {
10856 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10857 Some(())
10858 } else {
10859 None
10860 }
10861 }
10862
10863 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10864 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10865 }
10866
10867 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10868 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10869 }
10870
10871 fn manipulate_lines<M>(
10872 &mut self,
10873 window: &mut Window,
10874 cx: &mut Context<Self>,
10875 mut manipulate: M,
10876 ) where
10877 M: FnMut(&str) -> LineManipulationResult,
10878 {
10879 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10880
10881 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10882 let buffer = self.buffer.read(cx).snapshot(cx);
10883
10884 let mut edits = Vec::new();
10885
10886 let selections = self.selections.all::<Point>(cx);
10887 let mut selections = selections.iter().peekable();
10888 let mut contiguous_row_selections = Vec::new();
10889 let mut new_selections = Vec::new();
10890 let mut added_lines = 0;
10891 let mut removed_lines = 0;
10892
10893 while let Some(selection) = selections.next() {
10894 let (start_row, end_row) = consume_contiguous_rows(
10895 &mut contiguous_row_selections,
10896 selection,
10897 &display_map,
10898 &mut selections,
10899 );
10900
10901 let start_point = Point::new(start_row.0, 0);
10902 let end_point = Point::new(
10903 end_row.previous_row().0,
10904 buffer.line_len(end_row.previous_row()),
10905 );
10906 let text = buffer
10907 .text_for_range(start_point..end_point)
10908 .collect::<String>();
10909
10910 let LineManipulationResult {
10911 new_text,
10912 line_count_before,
10913 line_count_after,
10914 } = manipulate(&text);
10915
10916 edits.push((start_point..end_point, new_text));
10917
10918 // Selections must change based on added and removed line count
10919 let start_row =
10920 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10921 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10922 new_selections.push(Selection {
10923 id: selection.id,
10924 start: start_row,
10925 end: end_row,
10926 goal: SelectionGoal::None,
10927 reversed: selection.reversed,
10928 });
10929
10930 if line_count_after > line_count_before {
10931 added_lines += line_count_after - line_count_before;
10932 } else if line_count_before > line_count_after {
10933 removed_lines += line_count_before - line_count_after;
10934 }
10935 }
10936
10937 self.transact(window, cx, |this, window, cx| {
10938 let buffer = this.buffer.update(cx, |buffer, cx| {
10939 buffer.edit(edits, None, cx);
10940 buffer.snapshot(cx)
10941 });
10942
10943 // Recalculate offsets on newly edited buffer
10944 let new_selections = new_selections
10945 .iter()
10946 .map(|s| {
10947 let start_point = Point::new(s.start.0, 0);
10948 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10949 Selection {
10950 id: s.id,
10951 start: buffer.point_to_offset(start_point),
10952 end: buffer.point_to_offset(end_point),
10953 goal: s.goal,
10954 reversed: s.reversed,
10955 }
10956 })
10957 .collect();
10958
10959 this.change_selections(Default::default(), window, cx, |s| {
10960 s.select(new_selections);
10961 });
10962
10963 this.request_autoscroll(Autoscroll::fit(), cx);
10964 });
10965 }
10966
10967 fn manipulate_immutable_lines<Fn>(
10968 &mut self,
10969 window: &mut Window,
10970 cx: &mut Context<Self>,
10971 mut callback: Fn,
10972 ) where
10973 Fn: FnMut(&mut Vec<&str>),
10974 {
10975 self.manipulate_lines(window, cx, |text| {
10976 let mut lines: Vec<&str> = text.split('\n').collect();
10977 let line_count_before = lines.len();
10978
10979 callback(&mut lines);
10980
10981 LineManipulationResult {
10982 new_text: lines.join("\n"),
10983 line_count_before,
10984 line_count_after: lines.len(),
10985 }
10986 });
10987 }
10988
10989 fn manipulate_mutable_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<Cow<'_, str>>),
10996 {
10997 self.manipulate_lines(window, cx, |text| {
10998 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).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 pub fn convert_indentation_to_spaces(
11012 &mut self,
11013 _: &ConvertIndentationToSpaces,
11014 window: &mut Window,
11015 cx: &mut Context<Self>,
11016 ) {
11017 let settings = self.buffer.read(cx).language_settings(cx);
11018 let tab_size = settings.tab_size.get() as usize;
11019
11020 self.manipulate_mutable_lines(window, cx, |lines| {
11021 // Allocates a reasonably sized scratch buffer once for the whole loop
11022 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11023 // Avoids recomputing spaces that could be inserted many times
11024 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11025 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11026 .collect();
11027
11028 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11029 let mut chars = line.as_ref().chars();
11030 let mut col = 0;
11031 let mut changed = false;
11032
11033 while let Some(ch) = chars.next() {
11034 match ch {
11035 ' ' => {
11036 reindented_line.push(' ');
11037 col += 1;
11038 }
11039 '\t' => {
11040 // \t are converted to spaces depending on the current column
11041 let spaces_len = tab_size - (col % tab_size);
11042 reindented_line.extend(&space_cache[spaces_len - 1]);
11043 col += spaces_len;
11044 changed = true;
11045 }
11046 _ => {
11047 // If we dont append before break, the character is consumed
11048 reindented_line.push(ch);
11049 break;
11050 }
11051 }
11052 }
11053
11054 if !changed {
11055 reindented_line.clear();
11056 continue;
11057 }
11058 // Append the rest of the line and replace old reference with new one
11059 reindented_line.extend(chars);
11060 *line = Cow::Owned(reindented_line.clone());
11061 reindented_line.clear();
11062 }
11063 });
11064 }
11065
11066 pub fn convert_indentation_to_tabs(
11067 &mut self,
11068 _: &ConvertIndentationToTabs,
11069 window: &mut Window,
11070 cx: &mut Context<Self>,
11071 ) {
11072 let settings = self.buffer.read(cx).language_settings(cx);
11073 let tab_size = settings.tab_size.get() as usize;
11074
11075 self.manipulate_mutable_lines(window, cx, |lines| {
11076 // Allocates a reasonably sized buffer once for the whole loop
11077 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11078 // Avoids recomputing spaces that could be inserted many times
11079 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11080 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11081 .collect();
11082
11083 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11084 let mut chars = line.chars();
11085 let mut spaces_count = 0;
11086 let mut first_non_indent_char = None;
11087 let mut changed = false;
11088
11089 while let Some(ch) = chars.next() {
11090 match ch {
11091 ' ' => {
11092 // Keep track of spaces. Append \t when we reach tab_size
11093 spaces_count += 1;
11094 changed = true;
11095 if spaces_count == tab_size {
11096 reindented_line.push('\t');
11097 spaces_count = 0;
11098 }
11099 }
11100 '\t' => {
11101 reindented_line.push('\t');
11102 spaces_count = 0;
11103 }
11104 _ => {
11105 // Dont append it yet, we might have remaining spaces
11106 first_non_indent_char = Some(ch);
11107 break;
11108 }
11109 }
11110 }
11111
11112 if !changed {
11113 reindented_line.clear();
11114 continue;
11115 }
11116 // Remaining spaces that didn't make a full tab stop
11117 if spaces_count > 0 {
11118 reindented_line.extend(&space_cache[spaces_count - 1]);
11119 }
11120 // If we consume an extra character that was not indentation, add it back
11121 if let Some(extra_char) = first_non_indent_char {
11122 reindented_line.push(extra_char);
11123 }
11124 // Append the rest of the line and replace old reference with new one
11125 reindented_line.extend(chars);
11126 *line = Cow::Owned(reindented_line.clone());
11127 reindented_line.clear();
11128 }
11129 });
11130 }
11131
11132 pub fn convert_to_upper_case(
11133 &mut self,
11134 _: &ConvertToUpperCase,
11135 window: &mut Window,
11136 cx: &mut Context<Self>,
11137 ) {
11138 self.manipulate_text(window, cx, |text| text.to_uppercase())
11139 }
11140
11141 pub fn convert_to_lower_case(
11142 &mut self,
11143 _: &ConvertToLowerCase,
11144 window: &mut Window,
11145 cx: &mut Context<Self>,
11146 ) {
11147 self.manipulate_text(window, cx, |text| text.to_lowercase())
11148 }
11149
11150 pub fn convert_to_title_case(
11151 &mut self,
11152 _: &ConvertToTitleCase,
11153 window: &mut Window,
11154 cx: &mut Context<Self>,
11155 ) {
11156 self.manipulate_text(window, cx, |text| {
11157 text.split('\n')
11158 .map(|line| line.to_case(Case::Title))
11159 .join("\n")
11160 })
11161 }
11162
11163 pub fn convert_to_snake_case(
11164 &mut self,
11165 _: &ConvertToSnakeCase,
11166 window: &mut Window,
11167 cx: &mut Context<Self>,
11168 ) {
11169 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11170 }
11171
11172 pub fn convert_to_kebab_case(
11173 &mut self,
11174 _: &ConvertToKebabCase,
11175 window: &mut Window,
11176 cx: &mut Context<Self>,
11177 ) {
11178 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11179 }
11180
11181 pub fn convert_to_upper_camel_case(
11182 &mut self,
11183 _: &ConvertToUpperCamelCase,
11184 window: &mut Window,
11185 cx: &mut Context<Self>,
11186 ) {
11187 self.manipulate_text(window, cx, |text| {
11188 text.split('\n')
11189 .map(|line| line.to_case(Case::UpperCamel))
11190 .join("\n")
11191 })
11192 }
11193
11194 pub fn convert_to_lower_camel_case(
11195 &mut self,
11196 _: &ConvertToLowerCamelCase,
11197 window: &mut Window,
11198 cx: &mut Context<Self>,
11199 ) {
11200 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11201 }
11202
11203 pub fn convert_to_opposite_case(
11204 &mut self,
11205 _: &ConvertToOppositeCase,
11206 window: &mut Window,
11207 cx: &mut Context<Self>,
11208 ) {
11209 self.manipulate_text(window, cx, |text| {
11210 text.chars()
11211 .fold(String::with_capacity(text.len()), |mut t, c| {
11212 if c.is_uppercase() {
11213 t.extend(c.to_lowercase());
11214 } else {
11215 t.extend(c.to_uppercase());
11216 }
11217 t
11218 })
11219 })
11220 }
11221
11222 pub fn convert_to_sentence_case(
11223 &mut self,
11224 _: &ConvertToSentenceCase,
11225 window: &mut Window,
11226 cx: &mut Context<Self>,
11227 ) {
11228 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11229 }
11230
11231 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11232 self.manipulate_text(window, cx, |text| {
11233 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11234 if has_upper_case_characters {
11235 text.to_lowercase()
11236 } else {
11237 text.to_uppercase()
11238 }
11239 })
11240 }
11241
11242 pub fn convert_to_rot13(
11243 &mut self,
11244 _: &ConvertToRot13,
11245 window: &mut Window,
11246 cx: &mut Context<Self>,
11247 ) {
11248 self.manipulate_text(window, cx, |text| {
11249 text.chars()
11250 .map(|c| match c {
11251 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11252 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11253 _ => c,
11254 })
11255 .collect()
11256 })
11257 }
11258
11259 pub fn convert_to_rot47(
11260 &mut self,
11261 _: &ConvertToRot47,
11262 window: &mut Window,
11263 cx: &mut Context<Self>,
11264 ) {
11265 self.manipulate_text(window, cx, |text| {
11266 text.chars()
11267 .map(|c| {
11268 let code_point = c as u32;
11269 if code_point >= 33 && code_point <= 126 {
11270 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11271 }
11272 c
11273 })
11274 .collect()
11275 })
11276 }
11277
11278 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11279 where
11280 Fn: FnMut(&str) -> String,
11281 {
11282 let buffer = self.buffer.read(cx).snapshot(cx);
11283
11284 let mut new_selections = Vec::new();
11285 let mut edits = Vec::new();
11286 let mut selection_adjustment = 0i32;
11287
11288 for selection in self.selections.all::<usize>(cx) {
11289 let selection_is_empty = selection.is_empty();
11290
11291 let (start, end) = if selection_is_empty {
11292 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11293 (word_range.start, word_range.end)
11294 } else {
11295 (selection.start, selection.end)
11296 };
11297
11298 let text = buffer.text_for_range(start..end).collect::<String>();
11299 let old_length = text.len() as i32;
11300 let text = callback(&text);
11301
11302 new_selections.push(Selection {
11303 start: (start as i32 - selection_adjustment) as usize,
11304 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11305 goal: SelectionGoal::None,
11306 ..selection
11307 });
11308
11309 selection_adjustment += old_length - text.len() as i32;
11310
11311 edits.push((start..end, text));
11312 }
11313
11314 self.transact(window, cx, |this, window, cx| {
11315 this.buffer.update(cx, |buffer, cx| {
11316 buffer.edit(edits, None, cx);
11317 });
11318
11319 this.change_selections(Default::default(), window, cx, |s| {
11320 s.select(new_selections);
11321 });
11322
11323 this.request_autoscroll(Autoscroll::fit(), cx);
11324 });
11325 }
11326
11327 pub fn move_selection_on_drop(
11328 &mut self,
11329 selection: &Selection<Anchor>,
11330 target: DisplayPoint,
11331 is_cut: bool,
11332 window: &mut Window,
11333 cx: &mut Context<Self>,
11334 ) {
11335 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11336 let buffer = &display_map.buffer_snapshot;
11337 let mut edits = Vec::new();
11338 let insert_point = display_map
11339 .clip_point(target, Bias::Left)
11340 .to_point(&display_map);
11341 let text = buffer
11342 .text_for_range(selection.start..selection.end)
11343 .collect::<String>();
11344 if is_cut {
11345 edits.push(((selection.start..selection.end), String::new()));
11346 }
11347 let insert_anchor = buffer.anchor_before(insert_point);
11348 edits.push(((insert_anchor..insert_anchor), text));
11349 let last_edit_start = insert_anchor.bias_left(buffer);
11350 let last_edit_end = insert_anchor.bias_right(buffer);
11351 self.transact(window, cx, |this, window, cx| {
11352 this.buffer.update(cx, |buffer, cx| {
11353 buffer.edit(edits, None, cx);
11354 });
11355 this.change_selections(Default::default(), window, cx, |s| {
11356 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11357 });
11358 });
11359 }
11360
11361 pub fn clear_selection_drag_state(&mut self) {
11362 self.selection_drag_state = SelectionDragState::None;
11363 }
11364
11365 pub fn duplicate(
11366 &mut self,
11367 upwards: bool,
11368 whole_lines: bool,
11369 window: &mut Window,
11370 cx: &mut Context<Self>,
11371 ) {
11372 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11373
11374 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11375 let buffer = &display_map.buffer_snapshot;
11376 let selections = self.selections.all::<Point>(cx);
11377
11378 let mut edits = Vec::new();
11379 let mut selections_iter = selections.iter().peekable();
11380 while let Some(selection) = selections_iter.next() {
11381 let mut rows = selection.spanned_rows(false, &display_map);
11382 // duplicate line-wise
11383 if whole_lines || selection.start == selection.end {
11384 // Avoid duplicating the same lines twice.
11385 while let Some(next_selection) = selections_iter.peek() {
11386 let next_rows = next_selection.spanned_rows(false, &display_map);
11387 if next_rows.start < rows.end {
11388 rows.end = next_rows.end;
11389 selections_iter.next().unwrap();
11390 } else {
11391 break;
11392 }
11393 }
11394
11395 // Copy the text from the selected row region and splice it either at the start
11396 // or end of the region.
11397 let start = Point::new(rows.start.0, 0);
11398 let end = Point::new(
11399 rows.end.previous_row().0,
11400 buffer.line_len(rows.end.previous_row()),
11401 );
11402 let text = buffer
11403 .text_for_range(start..end)
11404 .chain(Some("\n"))
11405 .collect::<String>();
11406 let insert_location = if upwards {
11407 Point::new(rows.end.0, 0)
11408 } else {
11409 start
11410 };
11411 edits.push((insert_location..insert_location, text));
11412 } else {
11413 // duplicate character-wise
11414 let start = selection.start;
11415 let end = selection.end;
11416 let text = buffer.text_for_range(start..end).collect::<String>();
11417 edits.push((selection.end..selection.end, text));
11418 }
11419 }
11420
11421 self.transact(window, cx, |this, _, cx| {
11422 this.buffer.update(cx, |buffer, cx| {
11423 buffer.edit(edits, None, cx);
11424 });
11425
11426 this.request_autoscroll(Autoscroll::fit(), cx);
11427 });
11428 }
11429
11430 pub fn duplicate_line_up(
11431 &mut self,
11432 _: &DuplicateLineUp,
11433 window: &mut Window,
11434 cx: &mut Context<Self>,
11435 ) {
11436 self.duplicate(true, true, window, cx);
11437 }
11438
11439 pub fn duplicate_line_down(
11440 &mut self,
11441 _: &DuplicateLineDown,
11442 window: &mut Window,
11443 cx: &mut Context<Self>,
11444 ) {
11445 self.duplicate(false, true, window, cx);
11446 }
11447
11448 pub fn duplicate_selection(
11449 &mut self,
11450 _: &DuplicateSelection,
11451 window: &mut Window,
11452 cx: &mut Context<Self>,
11453 ) {
11454 self.duplicate(false, false, window, cx);
11455 }
11456
11457 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11458 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11459 if self.mode.is_single_line() {
11460 cx.propagate();
11461 return;
11462 }
11463
11464 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11465 let buffer = self.buffer.read(cx).snapshot(cx);
11466
11467 let mut edits = Vec::new();
11468 let mut unfold_ranges = Vec::new();
11469 let mut refold_creases = Vec::new();
11470
11471 let selections = self.selections.all::<Point>(cx);
11472 let mut selections = selections.iter().peekable();
11473 let mut contiguous_row_selections = Vec::new();
11474 let mut new_selections = Vec::new();
11475
11476 while let Some(selection) = selections.next() {
11477 // Find all the selections that span a contiguous row range
11478 let (start_row, end_row) = consume_contiguous_rows(
11479 &mut contiguous_row_selections,
11480 selection,
11481 &display_map,
11482 &mut selections,
11483 );
11484
11485 // Move the text spanned by the row range to be before the line preceding the row range
11486 if start_row.0 > 0 {
11487 let range_to_move = Point::new(
11488 start_row.previous_row().0,
11489 buffer.line_len(start_row.previous_row()),
11490 )
11491 ..Point::new(
11492 end_row.previous_row().0,
11493 buffer.line_len(end_row.previous_row()),
11494 );
11495 let insertion_point = display_map
11496 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11497 .0;
11498
11499 // Don't move lines across excerpts
11500 if buffer
11501 .excerpt_containing(insertion_point..range_to_move.end)
11502 .is_some()
11503 {
11504 let text = buffer
11505 .text_for_range(range_to_move.clone())
11506 .flat_map(|s| s.chars())
11507 .skip(1)
11508 .chain(['\n'])
11509 .collect::<String>();
11510
11511 edits.push((
11512 buffer.anchor_after(range_to_move.start)
11513 ..buffer.anchor_before(range_to_move.end),
11514 String::new(),
11515 ));
11516 let insertion_anchor = buffer.anchor_after(insertion_point);
11517 edits.push((insertion_anchor..insertion_anchor, text));
11518
11519 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11520
11521 // Move selections up
11522 new_selections.extend(contiguous_row_selections.drain(..).map(
11523 |mut selection| {
11524 selection.start.row -= row_delta;
11525 selection.end.row -= row_delta;
11526 selection
11527 },
11528 ));
11529
11530 // Move folds up
11531 unfold_ranges.push(range_to_move.clone());
11532 for fold in display_map.folds_in_range(
11533 buffer.anchor_before(range_to_move.start)
11534 ..buffer.anchor_after(range_to_move.end),
11535 ) {
11536 let mut start = fold.range.start.to_point(&buffer);
11537 let mut end = fold.range.end.to_point(&buffer);
11538 start.row -= row_delta;
11539 end.row -= row_delta;
11540 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11541 }
11542 }
11543 }
11544
11545 // If we didn't move line(s), preserve the existing selections
11546 new_selections.append(&mut contiguous_row_selections);
11547 }
11548
11549 self.transact(window, cx, |this, window, cx| {
11550 this.unfold_ranges(&unfold_ranges, true, true, cx);
11551 this.buffer.update(cx, |buffer, cx| {
11552 for (range, text) in edits {
11553 buffer.edit([(range, text)], None, cx);
11554 }
11555 });
11556 this.fold_creases(refold_creases, true, window, cx);
11557 this.change_selections(Default::default(), window, cx, |s| {
11558 s.select(new_selections);
11559 })
11560 });
11561 }
11562
11563 pub fn move_line_down(
11564 &mut self,
11565 _: &MoveLineDown,
11566 window: &mut Window,
11567 cx: &mut Context<Self>,
11568 ) {
11569 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11570 if self.mode.is_single_line() {
11571 cx.propagate();
11572 return;
11573 }
11574
11575 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11576 let buffer = self.buffer.read(cx).snapshot(cx);
11577
11578 let mut edits = Vec::new();
11579 let mut unfold_ranges = Vec::new();
11580 let mut refold_creases = Vec::new();
11581
11582 let selections = self.selections.all::<Point>(cx);
11583 let mut selections = selections.iter().peekable();
11584 let mut contiguous_row_selections = Vec::new();
11585 let mut new_selections = Vec::new();
11586
11587 while let Some(selection) = selections.next() {
11588 // Find all the selections that span a contiguous row range
11589 let (start_row, end_row) = consume_contiguous_rows(
11590 &mut contiguous_row_selections,
11591 selection,
11592 &display_map,
11593 &mut selections,
11594 );
11595
11596 // Move the text spanned by the row range to be after the last line of the row range
11597 if end_row.0 <= buffer.max_point().row {
11598 let range_to_move =
11599 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11600 let insertion_point = display_map
11601 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11602 .0;
11603
11604 // Don't move lines across excerpt boundaries
11605 if buffer
11606 .excerpt_containing(range_to_move.start..insertion_point)
11607 .is_some()
11608 {
11609 let mut text = String::from("\n");
11610 text.extend(buffer.text_for_range(range_to_move.clone()));
11611 text.pop(); // Drop trailing newline
11612 edits.push((
11613 buffer.anchor_after(range_to_move.start)
11614 ..buffer.anchor_before(range_to_move.end),
11615 String::new(),
11616 ));
11617 let insertion_anchor = buffer.anchor_after(insertion_point);
11618 edits.push((insertion_anchor..insertion_anchor, text));
11619
11620 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11621
11622 // Move selections down
11623 new_selections.extend(contiguous_row_selections.drain(..).map(
11624 |mut selection| {
11625 selection.start.row += row_delta;
11626 selection.end.row += row_delta;
11627 selection
11628 },
11629 ));
11630
11631 // Move folds down
11632 unfold_ranges.push(range_to_move.clone());
11633 for fold in display_map.folds_in_range(
11634 buffer.anchor_before(range_to_move.start)
11635 ..buffer.anchor_after(range_to_move.end),
11636 ) {
11637 let mut start = fold.range.start.to_point(&buffer);
11638 let mut end = fold.range.end.to_point(&buffer);
11639 start.row += row_delta;
11640 end.row += row_delta;
11641 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11642 }
11643 }
11644 }
11645
11646 // If we didn't move line(s), preserve the existing selections
11647 new_selections.append(&mut contiguous_row_selections);
11648 }
11649
11650 self.transact(window, cx, |this, window, cx| {
11651 this.unfold_ranges(&unfold_ranges, true, true, cx);
11652 this.buffer.update(cx, |buffer, cx| {
11653 for (range, text) in edits {
11654 buffer.edit([(range, text)], None, cx);
11655 }
11656 });
11657 this.fold_creases(refold_creases, true, window, cx);
11658 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11659 });
11660 }
11661
11662 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11663 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11664 let text_layout_details = &self.text_layout_details(window);
11665 self.transact(window, cx, |this, window, cx| {
11666 let edits = this.change_selections(Default::default(), window, cx, |s| {
11667 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11668 s.move_with(|display_map, selection| {
11669 if !selection.is_empty() {
11670 return;
11671 }
11672
11673 let mut head = selection.head();
11674 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11675 if head.column() == display_map.line_len(head.row()) {
11676 transpose_offset = display_map
11677 .buffer_snapshot
11678 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11679 }
11680
11681 if transpose_offset == 0 {
11682 return;
11683 }
11684
11685 *head.column_mut() += 1;
11686 head = display_map.clip_point(head, Bias::Right);
11687 let goal = SelectionGoal::HorizontalPosition(
11688 display_map
11689 .x_for_display_point(head, text_layout_details)
11690 .into(),
11691 );
11692 selection.collapse_to(head, goal);
11693
11694 let transpose_start = display_map
11695 .buffer_snapshot
11696 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11697 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11698 let transpose_end = display_map
11699 .buffer_snapshot
11700 .clip_offset(transpose_offset + 1, Bias::Right);
11701 if let Some(ch) =
11702 display_map.buffer_snapshot.chars_at(transpose_start).next()
11703 {
11704 edits.push((transpose_start..transpose_offset, String::new()));
11705 edits.push((transpose_end..transpose_end, ch.to_string()));
11706 }
11707 }
11708 });
11709 edits
11710 });
11711 this.buffer
11712 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11713 let selections = this.selections.all::<usize>(cx);
11714 this.change_selections(Default::default(), window, cx, |s| {
11715 s.select(selections);
11716 });
11717 });
11718 }
11719
11720 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11721 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11722 if self.mode.is_single_line() {
11723 cx.propagate();
11724 return;
11725 }
11726
11727 self.rewrap_impl(RewrapOptions::default(), cx)
11728 }
11729
11730 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11731 let buffer = self.buffer.read(cx).snapshot(cx);
11732 let selections = self.selections.all::<Point>(cx);
11733
11734 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11735 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11736 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11737 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11738 .peekable();
11739
11740 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11741 row
11742 } else {
11743 return Vec::new();
11744 };
11745
11746 let language_settings = buffer.language_settings_at(selection.head(), cx);
11747 let language_scope = buffer.language_scope_at(selection.head());
11748
11749 let indent_and_prefix_for_row =
11750 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11751 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11752 let (comment_prefix, rewrap_prefix) =
11753 if let Some(language_scope) = &language_scope {
11754 let indent_end = Point::new(row, indent.len);
11755 let comment_prefix = language_scope
11756 .line_comment_prefixes()
11757 .iter()
11758 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11759 .map(|prefix| prefix.to_string());
11760 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11761 let line_text_after_indent = buffer
11762 .text_for_range(indent_end..line_end)
11763 .collect::<String>();
11764 let rewrap_prefix = language_scope
11765 .rewrap_prefixes()
11766 .iter()
11767 .find_map(|prefix_regex| {
11768 prefix_regex.find(&line_text_after_indent).map(|mat| {
11769 if mat.start() == 0 {
11770 Some(mat.as_str().to_string())
11771 } else {
11772 None
11773 }
11774 })
11775 })
11776 .flatten();
11777 (comment_prefix, rewrap_prefix)
11778 } else {
11779 (None, None)
11780 };
11781 (indent, comment_prefix, rewrap_prefix)
11782 };
11783
11784 let mut ranges = Vec::new();
11785 let from_empty_selection = selection.is_empty();
11786
11787 let mut current_range_start = first_row;
11788 let mut prev_row = first_row;
11789 let (
11790 mut current_range_indent,
11791 mut current_range_comment_prefix,
11792 mut current_range_rewrap_prefix,
11793 ) = indent_and_prefix_for_row(first_row);
11794
11795 for row in non_blank_rows_iter.skip(1) {
11796 let has_paragraph_break = row > prev_row + 1;
11797
11798 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11799 indent_and_prefix_for_row(row);
11800
11801 let has_indent_change = row_indent != current_range_indent;
11802 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11803
11804 let has_boundary_change = has_comment_change
11805 || row_rewrap_prefix.is_some()
11806 || (has_indent_change && current_range_comment_prefix.is_some());
11807
11808 if has_paragraph_break || has_boundary_change {
11809 ranges.push((
11810 language_settings.clone(),
11811 Point::new(current_range_start, 0)
11812 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11813 current_range_indent,
11814 current_range_comment_prefix.clone(),
11815 current_range_rewrap_prefix.clone(),
11816 from_empty_selection,
11817 ));
11818 current_range_start = row;
11819 current_range_indent = row_indent;
11820 current_range_comment_prefix = row_comment_prefix;
11821 current_range_rewrap_prefix = row_rewrap_prefix;
11822 }
11823 prev_row = row;
11824 }
11825
11826 ranges.push((
11827 language_settings.clone(),
11828 Point::new(current_range_start, 0)
11829 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11830 current_range_indent,
11831 current_range_comment_prefix,
11832 current_range_rewrap_prefix,
11833 from_empty_selection,
11834 ));
11835
11836 ranges
11837 });
11838
11839 let mut edits = Vec::new();
11840 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11841
11842 for (
11843 language_settings,
11844 wrap_range,
11845 indent_size,
11846 comment_prefix,
11847 rewrap_prefix,
11848 from_empty_selection,
11849 ) in wrap_ranges
11850 {
11851 let mut start_row = wrap_range.start.row;
11852 let mut end_row = wrap_range.end.row;
11853
11854 // Skip selections that overlap with a range that has already been rewrapped.
11855 let selection_range = start_row..end_row;
11856 if rewrapped_row_ranges
11857 .iter()
11858 .any(|range| range.overlaps(&selection_range))
11859 {
11860 continue;
11861 }
11862
11863 let tab_size = language_settings.tab_size;
11864
11865 let indent_prefix = indent_size.chars().collect::<String>();
11866 let mut line_prefix = indent_prefix.clone();
11867 let mut inside_comment = false;
11868 if let Some(prefix) = &comment_prefix {
11869 line_prefix.push_str(prefix);
11870 inside_comment = true;
11871 }
11872 if let Some(prefix) = &rewrap_prefix {
11873 line_prefix.push_str(prefix);
11874 }
11875
11876 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11877 RewrapBehavior::InComments => inside_comment,
11878 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11879 RewrapBehavior::Anywhere => true,
11880 };
11881
11882 let should_rewrap = options.override_language_settings
11883 || allow_rewrap_based_on_language
11884 || self.hard_wrap.is_some();
11885 if !should_rewrap {
11886 continue;
11887 }
11888
11889 if from_empty_selection {
11890 'expand_upwards: while start_row > 0 {
11891 let prev_row = start_row - 1;
11892 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11893 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11894 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11895 {
11896 start_row = prev_row;
11897 } else {
11898 break 'expand_upwards;
11899 }
11900 }
11901
11902 'expand_downwards: while end_row < buffer.max_point().row {
11903 let next_row = end_row + 1;
11904 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11905 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11906 && !buffer.is_line_blank(MultiBufferRow(next_row))
11907 {
11908 end_row = next_row;
11909 } else {
11910 break 'expand_downwards;
11911 }
11912 }
11913 }
11914
11915 let start = Point::new(start_row, 0);
11916 let start_offset = start.to_offset(&buffer);
11917 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11918 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11919 let Some(lines_without_prefixes) = selection_text
11920 .lines()
11921 .enumerate()
11922 .map(|(ix, line)| {
11923 let line_trimmed = line.trim_start();
11924 if rewrap_prefix.is_some() && ix > 0 {
11925 Ok(line_trimmed)
11926 } else {
11927 line_trimmed
11928 .strip_prefix(&line_prefix.trim_start())
11929 .with_context(|| {
11930 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11931 })
11932 }
11933 })
11934 .collect::<Result<Vec<_>, _>>()
11935 .log_err()
11936 else {
11937 continue;
11938 };
11939
11940 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11941 buffer
11942 .language_settings_at(Point::new(start_row, 0), cx)
11943 .preferred_line_length as usize
11944 });
11945
11946 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11947 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11948 } else {
11949 line_prefix.clone()
11950 };
11951
11952 let wrapped_text = wrap_with_prefix(
11953 line_prefix,
11954 subsequent_lines_prefix,
11955 lines_without_prefixes.join("\n"),
11956 wrap_column,
11957 tab_size,
11958 options.preserve_existing_whitespace,
11959 );
11960
11961 // TODO: should always use char-based diff while still supporting cursor behavior that
11962 // matches vim.
11963 let mut diff_options = DiffOptions::default();
11964 if options.override_language_settings {
11965 diff_options.max_word_diff_len = 0;
11966 diff_options.max_word_diff_line_count = 0;
11967 } else {
11968 diff_options.max_word_diff_len = usize::MAX;
11969 diff_options.max_word_diff_line_count = usize::MAX;
11970 }
11971
11972 for (old_range, new_text) in
11973 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11974 {
11975 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11976 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11977 edits.push((edit_start..edit_end, new_text));
11978 }
11979
11980 rewrapped_row_ranges.push(start_row..=end_row);
11981 }
11982
11983 self.buffer
11984 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11985 }
11986
11987 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11988 let mut text = String::new();
11989 let buffer = self.buffer.read(cx).snapshot(cx);
11990 let mut selections = self.selections.all::<Point>(cx);
11991 let mut clipboard_selections = Vec::with_capacity(selections.len());
11992 {
11993 let max_point = buffer.max_point();
11994 let mut is_first = true;
11995 for selection in &mut selections {
11996 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11997 if is_entire_line {
11998 selection.start = Point::new(selection.start.row, 0);
11999 if !selection.is_empty() && selection.end.column == 0 {
12000 selection.end = cmp::min(max_point, selection.end);
12001 } else {
12002 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12003 }
12004 selection.goal = SelectionGoal::None;
12005 }
12006 if is_first {
12007 is_first = false;
12008 } else {
12009 text += "\n";
12010 }
12011 let mut len = 0;
12012 for chunk in buffer.text_for_range(selection.start..selection.end) {
12013 text.push_str(chunk);
12014 len += chunk.len();
12015 }
12016 clipboard_selections.push(ClipboardSelection {
12017 len,
12018 is_entire_line,
12019 first_line_indent: buffer
12020 .indent_size_for_line(MultiBufferRow(selection.start.row))
12021 .len,
12022 });
12023 }
12024 }
12025
12026 self.transact(window, cx, |this, window, cx| {
12027 this.change_selections(Default::default(), window, cx, |s| {
12028 s.select(selections);
12029 });
12030 this.insert("", window, cx);
12031 });
12032 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12033 }
12034
12035 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12036 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12037 let item = self.cut_common(window, cx);
12038 cx.write_to_clipboard(item);
12039 }
12040
12041 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12042 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12043 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12044 s.move_with(|snapshot, sel| {
12045 if sel.is_empty() {
12046 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12047 }
12048 });
12049 });
12050 let item = self.cut_common(window, cx);
12051 cx.set_global(KillRing(item))
12052 }
12053
12054 pub fn kill_ring_yank(
12055 &mut self,
12056 _: &KillRingYank,
12057 window: &mut Window,
12058 cx: &mut Context<Self>,
12059 ) {
12060 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12061 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12062 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12063 (kill_ring.text().to_string(), kill_ring.metadata_json())
12064 } else {
12065 return;
12066 }
12067 } else {
12068 return;
12069 };
12070 self.do_paste(&text, metadata, false, window, cx);
12071 }
12072
12073 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12074 self.do_copy(true, cx);
12075 }
12076
12077 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12078 self.do_copy(false, cx);
12079 }
12080
12081 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12082 let selections = self.selections.all::<Point>(cx);
12083 let buffer = self.buffer.read(cx).read(cx);
12084 let mut text = String::new();
12085
12086 let mut clipboard_selections = Vec::with_capacity(selections.len());
12087 {
12088 let max_point = buffer.max_point();
12089 let mut is_first = true;
12090 for selection in &selections {
12091 let mut start = selection.start;
12092 let mut end = selection.end;
12093 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12094 if is_entire_line {
12095 start = Point::new(start.row, 0);
12096 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12097 }
12098
12099 let mut trimmed_selections = Vec::new();
12100 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12101 let row = MultiBufferRow(start.row);
12102 let first_indent = buffer.indent_size_for_line(row);
12103 if first_indent.len == 0 || start.column > first_indent.len {
12104 trimmed_selections.push(start..end);
12105 } else {
12106 trimmed_selections.push(
12107 Point::new(row.0, first_indent.len)
12108 ..Point::new(row.0, buffer.line_len(row)),
12109 );
12110 for row in start.row + 1..=end.row {
12111 let mut line_len = buffer.line_len(MultiBufferRow(row));
12112 if row == end.row {
12113 line_len = end.column;
12114 }
12115 if line_len == 0 {
12116 trimmed_selections
12117 .push(Point::new(row, 0)..Point::new(row, line_len));
12118 continue;
12119 }
12120 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12121 if row_indent_size.len >= first_indent.len {
12122 trimmed_selections.push(
12123 Point::new(row, first_indent.len)..Point::new(row, line_len),
12124 );
12125 } else {
12126 trimmed_selections.clear();
12127 trimmed_selections.push(start..end);
12128 break;
12129 }
12130 }
12131 }
12132 } else {
12133 trimmed_selections.push(start..end);
12134 }
12135
12136 for trimmed_range in trimmed_selections {
12137 if is_first {
12138 is_first = false;
12139 } else {
12140 text += "\n";
12141 }
12142 let mut len = 0;
12143 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12144 text.push_str(chunk);
12145 len += chunk.len();
12146 }
12147 clipboard_selections.push(ClipboardSelection {
12148 len,
12149 is_entire_line,
12150 first_line_indent: buffer
12151 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12152 .len,
12153 });
12154 }
12155 }
12156 }
12157
12158 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12159 text,
12160 clipboard_selections,
12161 ));
12162 }
12163
12164 pub fn do_paste(
12165 &mut self,
12166 text: &String,
12167 clipboard_selections: Option<Vec<ClipboardSelection>>,
12168 handle_entire_lines: bool,
12169 window: &mut Window,
12170 cx: &mut Context<Self>,
12171 ) {
12172 if self.read_only(cx) {
12173 return;
12174 }
12175
12176 let clipboard_text = Cow::Borrowed(text);
12177
12178 self.transact(window, cx, |this, window, cx| {
12179 let had_active_edit_prediction = this.has_active_edit_prediction();
12180
12181 if let Some(mut clipboard_selections) = clipboard_selections {
12182 let old_selections = this.selections.all::<usize>(cx);
12183 let all_selections_were_entire_line =
12184 clipboard_selections.iter().all(|s| s.is_entire_line);
12185 let first_selection_indent_column =
12186 clipboard_selections.first().map(|s| s.first_line_indent);
12187 if clipboard_selections.len() != old_selections.len() {
12188 clipboard_selections.drain(..);
12189 }
12190 let cursor_offset = this.selections.last::<usize>(cx).head();
12191 let mut auto_indent_on_paste = true;
12192
12193 this.buffer.update(cx, |buffer, cx| {
12194 let snapshot = buffer.read(cx);
12195 auto_indent_on_paste = snapshot
12196 .language_settings_at(cursor_offset, cx)
12197 .auto_indent_on_paste;
12198
12199 let mut start_offset = 0;
12200 let mut edits = Vec::new();
12201 let mut original_indent_columns = Vec::new();
12202 for (ix, selection) in old_selections.iter().enumerate() {
12203 let to_insert;
12204 let entire_line;
12205 let original_indent_column;
12206 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12207 let end_offset = start_offset + clipboard_selection.len;
12208 to_insert = &clipboard_text[start_offset..end_offset];
12209 entire_line = clipboard_selection.is_entire_line;
12210 start_offset = end_offset + 1;
12211 original_indent_column = Some(clipboard_selection.first_line_indent);
12212 } else {
12213 to_insert = clipboard_text.as_str();
12214 entire_line = all_selections_were_entire_line;
12215 original_indent_column = first_selection_indent_column
12216 }
12217
12218 // If the corresponding selection was empty when this slice of the
12219 // clipboard text was written, then the entire line containing the
12220 // selection was copied. If this selection is also currently empty,
12221 // then paste the line before the current line of the buffer.
12222 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12223 let column = selection.start.to_point(&snapshot).column as usize;
12224 let line_start = selection.start - column;
12225 line_start..line_start
12226 } else {
12227 selection.range()
12228 };
12229
12230 edits.push((range, to_insert));
12231 original_indent_columns.push(original_indent_column);
12232 }
12233 drop(snapshot);
12234
12235 buffer.edit(
12236 edits,
12237 if auto_indent_on_paste {
12238 Some(AutoindentMode::Block {
12239 original_indent_columns,
12240 })
12241 } else {
12242 None
12243 },
12244 cx,
12245 );
12246 });
12247
12248 let selections = this.selections.all::<usize>(cx);
12249 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12250 } else {
12251 this.insert(&clipboard_text, window, cx);
12252 }
12253
12254 let trigger_in_words =
12255 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12256
12257 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
12258 });
12259 }
12260
12261 pub fn diff_clipboard_with_selection(
12262 &mut self,
12263 _: &DiffClipboardWithSelection,
12264 window: &mut Window,
12265 cx: &mut Context<Self>,
12266 ) {
12267 let selections = self.selections.all::<usize>(cx);
12268
12269 if selections.is_empty() {
12270 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12271 return;
12272 };
12273
12274 let clipboard_text = match cx.read_from_clipboard() {
12275 Some(item) => match item.entries().first() {
12276 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12277 _ => None,
12278 },
12279 None => None,
12280 };
12281
12282 let Some(clipboard_text) = clipboard_text else {
12283 log::warn!("Clipboard doesn't contain text.");
12284 return;
12285 };
12286
12287 window.dispatch_action(
12288 Box::new(DiffClipboardWithSelectionData {
12289 clipboard_text,
12290 editor: cx.entity(),
12291 }),
12292 cx,
12293 );
12294 }
12295
12296 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12297 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12298 if let Some(item) = cx.read_from_clipboard() {
12299 let entries = item.entries();
12300
12301 match entries.first() {
12302 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12303 // of all the pasted entries.
12304 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12305 .do_paste(
12306 clipboard_string.text(),
12307 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12308 true,
12309 window,
12310 cx,
12311 ),
12312 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12313 }
12314 }
12315 }
12316
12317 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12318 if self.read_only(cx) {
12319 return;
12320 }
12321
12322 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12323
12324 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12325 if let Some((selections, _)) =
12326 self.selection_history.transaction(transaction_id).cloned()
12327 {
12328 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12329 s.select_anchors(selections.to_vec());
12330 });
12331 } else {
12332 log::error!(
12333 "No entry in selection_history found for undo. \
12334 This may correspond to a bug where undo does not update the selection. \
12335 If this is occurring, please add details to \
12336 https://github.com/zed-industries/zed/issues/22692"
12337 );
12338 }
12339 self.request_autoscroll(Autoscroll::fit(), cx);
12340 self.unmark_text(window, cx);
12341 self.refresh_edit_prediction(true, false, window, cx);
12342 cx.emit(EditorEvent::Edited { transaction_id });
12343 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12344 }
12345 }
12346
12347 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12348 if self.read_only(cx) {
12349 return;
12350 }
12351
12352 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12353
12354 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12355 if let Some((_, Some(selections))) =
12356 self.selection_history.transaction(transaction_id).cloned()
12357 {
12358 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12359 s.select_anchors(selections.to_vec());
12360 });
12361 } else {
12362 log::error!(
12363 "No entry in selection_history found for redo. \
12364 This may correspond to a bug where undo does not update the selection. \
12365 If this is occurring, please add details to \
12366 https://github.com/zed-industries/zed/issues/22692"
12367 );
12368 }
12369 self.request_autoscroll(Autoscroll::fit(), cx);
12370 self.unmark_text(window, cx);
12371 self.refresh_edit_prediction(true, false, window, cx);
12372 cx.emit(EditorEvent::Edited { transaction_id });
12373 }
12374 }
12375
12376 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12377 self.buffer
12378 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12379 }
12380
12381 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12382 self.buffer
12383 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12384 }
12385
12386 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12387 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12388 self.change_selections(Default::default(), window, cx, |s| {
12389 s.move_with(|map, selection| {
12390 let cursor = if selection.is_empty() {
12391 movement::left(map, selection.start)
12392 } else {
12393 selection.start
12394 };
12395 selection.collapse_to(cursor, SelectionGoal::None);
12396 });
12397 })
12398 }
12399
12400 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12401 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12402 self.change_selections(Default::default(), window, cx, |s| {
12403 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12404 })
12405 }
12406
12407 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12408 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12409 self.change_selections(Default::default(), window, cx, |s| {
12410 s.move_with(|map, selection| {
12411 let cursor = if selection.is_empty() {
12412 movement::right(map, selection.end)
12413 } else {
12414 selection.end
12415 };
12416 selection.collapse_to(cursor, SelectionGoal::None)
12417 });
12418 })
12419 }
12420
12421 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12422 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12423 self.change_selections(Default::default(), window, cx, |s| {
12424 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12425 })
12426 }
12427
12428 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12429 if self.take_rename(true, window, cx).is_some() {
12430 return;
12431 }
12432
12433 if self.mode.is_single_line() {
12434 cx.propagate();
12435 return;
12436 }
12437
12438 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12439
12440 let text_layout_details = &self.text_layout_details(window);
12441 let selection_count = self.selections.count();
12442 let first_selection = self.selections.first_anchor();
12443
12444 self.change_selections(Default::default(), window, cx, |s| {
12445 s.move_with(|map, selection| {
12446 if !selection.is_empty() {
12447 selection.goal = SelectionGoal::None;
12448 }
12449 let (cursor, goal) = movement::up(
12450 map,
12451 selection.start,
12452 selection.goal,
12453 false,
12454 text_layout_details,
12455 );
12456 selection.collapse_to(cursor, goal);
12457 });
12458 });
12459
12460 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12461 {
12462 cx.propagate();
12463 }
12464 }
12465
12466 pub fn move_up_by_lines(
12467 &mut self,
12468 action: &MoveUpByLines,
12469 window: &mut Window,
12470 cx: &mut Context<Self>,
12471 ) {
12472 if self.take_rename(true, window, cx).is_some() {
12473 return;
12474 }
12475
12476 if self.mode.is_single_line() {
12477 cx.propagate();
12478 return;
12479 }
12480
12481 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12482
12483 let text_layout_details = &self.text_layout_details(window);
12484
12485 self.change_selections(Default::default(), window, cx, |s| {
12486 s.move_with(|map, selection| {
12487 if !selection.is_empty() {
12488 selection.goal = SelectionGoal::None;
12489 }
12490 let (cursor, goal) = movement::up_by_rows(
12491 map,
12492 selection.start,
12493 action.lines,
12494 selection.goal,
12495 false,
12496 text_layout_details,
12497 );
12498 selection.collapse_to(cursor, goal);
12499 });
12500 })
12501 }
12502
12503 pub fn move_down_by_lines(
12504 &mut self,
12505 action: &MoveDownByLines,
12506 window: &mut Window,
12507 cx: &mut Context<Self>,
12508 ) {
12509 if self.take_rename(true, window, cx).is_some() {
12510 return;
12511 }
12512
12513 if self.mode.is_single_line() {
12514 cx.propagate();
12515 return;
12516 }
12517
12518 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12519
12520 let text_layout_details = &self.text_layout_details(window);
12521
12522 self.change_selections(Default::default(), window, cx, |s| {
12523 s.move_with(|map, selection| {
12524 if !selection.is_empty() {
12525 selection.goal = SelectionGoal::None;
12526 }
12527 let (cursor, goal) = movement::down_by_rows(
12528 map,
12529 selection.start,
12530 action.lines,
12531 selection.goal,
12532 false,
12533 text_layout_details,
12534 );
12535 selection.collapse_to(cursor, goal);
12536 });
12537 })
12538 }
12539
12540 pub fn select_down_by_lines(
12541 &mut self,
12542 action: &SelectDownByLines,
12543 window: &mut Window,
12544 cx: &mut Context<Self>,
12545 ) {
12546 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12547 let text_layout_details = &self.text_layout_details(window);
12548 self.change_selections(Default::default(), window, cx, |s| {
12549 s.move_heads_with(|map, head, goal| {
12550 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12551 })
12552 })
12553 }
12554
12555 pub fn select_up_by_lines(
12556 &mut self,
12557 action: &SelectUpByLines,
12558 window: &mut Window,
12559 cx: &mut Context<Self>,
12560 ) {
12561 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12562 let text_layout_details = &self.text_layout_details(window);
12563 self.change_selections(Default::default(), window, cx, |s| {
12564 s.move_heads_with(|map, head, goal| {
12565 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12566 })
12567 })
12568 }
12569
12570 pub fn select_page_up(
12571 &mut self,
12572 _: &SelectPageUp,
12573 window: &mut Window,
12574 cx: &mut Context<Self>,
12575 ) {
12576 let Some(row_count) = self.visible_row_count() else {
12577 return;
12578 };
12579
12580 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12581
12582 let text_layout_details = &self.text_layout_details(window);
12583
12584 self.change_selections(Default::default(), window, cx, |s| {
12585 s.move_heads_with(|map, head, goal| {
12586 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12587 })
12588 })
12589 }
12590
12591 pub fn move_page_up(
12592 &mut self,
12593 action: &MovePageUp,
12594 window: &mut Window,
12595 cx: &mut Context<Self>,
12596 ) {
12597 if self.take_rename(true, window, cx).is_some() {
12598 return;
12599 }
12600
12601 if self
12602 .context_menu
12603 .borrow_mut()
12604 .as_mut()
12605 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12606 .unwrap_or(false)
12607 {
12608 return;
12609 }
12610
12611 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12612 cx.propagate();
12613 return;
12614 }
12615
12616 let Some(row_count) = self.visible_row_count() else {
12617 return;
12618 };
12619
12620 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12621
12622 let effects = if action.center_cursor {
12623 SelectionEffects::scroll(Autoscroll::center())
12624 } else {
12625 SelectionEffects::default()
12626 };
12627
12628 let text_layout_details = &self.text_layout_details(window);
12629
12630 self.change_selections(effects, window, cx, |s| {
12631 s.move_with(|map, selection| {
12632 if !selection.is_empty() {
12633 selection.goal = SelectionGoal::None;
12634 }
12635 let (cursor, goal) = movement::up_by_rows(
12636 map,
12637 selection.end,
12638 row_count,
12639 selection.goal,
12640 false,
12641 text_layout_details,
12642 );
12643 selection.collapse_to(cursor, goal);
12644 });
12645 });
12646 }
12647
12648 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12649 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12650 let text_layout_details = &self.text_layout_details(window);
12651 self.change_selections(Default::default(), window, cx, |s| {
12652 s.move_heads_with(|map, head, goal| {
12653 movement::up(map, head, goal, false, text_layout_details)
12654 })
12655 })
12656 }
12657
12658 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12659 self.take_rename(true, window, cx);
12660
12661 if self.mode.is_single_line() {
12662 cx.propagate();
12663 return;
12664 }
12665
12666 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12667
12668 let text_layout_details = &self.text_layout_details(window);
12669 let selection_count = self.selections.count();
12670 let first_selection = self.selections.first_anchor();
12671
12672 self.change_selections(Default::default(), window, cx, |s| {
12673 s.move_with(|map, selection| {
12674 if !selection.is_empty() {
12675 selection.goal = SelectionGoal::None;
12676 }
12677 let (cursor, goal) = movement::down(
12678 map,
12679 selection.end,
12680 selection.goal,
12681 false,
12682 text_layout_details,
12683 );
12684 selection.collapse_to(cursor, goal);
12685 });
12686 });
12687
12688 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12689 {
12690 cx.propagate();
12691 }
12692 }
12693
12694 pub fn select_page_down(
12695 &mut self,
12696 _: &SelectPageDown,
12697 window: &mut Window,
12698 cx: &mut Context<Self>,
12699 ) {
12700 let Some(row_count) = self.visible_row_count() else {
12701 return;
12702 };
12703
12704 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12705
12706 let text_layout_details = &self.text_layout_details(window);
12707
12708 self.change_selections(Default::default(), window, cx, |s| {
12709 s.move_heads_with(|map, head, goal| {
12710 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12711 })
12712 })
12713 }
12714
12715 pub fn move_page_down(
12716 &mut self,
12717 action: &MovePageDown,
12718 window: &mut Window,
12719 cx: &mut Context<Self>,
12720 ) {
12721 if self.take_rename(true, window, cx).is_some() {
12722 return;
12723 }
12724
12725 if self
12726 .context_menu
12727 .borrow_mut()
12728 .as_mut()
12729 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12730 .unwrap_or(false)
12731 {
12732 return;
12733 }
12734
12735 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12736 cx.propagate();
12737 return;
12738 }
12739
12740 let Some(row_count) = self.visible_row_count() else {
12741 return;
12742 };
12743
12744 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12745
12746 let effects = if action.center_cursor {
12747 SelectionEffects::scroll(Autoscroll::center())
12748 } else {
12749 SelectionEffects::default()
12750 };
12751
12752 let text_layout_details = &self.text_layout_details(window);
12753 self.change_selections(effects, window, cx, |s| {
12754 s.move_with(|map, selection| {
12755 if !selection.is_empty() {
12756 selection.goal = SelectionGoal::None;
12757 }
12758 let (cursor, goal) = movement::down_by_rows(
12759 map,
12760 selection.end,
12761 row_count,
12762 selection.goal,
12763 false,
12764 text_layout_details,
12765 );
12766 selection.collapse_to(cursor, goal);
12767 });
12768 });
12769 }
12770
12771 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12772 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12773 let text_layout_details = &self.text_layout_details(window);
12774 self.change_selections(Default::default(), window, cx, |s| {
12775 s.move_heads_with(|map, head, goal| {
12776 movement::down(map, head, goal, false, text_layout_details)
12777 })
12778 });
12779 }
12780
12781 pub fn context_menu_first(
12782 &mut self,
12783 _: &ContextMenuFirst,
12784 window: &mut Window,
12785 cx: &mut Context<Self>,
12786 ) {
12787 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12788 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12789 }
12790 }
12791
12792 pub fn context_menu_prev(
12793 &mut self,
12794 _: &ContextMenuPrevious,
12795 window: &mut Window,
12796 cx: &mut Context<Self>,
12797 ) {
12798 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12799 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12800 }
12801 }
12802
12803 pub fn context_menu_next(
12804 &mut self,
12805 _: &ContextMenuNext,
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_next(self.completion_provider.as_deref(), window, cx);
12811 }
12812 }
12813
12814 pub fn context_menu_last(
12815 &mut self,
12816 _: &ContextMenuLast,
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_last(self.completion_provider.as_deref(), window, cx);
12822 }
12823 }
12824
12825 pub fn signature_help_prev(
12826 &mut self,
12827 _: &SignatureHelpPrevious,
12828 _: &mut Window,
12829 cx: &mut Context<Self>,
12830 ) {
12831 if let Some(popover) = self.signature_help_state.popover_mut() {
12832 if popover.current_signature == 0 {
12833 popover.current_signature = popover.signatures.len() - 1;
12834 } else {
12835 popover.current_signature -= 1;
12836 }
12837 cx.notify();
12838 }
12839 }
12840
12841 pub fn signature_help_next(
12842 &mut self,
12843 _: &SignatureHelpNext,
12844 _: &mut Window,
12845 cx: &mut Context<Self>,
12846 ) {
12847 if let Some(popover) = self.signature_help_state.popover_mut() {
12848 if popover.current_signature + 1 == popover.signatures.len() {
12849 popover.current_signature = 0;
12850 } else {
12851 popover.current_signature += 1;
12852 }
12853 cx.notify();
12854 }
12855 }
12856
12857 pub fn move_to_previous_word_start(
12858 &mut self,
12859 _: &MoveToPreviousWordStart,
12860 window: &mut Window,
12861 cx: &mut Context<Self>,
12862 ) {
12863 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12864 self.change_selections(Default::default(), window, cx, |s| {
12865 s.move_cursors_with(|map, head, _| {
12866 (
12867 movement::previous_word_start(map, head),
12868 SelectionGoal::None,
12869 )
12870 });
12871 })
12872 }
12873
12874 pub fn move_to_previous_subword_start(
12875 &mut self,
12876 _: &MoveToPreviousSubwordStart,
12877 window: &mut Window,
12878 cx: &mut Context<Self>,
12879 ) {
12880 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12881 self.change_selections(Default::default(), window, cx, |s| {
12882 s.move_cursors_with(|map, head, _| {
12883 (
12884 movement::previous_subword_start(map, head),
12885 SelectionGoal::None,
12886 )
12887 });
12888 })
12889 }
12890
12891 pub fn select_to_previous_word_start(
12892 &mut self,
12893 _: &SelectToPreviousWordStart,
12894 window: &mut Window,
12895 cx: &mut Context<Self>,
12896 ) {
12897 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12898 self.change_selections(Default::default(), window, cx, |s| {
12899 s.move_heads_with(|map, head, _| {
12900 (
12901 movement::previous_word_start(map, head),
12902 SelectionGoal::None,
12903 )
12904 });
12905 })
12906 }
12907
12908 pub fn select_to_previous_subword_start(
12909 &mut self,
12910 _: &SelectToPreviousSubwordStart,
12911 window: &mut Window,
12912 cx: &mut Context<Self>,
12913 ) {
12914 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12915 self.change_selections(Default::default(), window, cx, |s| {
12916 s.move_heads_with(|map, head, _| {
12917 (
12918 movement::previous_subword_start(map, head),
12919 SelectionGoal::None,
12920 )
12921 });
12922 })
12923 }
12924
12925 pub fn delete_to_previous_word_start(
12926 &mut self,
12927 action: &DeleteToPreviousWordStart,
12928 window: &mut Window,
12929 cx: &mut Context<Self>,
12930 ) {
12931 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12932 self.transact(window, cx, |this, window, cx| {
12933 this.select_autoclose_pair(window, cx);
12934 this.change_selections(Default::default(), window, cx, |s| {
12935 s.move_with(|map, selection| {
12936 if selection.is_empty() {
12937 let cursor = if action.ignore_newlines {
12938 movement::previous_word_start(map, selection.head())
12939 } else {
12940 movement::previous_word_start_or_newline(map, selection.head())
12941 };
12942 selection.set_head(cursor, SelectionGoal::None);
12943 }
12944 });
12945 });
12946 this.insert("", window, cx);
12947 });
12948 }
12949
12950 pub fn delete_to_previous_subword_start(
12951 &mut self,
12952 _: &DeleteToPreviousSubwordStart,
12953 window: &mut Window,
12954 cx: &mut Context<Self>,
12955 ) {
12956 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12957 self.transact(window, cx, |this, window, cx| {
12958 this.select_autoclose_pair(window, cx);
12959 this.change_selections(Default::default(), window, cx, |s| {
12960 s.move_with(|map, selection| {
12961 if selection.is_empty() {
12962 let cursor = movement::previous_subword_start(map, selection.head());
12963 selection.set_head(cursor, SelectionGoal::None);
12964 }
12965 });
12966 });
12967 this.insert("", window, cx);
12968 });
12969 }
12970
12971 pub fn move_to_next_word_end(
12972 &mut self,
12973 _: &MoveToNextWordEnd,
12974 window: &mut Window,
12975 cx: &mut Context<Self>,
12976 ) {
12977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12978 self.change_selections(Default::default(), window, cx, |s| {
12979 s.move_cursors_with(|map, head, _| {
12980 (movement::next_word_end(map, head), SelectionGoal::None)
12981 });
12982 })
12983 }
12984
12985 pub fn move_to_next_subword_end(
12986 &mut self,
12987 _: &MoveToNextSubwordEnd,
12988 window: &mut Window,
12989 cx: &mut Context<Self>,
12990 ) {
12991 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12992 self.change_selections(Default::default(), window, cx, |s| {
12993 s.move_cursors_with(|map, head, _| {
12994 (movement::next_subword_end(map, head), SelectionGoal::None)
12995 });
12996 })
12997 }
12998
12999 pub fn select_to_next_word_end(
13000 &mut self,
13001 _: &SelectToNextWordEnd,
13002 window: &mut Window,
13003 cx: &mut Context<Self>,
13004 ) {
13005 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13006 self.change_selections(Default::default(), window, cx, |s| {
13007 s.move_heads_with(|map, head, _| {
13008 (movement::next_word_end(map, head), SelectionGoal::None)
13009 });
13010 })
13011 }
13012
13013 pub fn select_to_next_subword_end(
13014 &mut self,
13015 _: &SelectToNextSubwordEnd,
13016 window: &mut Window,
13017 cx: &mut Context<Self>,
13018 ) {
13019 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13020 self.change_selections(Default::default(), window, cx, |s| {
13021 s.move_heads_with(|map, head, _| {
13022 (movement::next_subword_end(map, head), SelectionGoal::None)
13023 });
13024 })
13025 }
13026
13027 pub fn delete_to_next_word_end(
13028 &mut self,
13029 action: &DeleteToNextWordEnd,
13030 window: &mut Window,
13031 cx: &mut Context<Self>,
13032 ) {
13033 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13034 self.transact(window, cx, |this, window, cx| {
13035 this.change_selections(Default::default(), window, cx, |s| {
13036 s.move_with(|map, selection| {
13037 if selection.is_empty() {
13038 let cursor = if action.ignore_newlines {
13039 movement::next_word_end(map, selection.head())
13040 } else {
13041 movement::next_word_end_or_newline(map, selection.head())
13042 };
13043 selection.set_head(cursor, SelectionGoal::None);
13044 }
13045 });
13046 });
13047 this.insert("", window, cx);
13048 });
13049 }
13050
13051 pub fn delete_to_next_subword_end(
13052 &mut self,
13053 _: &DeleteToNextSubwordEnd,
13054 window: &mut Window,
13055 cx: &mut Context<Self>,
13056 ) {
13057 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13058 self.transact(window, cx, |this, window, cx| {
13059 this.change_selections(Default::default(), window, cx, |s| {
13060 s.move_with(|map, selection| {
13061 if selection.is_empty() {
13062 let cursor = movement::next_subword_end(map, selection.head());
13063 selection.set_head(cursor, SelectionGoal::None);
13064 }
13065 });
13066 });
13067 this.insert("", window, cx);
13068 });
13069 }
13070
13071 pub fn move_to_beginning_of_line(
13072 &mut self,
13073 action: &MoveToBeginningOfLine,
13074 window: &mut Window,
13075 cx: &mut Context<Self>,
13076 ) {
13077 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13078 self.change_selections(Default::default(), window, cx, |s| {
13079 s.move_cursors_with(|map, head, _| {
13080 (
13081 movement::indented_line_beginning(
13082 map,
13083 head,
13084 action.stop_at_soft_wraps,
13085 action.stop_at_indent,
13086 ),
13087 SelectionGoal::None,
13088 )
13089 });
13090 })
13091 }
13092
13093 pub fn select_to_beginning_of_line(
13094 &mut self,
13095 action: &SelectToBeginningOfLine,
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_heads_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 delete_to_beginning_of_line(
13116 &mut self,
13117 action: &DeleteToBeginningOfLine,
13118 window: &mut Window,
13119 cx: &mut Context<Self>,
13120 ) {
13121 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13122 self.transact(window, cx, |this, window, cx| {
13123 this.change_selections(Default::default(), window, cx, |s| {
13124 s.move_with(|_, selection| {
13125 selection.reversed = true;
13126 });
13127 });
13128
13129 this.select_to_beginning_of_line(
13130 &SelectToBeginningOfLine {
13131 stop_at_soft_wraps: false,
13132 stop_at_indent: action.stop_at_indent,
13133 },
13134 window,
13135 cx,
13136 );
13137 this.backspace(&Backspace, window, cx);
13138 });
13139 }
13140
13141 pub fn move_to_end_of_line(
13142 &mut self,
13143 action: &MoveToEndOfLine,
13144 window: &mut Window,
13145 cx: &mut Context<Self>,
13146 ) {
13147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13148 self.change_selections(Default::default(), window, cx, |s| {
13149 s.move_cursors_with(|map, head, _| {
13150 (
13151 movement::line_end(map, head, action.stop_at_soft_wraps),
13152 SelectionGoal::None,
13153 )
13154 });
13155 })
13156 }
13157
13158 pub fn select_to_end_of_line(
13159 &mut self,
13160 action: &SelectToEndOfLine,
13161 window: &mut Window,
13162 cx: &mut Context<Self>,
13163 ) {
13164 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13165 self.change_selections(Default::default(), window, cx, |s| {
13166 s.move_heads_with(|map, head, _| {
13167 (
13168 movement::line_end(map, head, action.stop_at_soft_wraps),
13169 SelectionGoal::None,
13170 )
13171 });
13172 })
13173 }
13174
13175 pub fn delete_to_end_of_line(
13176 &mut self,
13177 _: &DeleteToEndOfLine,
13178 window: &mut Window,
13179 cx: &mut Context<Self>,
13180 ) {
13181 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13182 self.transact(window, cx, |this, window, cx| {
13183 this.select_to_end_of_line(
13184 &SelectToEndOfLine {
13185 stop_at_soft_wraps: false,
13186 },
13187 window,
13188 cx,
13189 );
13190 this.delete(&Delete, window, cx);
13191 });
13192 }
13193
13194 pub fn cut_to_end_of_line(
13195 &mut self,
13196 _: &CutToEndOfLine,
13197 window: &mut Window,
13198 cx: &mut Context<Self>,
13199 ) {
13200 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13201 self.transact(window, cx, |this, window, cx| {
13202 this.select_to_end_of_line(
13203 &SelectToEndOfLine {
13204 stop_at_soft_wraps: false,
13205 },
13206 window,
13207 cx,
13208 );
13209 this.cut(&Cut, window, cx);
13210 });
13211 }
13212
13213 pub fn move_to_start_of_paragraph(
13214 &mut self,
13215 _: &MoveToStartOfParagraph,
13216 window: &mut Window,
13217 cx: &mut Context<Self>,
13218 ) {
13219 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13220 cx.propagate();
13221 return;
13222 }
13223 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13224 self.change_selections(Default::default(), window, cx, |s| {
13225 s.move_with(|map, selection| {
13226 selection.collapse_to(
13227 movement::start_of_paragraph(map, selection.head(), 1),
13228 SelectionGoal::None,
13229 )
13230 });
13231 })
13232 }
13233
13234 pub fn move_to_end_of_paragraph(
13235 &mut self,
13236 _: &MoveToEndOfParagraph,
13237 window: &mut Window,
13238 cx: &mut Context<Self>,
13239 ) {
13240 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13241 cx.propagate();
13242 return;
13243 }
13244 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13245 self.change_selections(Default::default(), window, cx, |s| {
13246 s.move_with(|map, selection| {
13247 selection.collapse_to(
13248 movement::end_of_paragraph(map, selection.head(), 1),
13249 SelectionGoal::None,
13250 )
13251 });
13252 })
13253 }
13254
13255 pub fn select_to_start_of_paragraph(
13256 &mut self,
13257 _: &SelectToStartOfParagraph,
13258 window: &mut Window,
13259 cx: &mut Context<Self>,
13260 ) {
13261 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13262 cx.propagate();
13263 return;
13264 }
13265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13266 self.change_selections(Default::default(), window, cx, |s| {
13267 s.move_heads_with(|map, head, _| {
13268 (
13269 movement::start_of_paragraph(map, head, 1),
13270 SelectionGoal::None,
13271 )
13272 });
13273 })
13274 }
13275
13276 pub fn select_to_end_of_paragraph(
13277 &mut self,
13278 _: &SelectToEndOfParagraph,
13279 window: &mut Window,
13280 cx: &mut Context<Self>,
13281 ) {
13282 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13283 cx.propagate();
13284 return;
13285 }
13286 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13287 self.change_selections(Default::default(), window, cx, |s| {
13288 s.move_heads_with(|map, head, _| {
13289 (
13290 movement::end_of_paragraph(map, head, 1),
13291 SelectionGoal::None,
13292 )
13293 });
13294 })
13295 }
13296
13297 pub fn move_to_start_of_excerpt(
13298 &mut self,
13299 _: &MoveToStartOfExcerpt,
13300 window: &mut Window,
13301 cx: &mut Context<Self>,
13302 ) {
13303 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13304 cx.propagate();
13305 return;
13306 }
13307 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13308 self.change_selections(Default::default(), window, cx, |s| {
13309 s.move_with(|map, selection| {
13310 selection.collapse_to(
13311 movement::start_of_excerpt(
13312 map,
13313 selection.head(),
13314 workspace::searchable::Direction::Prev,
13315 ),
13316 SelectionGoal::None,
13317 )
13318 });
13319 })
13320 }
13321
13322 pub fn move_to_start_of_next_excerpt(
13323 &mut self,
13324 _: &MoveToStartOfNextExcerpt,
13325 window: &mut Window,
13326 cx: &mut Context<Self>,
13327 ) {
13328 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13329 cx.propagate();
13330 return;
13331 }
13332
13333 self.change_selections(Default::default(), window, cx, |s| {
13334 s.move_with(|map, selection| {
13335 selection.collapse_to(
13336 movement::start_of_excerpt(
13337 map,
13338 selection.head(),
13339 workspace::searchable::Direction::Next,
13340 ),
13341 SelectionGoal::None,
13342 )
13343 });
13344 })
13345 }
13346
13347 pub fn move_to_end_of_excerpt(
13348 &mut self,
13349 _: &MoveToEndOfExcerpt,
13350 window: &mut Window,
13351 cx: &mut Context<Self>,
13352 ) {
13353 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13354 cx.propagate();
13355 return;
13356 }
13357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13358 self.change_selections(Default::default(), window, cx, |s| {
13359 s.move_with(|map, selection| {
13360 selection.collapse_to(
13361 movement::end_of_excerpt(
13362 map,
13363 selection.head(),
13364 workspace::searchable::Direction::Next,
13365 ),
13366 SelectionGoal::None,
13367 )
13368 });
13369 })
13370 }
13371
13372 pub fn move_to_end_of_previous_excerpt(
13373 &mut self,
13374 _: &MoveToEndOfPreviousExcerpt,
13375 window: &mut Window,
13376 cx: &mut Context<Self>,
13377 ) {
13378 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13379 cx.propagate();
13380 return;
13381 }
13382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13383 self.change_selections(Default::default(), window, cx, |s| {
13384 s.move_with(|map, selection| {
13385 selection.collapse_to(
13386 movement::end_of_excerpt(
13387 map,
13388 selection.head(),
13389 workspace::searchable::Direction::Prev,
13390 ),
13391 SelectionGoal::None,
13392 )
13393 });
13394 })
13395 }
13396
13397 pub fn select_to_start_of_excerpt(
13398 &mut self,
13399 _: &SelectToStartOfExcerpt,
13400 window: &mut Window,
13401 cx: &mut Context<Self>,
13402 ) {
13403 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13404 cx.propagate();
13405 return;
13406 }
13407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13408 self.change_selections(Default::default(), window, cx, |s| {
13409 s.move_heads_with(|map, head, _| {
13410 (
13411 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13412 SelectionGoal::None,
13413 )
13414 });
13415 })
13416 }
13417
13418 pub fn select_to_start_of_next_excerpt(
13419 &mut self,
13420 _: &SelectToStartOfNextExcerpt,
13421 window: &mut Window,
13422 cx: &mut Context<Self>,
13423 ) {
13424 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13425 cx.propagate();
13426 return;
13427 }
13428 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13429 self.change_selections(Default::default(), window, cx, |s| {
13430 s.move_heads_with(|map, head, _| {
13431 (
13432 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13433 SelectionGoal::None,
13434 )
13435 });
13436 })
13437 }
13438
13439 pub fn select_to_end_of_excerpt(
13440 &mut self,
13441 _: &SelectToEndOfExcerpt,
13442 window: &mut Window,
13443 cx: &mut Context<Self>,
13444 ) {
13445 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13446 cx.propagate();
13447 return;
13448 }
13449 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13450 self.change_selections(Default::default(), window, cx, |s| {
13451 s.move_heads_with(|map, head, _| {
13452 (
13453 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13454 SelectionGoal::None,
13455 )
13456 });
13457 })
13458 }
13459
13460 pub fn select_to_end_of_previous_excerpt(
13461 &mut self,
13462 _: &SelectToEndOfPreviousExcerpt,
13463 window: &mut Window,
13464 cx: &mut Context<Self>,
13465 ) {
13466 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13467 cx.propagate();
13468 return;
13469 }
13470 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13471 self.change_selections(Default::default(), window, cx, |s| {
13472 s.move_heads_with(|map, head, _| {
13473 (
13474 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13475 SelectionGoal::None,
13476 )
13477 });
13478 })
13479 }
13480
13481 pub fn move_to_beginning(
13482 &mut self,
13483 _: &MoveToBeginning,
13484 window: &mut Window,
13485 cx: &mut Context<Self>,
13486 ) {
13487 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13488 cx.propagate();
13489 return;
13490 }
13491 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13492 self.change_selections(Default::default(), window, cx, |s| {
13493 s.select_ranges(vec![0..0]);
13494 });
13495 }
13496
13497 pub fn select_to_beginning(
13498 &mut self,
13499 _: &SelectToBeginning,
13500 window: &mut Window,
13501 cx: &mut Context<Self>,
13502 ) {
13503 let mut selection = self.selections.last::<Point>(cx);
13504 selection.set_head(Point::zero(), SelectionGoal::None);
13505 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13506 self.change_selections(Default::default(), window, cx, |s| {
13507 s.select(vec![selection]);
13508 });
13509 }
13510
13511 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13512 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13513 cx.propagate();
13514 return;
13515 }
13516 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13517 let cursor = self.buffer.read(cx).read(cx).len();
13518 self.change_selections(Default::default(), window, cx, |s| {
13519 s.select_ranges(vec![cursor..cursor])
13520 });
13521 }
13522
13523 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13524 self.nav_history = nav_history;
13525 }
13526
13527 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13528 self.nav_history.as_ref()
13529 }
13530
13531 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13532 self.push_to_nav_history(
13533 self.selections.newest_anchor().head(),
13534 None,
13535 false,
13536 true,
13537 cx,
13538 );
13539 }
13540
13541 fn push_to_nav_history(
13542 &mut self,
13543 cursor_anchor: Anchor,
13544 new_position: Option<Point>,
13545 is_deactivate: bool,
13546 always: bool,
13547 cx: &mut Context<Self>,
13548 ) {
13549 if let Some(nav_history) = self.nav_history.as_mut() {
13550 let buffer = self.buffer.read(cx).read(cx);
13551 let cursor_position = cursor_anchor.to_point(&buffer);
13552 let scroll_state = self.scroll_manager.anchor();
13553 let scroll_top_row = scroll_state.top_row(&buffer);
13554 drop(buffer);
13555
13556 if let Some(new_position) = new_position {
13557 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13558 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13559 return;
13560 }
13561 }
13562
13563 nav_history.push(
13564 Some(NavigationData {
13565 cursor_anchor,
13566 cursor_position,
13567 scroll_anchor: scroll_state,
13568 scroll_top_row,
13569 }),
13570 cx,
13571 );
13572 cx.emit(EditorEvent::PushedToNavHistory {
13573 anchor: cursor_anchor,
13574 is_deactivate,
13575 })
13576 }
13577 }
13578
13579 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13580 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13581 let buffer = self.buffer.read(cx).snapshot(cx);
13582 let mut selection = self.selections.first::<usize>(cx);
13583 selection.set_head(buffer.len(), SelectionGoal::None);
13584 self.change_selections(Default::default(), window, cx, |s| {
13585 s.select(vec![selection]);
13586 });
13587 }
13588
13589 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13590 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13591 let end = self.buffer.read(cx).read(cx).len();
13592 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13593 s.select_ranges(vec![0..end]);
13594 });
13595 }
13596
13597 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13598 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13599 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13600 let mut selections = self.selections.all::<Point>(cx);
13601 let max_point = display_map.buffer_snapshot.max_point();
13602 for selection in &mut selections {
13603 let rows = selection.spanned_rows(true, &display_map);
13604 selection.start = Point::new(rows.start.0, 0);
13605 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13606 selection.reversed = false;
13607 }
13608 self.change_selections(Default::default(), window, cx, |s| {
13609 s.select(selections);
13610 });
13611 }
13612
13613 pub fn split_selection_into_lines(
13614 &mut self,
13615 _: &SplitSelectionIntoLines,
13616 window: &mut Window,
13617 cx: &mut Context<Self>,
13618 ) {
13619 let selections = self
13620 .selections
13621 .all::<Point>(cx)
13622 .into_iter()
13623 .map(|selection| selection.start..selection.end)
13624 .collect::<Vec<_>>();
13625 self.unfold_ranges(&selections, true, true, cx);
13626
13627 let mut new_selection_ranges = Vec::new();
13628 {
13629 let buffer = self.buffer.read(cx).read(cx);
13630 for selection in selections {
13631 for row in selection.start.row..selection.end.row {
13632 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13633 new_selection_ranges.push(cursor..cursor);
13634 }
13635
13636 let is_multiline_selection = selection.start.row != selection.end.row;
13637 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13638 // so this action feels more ergonomic when paired with other selection operations
13639 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13640 if !should_skip_last {
13641 new_selection_ranges.push(selection.end..selection.end);
13642 }
13643 }
13644 }
13645 self.change_selections(Default::default(), window, cx, |s| {
13646 s.select_ranges(new_selection_ranges);
13647 });
13648 }
13649
13650 pub fn add_selection_above(
13651 &mut self,
13652 _: &AddSelectionAbove,
13653 window: &mut Window,
13654 cx: &mut Context<Self>,
13655 ) {
13656 self.add_selection(true, window, cx);
13657 }
13658
13659 pub fn add_selection_below(
13660 &mut self,
13661 _: &AddSelectionBelow,
13662 window: &mut Window,
13663 cx: &mut Context<Self>,
13664 ) {
13665 self.add_selection(false, window, cx);
13666 }
13667
13668 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13669 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13670
13671 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13672 let all_selections = self.selections.all::<Point>(cx);
13673 let text_layout_details = self.text_layout_details(window);
13674
13675 let (mut columnar_selections, new_selections_to_columnarize) = {
13676 if let Some(state) = self.add_selections_state.as_ref() {
13677 let columnar_selection_ids: HashSet<_> = state
13678 .groups
13679 .iter()
13680 .flat_map(|group| group.stack.iter())
13681 .copied()
13682 .collect();
13683
13684 all_selections
13685 .into_iter()
13686 .partition(|s| columnar_selection_ids.contains(&s.id))
13687 } else {
13688 (Vec::new(), all_selections)
13689 }
13690 };
13691
13692 let mut state = self
13693 .add_selections_state
13694 .take()
13695 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13696
13697 for selection in new_selections_to_columnarize {
13698 let range = selection.display_range(&display_map).sorted();
13699 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13700 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13701 let positions = start_x.min(end_x)..start_x.max(end_x);
13702 let mut stack = Vec::new();
13703 for row in range.start.row().0..=range.end.row().0 {
13704 if let Some(selection) = self.selections.build_columnar_selection(
13705 &display_map,
13706 DisplayRow(row),
13707 &positions,
13708 selection.reversed,
13709 &text_layout_details,
13710 ) {
13711 stack.push(selection.id);
13712 columnar_selections.push(selection);
13713 }
13714 }
13715 if !stack.is_empty() {
13716 if above {
13717 stack.reverse();
13718 }
13719 state.groups.push(AddSelectionsGroup { above, stack });
13720 }
13721 }
13722
13723 let mut final_selections = Vec::new();
13724 let end_row = if above {
13725 DisplayRow(0)
13726 } else {
13727 display_map.max_point().row()
13728 };
13729
13730 let mut last_added_item_per_group = HashMap::default();
13731 for group in state.groups.iter_mut() {
13732 if let Some(last_id) = group.stack.last() {
13733 last_added_item_per_group.insert(*last_id, group);
13734 }
13735 }
13736
13737 for selection in columnar_selections {
13738 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13739 if above == group.above {
13740 let range = selection.display_range(&display_map).sorted();
13741 debug_assert_eq!(range.start.row(), range.end.row());
13742 let mut row = range.start.row();
13743 let positions =
13744 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13745 px(start)..px(end)
13746 } else {
13747 let start_x =
13748 display_map.x_for_display_point(range.start, &text_layout_details);
13749 let end_x =
13750 display_map.x_for_display_point(range.end, &text_layout_details);
13751 start_x.min(end_x)..start_x.max(end_x)
13752 };
13753
13754 let mut maybe_new_selection = None;
13755 while row != end_row {
13756 if above {
13757 row.0 -= 1;
13758 } else {
13759 row.0 += 1;
13760 }
13761 if let Some(new_selection) = self.selections.build_columnar_selection(
13762 &display_map,
13763 row,
13764 &positions,
13765 selection.reversed,
13766 &text_layout_details,
13767 ) {
13768 maybe_new_selection = Some(new_selection);
13769 break;
13770 }
13771 }
13772
13773 if let Some(new_selection) = maybe_new_selection {
13774 group.stack.push(new_selection.id);
13775 if above {
13776 final_selections.push(new_selection);
13777 final_selections.push(selection);
13778 } else {
13779 final_selections.push(selection);
13780 final_selections.push(new_selection);
13781 }
13782 } else {
13783 final_selections.push(selection);
13784 }
13785 } else {
13786 group.stack.pop();
13787 }
13788 } else {
13789 final_selections.push(selection);
13790 }
13791 }
13792
13793 self.change_selections(Default::default(), window, cx, |s| {
13794 s.select(final_selections);
13795 });
13796
13797 let final_selection_ids: HashSet<_> = self
13798 .selections
13799 .all::<Point>(cx)
13800 .iter()
13801 .map(|s| s.id)
13802 .collect();
13803 state.groups.retain_mut(|group| {
13804 // selections might get merged above so we remove invalid items from stacks
13805 group.stack.retain(|id| final_selection_ids.contains(id));
13806
13807 // single selection in stack can be treated as initial state
13808 group.stack.len() > 1
13809 });
13810
13811 if !state.groups.is_empty() {
13812 self.add_selections_state = Some(state);
13813 }
13814 }
13815
13816 fn select_match_ranges(
13817 &mut self,
13818 range: Range<usize>,
13819 reversed: bool,
13820 replace_newest: bool,
13821 auto_scroll: Option<Autoscroll>,
13822 window: &mut Window,
13823 cx: &mut Context<Editor>,
13824 ) {
13825 self.unfold_ranges(
13826 std::slice::from_ref(&range),
13827 false,
13828 auto_scroll.is_some(),
13829 cx,
13830 );
13831 let effects = if let Some(scroll) = auto_scroll {
13832 SelectionEffects::scroll(scroll)
13833 } else {
13834 SelectionEffects::no_scroll()
13835 };
13836 self.change_selections(effects, window, cx, |s| {
13837 if replace_newest {
13838 s.delete(s.newest_anchor().id);
13839 }
13840 if reversed {
13841 s.insert_range(range.end..range.start);
13842 } else {
13843 s.insert_range(range);
13844 }
13845 });
13846 }
13847
13848 pub fn select_next_match_internal(
13849 &mut self,
13850 display_map: &DisplaySnapshot,
13851 replace_newest: bool,
13852 autoscroll: Option<Autoscroll>,
13853 window: &mut Window,
13854 cx: &mut Context<Self>,
13855 ) -> Result<()> {
13856 let buffer = &display_map.buffer_snapshot;
13857 let mut selections = self.selections.all::<usize>(cx);
13858 if let Some(mut select_next_state) = self.select_next_state.take() {
13859 let query = &select_next_state.query;
13860 if !select_next_state.done {
13861 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13862 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13863 let mut next_selected_range = None;
13864
13865 let bytes_after_last_selection =
13866 buffer.bytes_in_range(last_selection.end..buffer.len());
13867 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13868 let query_matches = query
13869 .stream_find_iter(bytes_after_last_selection)
13870 .map(|result| (last_selection.end, result))
13871 .chain(
13872 query
13873 .stream_find_iter(bytes_before_first_selection)
13874 .map(|result| (0, result)),
13875 );
13876
13877 for (start_offset, query_match) in query_matches {
13878 let query_match = query_match.unwrap(); // can only fail due to I/O
13879 let offset_range =
13880 start_offset + query_match.start()..start_offset + query_match.end();
13881
13882 if !select_next_state.wordwise
13883 || (!buffer.is_inside_word(offset_range.start, false)
13884 && !buffer.is_inside_word(offset_range.end, false))
13885 {
13886 // TODO: This is n^2, because we might check all the selections
13887 if !selections
13888 .iter()
13889 .any(|selection| selection.range().overlaps(&offset_range))
13890 {
13891 next_selected_range = Some(offset_range);
13892 break;
13893 }
13894 }
13895 }
13896
13897 if let Some(next_selected_range) = next_selected_range {
13898 self.select_match_ranges(
13899 next_selected_range,
13900 last_selection.reversed,
13901 replace_newest,
13902 autoscroll,
13903 window,
13904 cx,
13905 );
13906 } else {
13907 select_next_state.done = true;
13908 }
13909 }
13910
13911 self.select_next_state = Some(select_next_state);
13912 } else {
13913 let mut only_carets = true;
13914 let mut same_text_selected = true;
13915 let mut selected_text = None;
13916
13917 let mut selections_iter = selections.iter().peekable();
13918 while let Some(selection) = selections_iter.next() {
13919 if selection.start != selection.end {
13920 only_carets = false;
13921 }
13922
13923 if same_text_selected {
13924 if selected_text.is_none() {
13925 selected_text =
13926 Some(buffer.text_for_range(selection.range()).collect::<String>());
13927 }
13928
13929 if let Some(next_selection) = selections_iter.peek() {
13930 if next_selection.range().len() == selection.range().len() {
13931 let next_selected_text = buffer
13932 .text_for_range(next_selection.range())
13933 .collect::<String>();
13934 if Some(next_selected_text) != selected_text {
13935 same_text_selected = false;
13936 selected_text = None;
13937 }
13938 } else {
13939 same_text_selected = false;
13940 selected_text = None;
13941 }
13942 }
13943 }
13944 }
13945
13946 if only_carets {
13947 for selection in &mut selections {
13948 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13949 selection.start = word_range.start;
13950 selection.end = word_range.end;
13951 selection.goal = SelectionGoal::None;
13952 selection.reversed = false;
13953 self.select_match_ranges(
13954 selection.start..selection.end,
13955 selection.reversed,
13956 replace_newest,
13957 autoscroll,
13958 window,
13959 cx,
13960 );
13961 }
13962
13963 if selections.len() == 1 {
13964 let selection = selections
13965 .last()
13966 .expect("ensured that there's only one selection");
13967 let query = buffer
13968 .text_for_range(selection.start..selection.end)
13969 .collect::<String>();
13970 let is_empty = query.is_empty();
13971 let select_state = SelectNextState {
13972 query: AhoCorasick::new(&[query])?,
13973 wordwise: true,
13974 done: is_empty,
13975 };
13976 self.select_next_state = Some(select_state);
13977 } else {
13978 self.select_next_state = None;
13979 }
13980 } else if let Some(selected_text) = selected_text {
13981 self.select_next_state = Some(SelectNextState {
13982 query: AhoCorasick::new(&[selected_text])?,
13983 wordwise: false,
13984 done: false,
13985 });
13986 self.select_next_match_internal(
13987 display_map,
13988 replace_newest,
13989 autoscroll,
13990 window,
13991 cx,
13992 )?;
13993 }
13994 }
13995 Ok(())
13996 }
13997
13998 pub fn select_all_matches(
13999 &mut self,
14000 _action: &SelectAllMatches,
14001 window: &mut Window,
14002 cx: &mut Context<Self>,
14003 ) -> Result<()> {
14004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14005
14006 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14007
14008 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14009 let Some(select_next_state) = self.select_next_state.as_mut() else {
14010 return Ok(());
14011 };
14012 if select_next_state.done {
14013 return Ok(());
14014 }
14015
14016 let mut new_selections = Vec::new();
14017
14018 let reversed = self.selections.oldest::<usize>(cx).reversed;
14019 let buffer = &display_map.buffer_snapshot;
14020 let query_matches = select_next_state
14021 .query
14022 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14023
14024 for query_match in query_matches.into_iter() {
14025 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14026 let offset_range = if reversed {
14027 query_match.end()..query_match.start()
14028 } else {
14029 query_match.start()..query_match.end()
14030 };
14031
14032 if !select_next_state.wordwise
14033 || (!buffer.is_inside_word(offset_range.start, false)
14034 && !buffer.is_inside_word(offset_range.end, false))
14035 {
14036 new_selections.push(offset_range.start..offset_range.end);
14037 }
14038 }
14039
14040 select_next_state.done = true;
14041
14042 if new_selections.is_empty() {
14043 log::error!("bug: new_selections is empty in select_all_matches");
14044 return Ok(());
14045 }
14046
14047 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14048 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14049 selections.select_ranges(new_selections)
14050 });
14051
14052 Ok(())
14053 }
14054
14055 pub fn select_next(
14056 &mut self,
14057 action: &SelectNext,
14058 window: &mut Window,
14059 cx: &mut Context<Self>,
14060 ) -> Result<()> {
14061 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14062 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14063 self.select_next_match_internal(
14064 &display_map,
14065 action.replace_newest,
14066 Some(Autoscroll::newest()),
14067 window,
14068 cx,
14069 )?;
14070 Ok(())
14071 }
14072
14073 pub fn select_previous(
14074 &mut self,
14075 action: &SelectPrevious,
14076 window: &mut Window,
14077 cx: &mut Context<Self>,
14078 ) -> Result<()> {
14079 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14080 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14081 let buffer = &display_map.buffer_snapshot;
14082 let mut selections = self.selections.all::<usize>(cx);
14083 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14084 let query = &select_prev_state.query;
14085 if !select_prev_state.done {
14086 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14087 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14088 let mut next_selected_range = None;
14089 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14090 let bytes_before_last_selection =
14091 buffer.reversed_bytes_in_range(0..last_selection.start);
14092 let bytes_after_first_selection =
14093 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14094 let query_matches = query
14095 .stream_find_iter(bytes_before_last_selection)
14096 .map(|result| (last_selection.start, result))
14097 .chain(
14098 query
14099 .stream_find_iter(bytes_after_first_selection)
14100 .map(|result| (buffer.len(), result)),
14101 );
14102 for (end_offset, query_match) in query_matches {
14103 let query_match = query_match.unwrap(); // can only fail due to I/O
14104 let offset_range =
14105 end_offset - query_match.end()..end_offset - query_match.start();
14106
14107 if !select_prev_state.wordwise
14108 || (!buffer.is_inside_word(offset_range.start, false)
14109 && !buffer.is_inside_word(offset_range.end, false))
14110 {
14111 next_selected_range = Some(offset_range);
14112 break;
14113 }
14114 }
14115
14116 if let Some(next_selected_range) = next_selected_range {
14117 self.select_match_ranges(
14118 next_selected_range,
14119 last_selection.reversed,
14120 action.replace_newest,
14121 Some(Autoscroll::newest()),
14122 window,
14123 cx,
14124 );
14125 } else {
14126 select_prev_state.done = true;
14127 }
14128 }
14129
14130 self.select_prev_state = Some(select_prev_state);
14131 } else {
14132 let mut only_carets = true;
14133 let mut same_text_selected = true;
14134 let mut selected_text = None;
14135
14136 let mut selections_iter = selections.iter().peekable();
14137 while let Some(selection) = selections_iter.next() {
14138 if selection.start != selection.end {
14139 only_carets = false;
14140 }
14141
14142 if same_text_selected {
14143 if selected_text.is_none() {
14144 selected_text =
14145 Some(buffer.text_for_range(selection.range()).collect::<String>());
14146 }
14147
14148 if let Some(next_selection) = selections_iter.peek() {
14149 if next_selection.range().len() == selection.range().len() {
14150 let next_selected_text = buffer
14151 .text_for_range(next_selection.range())
14152 .collect::<String>();
14153 if Some(next_selected_text) != selected_text {
14154 same_text_selected = false;
14155 selected_text = None;
14156 }
14157 } else {
14158 same_text_selected = false;
14159 selected_text = None;
14160 }
14161 }
14162 }
14163 }
14164
14165 if only_carets {
14166 for selection in &mut selections {
14167 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14168 selection.start = word_range.start;
14169 selection.end = word_range.end;
14170 selection.goal = SelectionGoal::None;
14171 selection.reversed = false;
14172 self.select_match_ranges(
14173 selection.start..selection.end,
14174 selection.reversed,
14175 action.replace_newest,
14176 Some(Autoscroll::newest()),
14177 window,
14178 cx,
14179 );
14180 }
14181 if selections.len() == 1 {
14182 let selection = selections
14183 .last()
14184 .expect("ensured that there's only one selection");
14185 let query = buffer
14186 .text_for_range(selection.start..selection.end)
14187 .collect::<String>();
14188 let is_empty = query.is_empty();
14189 let select_state = SelectNextState {
14190 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14191 wordwise: true,
14192 done: is_empty,
14193 };
14194 self.select_prev_state = Some(select_state);
14195 } else {
14196 self.select_prev_state = None;
14197 }
14198 } else if let Some(selected_text) = selected_text {
14199 self.select_prev_state = Some(SelectNextState {
14200 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14201 wordwise: false,
14202 done: false,
14203 });
14204 self.select_previous(action, window, cx)?;
14205 }
14206 }
14207 Ok(())
14208 }
14209
14210 pub fn find_next_match(
14211 &mut self,
14212 _: &FindNextMatch,
14213 window: &mut Window,
14214 cx: &mut Context<Self>,
14215 ) -> Result<()> {
14216 let selections = self.selections.disjoint_anchors();
14217 match selections.first() {
14218 Some(first) if selections.len() >= 2 => {
14219 self.change_selections(Default::default(), window, cx, |s| {
14220 s.select_ranges([first.range()]);
14221 });
14222 }
14223 _ => self.select_next(
14224 &SelectNext {
14225 replace_newest: true,
14226 },
14227 window,
14228 cx,
14229 )?,
14230 }
14231 Ok(())
14232 }
14233
14234 pub fn find_previous_match(
14235 &mut self,
14236 _: &FindPreviousMatch,
14237 window: &mut Window,
14238 cx: &mut Context<Self>,
14239 ) -> Result<()> {
14240 let selections = self.selections.disjoint_anchors();
14241 match selections.last() {
14242 Some(last) if selections.len() >= 2 => {
14243 self.change_selections(Default::default(), window, cx, |s| {
14244 s.select_ranges([last.range()]);
14245 });
14246 }
14247 _ => self.select_previous(
14248 &SelectPrevious {
14249 replace_newest: true,
14250 },
14251 window,
14252 cx,
14253 )?,
14254 }
14255 Ok(())
14256 }
14257
14258 pub fn toggle_comments(
14259 &mut self,
14260 action: &ToggleComments,
14261 window: &mut Window,
14262 cx: &mut Context<Self>,
14263 ) {
14264 if self.read_only(cx) {
14265 return;
14266 }
14267 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14268 let text_layout_details = &self.text_layout_details(window);
14269 self.transact(window, cx, |this, window, cx| {
14270 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14271 let mut edits = Vec::new();
14272 let mut selection_edit_ranges = Vec::new();
14273 let mut last_toggled_row = None;
14274 let snapshot = this.buffer.read(cx).read(cx);
14275 let empty_str: Arc<str> = Arc::default();
14276 let mut suffixes_inserted = Vec::new();
14277 let ignore_indent = action.ignore_indent;
14278
14279 fn comment_prefix_range(
14280 snapshot: &MultiBufferSnapshot,
14281 row: MultiBufferRow,
14282 comment_prefix: &str,
14283 comment_prefix_whitespace: &str,
14284 ignore_indent: bool,
14285 ) -> Range<Point> {
14286 let indent_size = if ignore_indent {
14287 0
14288 } else {
14289 snapshot.indent_size_for_line(row).len
14290 };
14291
14292 let start = Point::new(row.0, indent_size);
14293
14294 let mut line_bytes = snapshot
14295 .bytes_in_range(start..snapshot.max_point())
14296 .flatten()
14297 .copied();
14298
14299 // If this line currently begins with the line comment prefix, then record
14300 // the range containing the prefix.
14301 if line_bytes
14302 .by_ref()
14303 .take(comment_prefix.len())
14304 .eq(comment_prefix.bytes())
14305 {
14306 // Include any whitespace that matches the comment prefix.
14307 let matching_whitespace_len = line_bytes
14308 .zip(comment_prefix_whitespace.bytes())
14309 .take_while(|(a, b)| a == b)
14310 .count() as u32;
14311 let end = Point::new(
14312 start.row,
14313 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14314 );
14315 start..end
14316 } else {
14317 start..start
14318 }
14319 }
14320
14321 fn comment_suffix_range(
14322 snapshot: &MultiBufferSnapshot,
14323 row: MultiBufferRow,
14324 comment_suffix: &str,
14325 comment_suffix_has_leading_space: bool,
14326 ) -> Range<Point> {
14327 let end = Point::new(row.0, snapshot.line_len(row));
14328 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14329
14330 let mut line_end_bytes = snapshot
14331 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14332 .flatten()
14333 .copied();
14334
14335 let leading_space_len = if suffix_start_column > 0
14336 && line_end_bytes.next() == Some(b' ')
14337 && comment_suffix_has_leading_space
14338 {
14339 1
14340 } else {
14341 0
14342 };
14343
14344 // If this line currently begins with the line comment prefix, then record
14345 // the range containing the prefix.
14346 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14347 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14348 start..end
14349 } else {
14350 end..end
14351 }
14352 }
14353
14354 // TODO: Handle selections that cross excerpts
14355 for selection in &mut selections {
14356 let start_column = snapshot
14357 .indent_size_for_line(MultiBufferRow(selection.start.row))
14358 .len;
14359 let language = if let Some(language) =
14360 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14361 {
14362 language
14363 } else {
14364 continue;
14365 };
14366
14367 selection_edit_ranges.clear();
14368
14369 // If multiple selections contain a given row, avoid processing that
14370 // row more than once.
14371 let mut start_row = MultiBufferRow(selection.start.row);
14372 if last_toggled_row == Some(start_row) {
14373 start_row = start_row.next_row();
14374 }
14375 let end_row =
14376 if selection.end.row > selection.start.row && selection.end.column == 0 {
14377 MultiBufferRow(selection.end.row - 1)
14378 } else {
14379 MultiBufferRow(selection.end.row)
14380 };
14381 last_toggled_row = Some(end_row);
14382
14383 if start_row > end_row {
14384 continue;
14385 }
14386
14387 // If the language has line comments, toggle those.
14388 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14389
14390 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14391 if ignore_indent {
14392 full_comment_prefixes = full_comment_prefixes
14393 .into_iter()
14394 .map(|s| Arc::from(s.trim_end()))
14395 .collect();
14396 }
14397
14398 if !full_comment_prefixes.is_empty() {
14399 let first_prefix = full_comment_prefixes
14400 .first()
14401 .expect("prefixes is non-empty");
14402 let prefix_trimmed_lengths = full_comment_prefixes
14403 .iter()
14404 .map(|p| p.trim_end_matches(' ').len())
14405 .collect::<SmallVec<[usize; 4]>>();
14406
14407 let mut all_selection_lines_are_comments = true;
14408
14409 for row in start_row.0..=end_row.0 {
14410 let row = MultiBufferRow(row);
14411 if start_row < end_row && snapshot.is_line_blank(row) {
14412 continue;
14413 }
14414
14415 let prefix_range = full_comment_prefixes
14416 .iter()
14417 .zip(prefix_trimmed_lengths.iter().copied())
14418 .map(|(prefix, trimmed_prefix_len)| {
14419 comment_prefix_range(
14420 snapshot.deref(),
14421 row,
14422 &prefix[..trimmed_prefix_len],
14423 &prefix[trimmed_prefix_len..],
14424 ignore_indent,
14425 )
14426 })
14427 .max_by_key(|range| range.end.column - range.start.column)
14428 .expect("prefixes is non-empty");
14429
14430 if prefix_range.is_empty() {
14431 all_selection_lines_are_comments = false;
14432 }
14433
14434 selection_edit_ranges.push(prefix_range);
14435 }
14436
14437 if all_selection_lines_are_comments {
14438 edits.extend(
14439 selection_edit_ranges
14440 .iter()
14441 .cloned()
14442 .map(|range| (range, empty_str.clone())),
14443 );
14444 } else {
14445 let min_column = selection_edit_ranges
14446 .iter()
14447 .map(|range| range.start.column)
14448 .min()
14449 .unwrap_or(0);
14450 edits.extend(selection_edit_ranges.iter().map(|range| {
14451 let position = Point::new(range.start.row, min_column);
14452 (position..position, first_prefix.clone())
14453 }));
14454 }
14455 } else if let Some(BlockCommentConfig {
14456 start: full_comment_prefix,
14457 end: comment_suffix,
14458 ..
14459 }) = language.block_comment()
14460 {
14461 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14462 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14463 let prefix_range = comment_prefix_range(
14464 snapshot.deref(),
14465 start_row,
14466 comment_prefix,
14467 comment_prefix_whitespace,
14468 ignore_indent,
14469 );
14470 let suffix_range = comment_suffix_range(
14471 snapshot.deref(),
14472 end_row,
14473 comment_suffix.trim_start_matches(' '),
14474 comment_suffix.starts_with(' '),
14475 );
14476
14477 if prefix_range.is_empty() || suffix_range.is_empty() {
14478 edits.push((
14479 prefix_range.start..prefix_range.start,
14480 full_comment_prefix.clone(),
14481 ));
14482 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14483 suffixes_inserted.push((end_row, comment_suffix.len()));
14484 } else {
14485 edits.push((prefix_range, empty_str.clone()));
14486 edits.push((suffix_range, empty_str.clone()));
14487 }
14488 } else {
14489 continue;
14490 }
14491 }
14492
14493 drop(snapshot);
14494 this.buffer.update(cx, |buffer, cx| {
14495 buffer.edit(edits, None, cx);
14496 });
14497
14498 // Adjust selections so that they end before any comment suffixes that
14499 // were inserted.
14500 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14501 let mut selections = this.selections.all::<Point>(cx);
14502 let snapshot = this.buffer.read(cx).read(cx);
14503 for selection in &mut selections {
14504 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14505 match row.cmp(&MultiBufferRow(selection.end.row)) {
14506 Ordering::Less => {
14507 suffixes_inserted.next();
14508 continue;
14509 }
14510 Ordering::Greater => break,
14511 Ordering::Equal => {
14512 if selection.end.column == snapshot.line_len(row) {
14513 if selection.is_empty() {
14514 selection.start.column -= suffix_len as u32;
14515 }
14516 selection.end.column -= suffix_len as u32;
14517 }
14518 break;
14519 }
14520 }
14521 }
14522 }
14523
14524 drop(snapshot);
14525 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14526
14527 let selections = this.selections.all::<Point>(cx);
14528 let selections_on_single_row = selections.windows(2).all(|selections| {
14529 selections[0].start.row == selections[1].start.row
14530 && selections[0].end.row == selections[1].end.row
14531 && selections[0].start.row == selections[0].end.row
14532 });
14533 let selections_selecting = selections
14534 .iter()
14535 .any(|selection| selection.start != selection.end);
14536 let advance_downwards = action.advance_downwards
14537 && selections_on_single_row
14538 && !selections_selecting
14539 && !matches!(this.mode, EditorMode::SingleLine { .. });
14540
14541 if advance_downwards {
14542 let snapshot = this.buffer.read(cx).snapshot(cx);
14543
14544 this.change_selections(Default::default(), window, cx, |s| {
14545 s.move_cursors_with(|display_snapshot, display_point, _| {
14546 let mut point = display_point.to_point(display_snapshot);
14547 point.row += 1;
14548 point = snapshot.clip_point(point, Bias::Left);
14549 let display_point = point.to_display_point(display_snapshot);
14550 let goal = SelectionGoal::HorizontalPosition(
14551 display_snapshot
14552 .x_for_display_point(display_point, text_layout_details)
14553 .into(),
14554 );
14555 (display_point, goal)
14556 })
14557 });
14558 }
14559 });
14560 }
14561
14562 pub fn select_enclosing_symbol(
14563 &mut self,
14564 _: &SelectEnclosingSymbol,
14565 window: &mut Window,
14566 cx: &mut Context<Self>,
14567 ) {
14568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14569
14570 let buffer = self.buffer.read(cx).snapshot(cx);
14571 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14572
14573 fn update_selection(
14574 selection: &Selection<usize>,
14575 buffer_snap: &MultiBufferSnapshot,
14576 ) -> Option<Selection<usize>> {
14577 let cursor = selection.head();
14578 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14579 for symbol in symbols.iter().rev() {
14580 let start = symbol.range.start.to_offset(buffer_snap);
14581 let end = symbol.range.end.to_offset(buffer_snap);
14582 let new_range = start..end;
14583 if start < selection.start || end > selection.end {
14584 return Some(Selection {
14585 id: selection.id,
14586 start: new_range.start,
14587 end: new_range.end,
14588 goal: SelectionGoal::None,
14589 reversed: selection.reversed,
14590 });
14591 }
14592 }
14593 None
14594 }
14595
14596 let mut selected_larger_symbol = false;
14597 let new_selections = old_selections
14598 .iter()
14599 .map(|selection| match update_selection(selection, &buffer) {
14600 Some(new_selection) => {
14601 if new_selection.range() != selection.range() {
14602 selected_larger_symbol = true;
14603 }
14604 new_selection
14605 }
14606 None => selection.clone(),
14607 })
14608 .collect::<Vec<_>>();
14609
14610 if selected_larger_symbol {
14611 self.change_selections(Default::default(), window, cx, |s| {
14612 s.select(new_selections);
14613 });
14614 }
14615 }
14616
14617 pub fn select_larger_syntax_node(
14618 &mut self,
14619 _: &SelectLargerSyntaxNode,
14620 window: &mut Window,
14621 cx: &mut Context<Self>,
14622 ) {
14623 let Some(visible_row_count) = self.visible_row_count() else {
14624 return;
14625 };
14626 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14627 if old_selections.is_empty() {
14628 return;
14629 }
14630
14631 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14632
14633 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14634 let buffer = self.buffer.read(cx).snapshot(cx);
14635
14636 let mut selected_larger_node = false;
14637 let mut new_selections = old_selections
14638 .iter()
14639 .map(|selection| {
14640 let old_range = selection.start..selection.end;
14641
14642 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14643 // manually select word at selection
14644 if ["string_content", "inline"].contains(&node.kind()) {
14645 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14646 // ignore if word is already selected
14647 if !word_range.is_empty() && old_range != word_range {
14648 let (last_word_range, _) =
14649 buffer.surrounding_word(old_range.end, false);
14650 // only select word if start and end point belongs to same word
14651 if word_range == last_word_range {
14652 selected_larger_node = true;
14653 return Selection {
14654 id: selection.id,
14655 start: word_range.start,
14656 end: word_range.end,
14657 goal: SelectionGoal::None,
14658 reversed: selection.reversed,
14659 };
14660 }
14661 }
14662 }
14663 }
14664
14665 let mut new_range = old_range.clone();
14666 while let Some((_node, containing_range)) =
14667 buffer.syntax_ancestor(new_range.clone())
14668 {
14669 new_range = match containing_range {
14670 MultiOrSingleBufferOffsetRange::Single(_) => break,
14671 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14672 };
14673 if !display_map.intersects_fold(new_range.start)
14674 && !display_map.intersects_fold(new_range.end)
14675 {
14676 break;
14677 }
14678 }
14679
14680 selected_larger_node |= new_range != old_range;
14681 Selection {
14682 id: selection.id,
14683 start: new_range.start,
14684 end: new_range.end,
14685 goal: SelectionGoal::None,
14686 reversed: selection.reversed,
14687 }
14688 })
14689 .collect::<Vec<_>>();
14690
14691 if !selected_larger_node {
14692 return; // don't put this call in the history
14693 }
14694
14695 // scroll based on transformation done to the last selection created by the user
14696 let (last_old, last_new) = old_selections
14697 .last()
14698 .zip(new_selections.last().cloned())
14699 .expect("old_selections isn't empty");
14700
14701 // revert selection
14702 let is_selection_reversed = {
14703 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14704 new_selections.last_mut().expect("checked above").reversed =
14705 should_newest_selection_be_reversed;
14706 should_newest_selection_be_reversed
14707 };
14708
14709 if selected_larger_node {
14710 self.select_syntax_node_history.disable_clearing = true;
14711 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14712 s.select(new_selections.clone());
14713 });
14714 self.select_syntax_node_history.disable_clearing = false;
14715 }
14716
14717 let start_row = last_new.start.to_display_point(&display_map).row().0;
14718 let end_row = last_new.end.to_display_point(&display_map).row().0;
14719 let selection_height = end_row - start_row + 1;
14720 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14721
14722 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14723 let scroll_behavior = if fits_on_the_screen {
14724 self.request_autoscroll(Autoscroll::fit(), cx);
14725 SelectSyntaxNodeScrollBehavior::FitSelection
14726 } else if is_selection_reversed {
14727 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14728 SelectSyntaxNodeScrollBehavior::CursorTop
14729 } else {
14730 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14731 SelectSyntaxNodeScrollBehavior::CursorBottom
14732 };
14733
14734 self.select_syntax_node_history.push((
14735 old_selections,
14736 scroll_behavior,
14737 is_selection_reversed,
14738 ));
14739 }
14740
14741 pub fn select_smaller_syntax_node(
14742 &mut self,
14743 _: &SelectSmallerSyntaxNode,
14744 window: &mut Window,
14745 cx: &mut Context<Self>,
14746 ) {
14747 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14748
14749 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14750 self.select_syntax_node_history.pop()
14751 {
14752 if let Some(selection) = selections.last_mut() {
14753 selection.reversed = is_selection_reversed;
14754 }
14755
14756 self.select_syntax_node_history.disable_clearing = true;
14757 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14758 s.select(selections.to_vec());
14759 });
14760 self.select_syntax_node_history.disable_clearing = false;
14761
14762 match scroll_behavior {
14763 SelectSyntaxNodeScrollBehavior::CursorTop => {
14764 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14765 }
14766 SelectSyntaxNodeScrollBehavior::FitSelection => {
14767 self.request_autoscroll(Autoscroll::fit(), cx);
14768 }
14769 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14770 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14771 }
14772 }
14773 }
14774 }
14775
14776 pub fn unwrap_syntax_node(
14777 &mut self,
14778 _: &UnwrapSyntaxNode,
14779 window: &mut Window,
14780 cx: &mut Context<Self>,
14781 ) {
14782 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14783
14784 let buffer = self.buffer.read(cx).snapshot(cx);
14785 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14786
14787 let edits = old_selections
14788 .iter()
14789 // only consider the first selection for now
14790 .take(1)
14791 .map(|selection| {
14792 // Only requires two branches once if-let-chains stabilize (#53667)
14793 let selection_range = if !selection.is_empty() {
14794 selection.range()
14795 } else if let Some((_, ancestor_range)) =
14796 buffer.syntax_ancestor(selection.start..selection.end)
14797 {
14798 match ancestor_range {
14799 MultiOrSingleBufferOffsetRange::Single(range) => range,
14800 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14801 }
14802 } else {
14803 selection.range()
14804 };
14805
14806 let mut new_range = selection_range.clone();
14807 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(new_range.clone()) {
14808 new_range = match ancestor_range {
14809 MultiOrSingleBufferOffsetRange::Single(range) => range,
14810 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14811 };
14812 if new_range.start < selection_range.start
14813 || new_range.end > selection_range.end
14814 {
14815 break;
14816 }
14817 }
14818
14819 (selection, selection_range, new_range)
14820 })
14821 .collect::<Vec<_>>();
14822
14823 self.transact(window, cx, |editor, window, cx| {
14824 for (_, child, parent) in &edits {
14825 let text = buffer.text_for_range(child.clone()).collect::<String>();
14826 editor.replace_text_in_range(Some(parent.clone()), &text, window, cx);
14827 }
14828
14829 editor.change_selections(
14830 SelectionEffects::scroll(Autoscroll::fit()),
14831 window,
14832 cx,
14833 |s| {
14834 s.select(
14835 edits
14836 .iter()
14837 .map(|(s, old, new)| Selection {
14838 id: s.id,
14839 start: new.start,
14840 end: new.start + old.len(),
14841 goal: SelectionGoal::None,
14842 reversed: s.reversed,
14843 })
14844 .collect(),
14845 );
14846 },
14847 );
14848 });
14849 }
14850
14851 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14852 if !EditorSettings::get_global(cx).gutter.runnables {
14853 self.clear_tasks();
14854 return Task::ready(());
14855 }
14856 let project = self.project.as_ref().map(Entity::downgrade);
14857 let task_sources = self.lsp_task_sources(cx);
14858 let multi_buffer = self.buffer.downgrade();
14859 cx.spawn_in(window, async move |editor, cx| {
14860 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14861 let Some(project) = project.and_then(|p| p.upgrade()) else {
14862 return;
14863 };
14864 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14865 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14866 }) else {
14867 return;
14868 };
14869
14870 let hide_runnables = project
14871 .update(cx, |project, cx| {
14872 // Do not display any test indicators in non-dev server remote projects.
14873 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14874 })
14875 .unwrap_or(true);
14876 if hide_runnables {
14877 return;
14878 }
14879 let new_rows =
14880 cx.background_spawn({
14881 let snapshot = display_snapshot.clone();
14882 async move {
14883 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14884 }
14885 })
14886 .await;
14887 let Ok(lsp_tasks) =
14888 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14889 else {
14890 return;
14891 };
14892 let lsp_tasks = lsp_tasks.await;
14893
14894 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14895 lsp_tasks
14896 .into_iter()
14897 .flat_map(|(kind, tasks)| {
14898 tasks.into_iter().filter_map(move |(location, task)| {
14899 Some((kind.clone(), location?, task))
14900 })
14901 })
14902 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14903 let buffer = location.target.buffer;
14904 let buffer_snapshot = buffer.read(cx).snapshot();
14905 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14906 |(excerpt_id, snapshot, _)| {
14907 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14908 display_snapshot
14909 .buffer_snapshot
14910 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14911 } else {
14912 None
14913 }
14914 },
14915 );
14916 if let Some(offset) = offset {
14917 let task_buffer_range =
14918 location.target.range.to_point(&buffer_snapshot);
14919 let context_buffer_range =
14920 task_buffer_range.to_offset(&buffer_snapshot);
14921 let context_range = BufferOffset(context_buffer_range.start)
14922 ..BufferOffset(context_buffer_range.end);
14923
14924 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14925 .or_insert_with(|| RunnableTasks {
14926 templates: Vec::new(),
14927 offset,
14928 column: task_buffer_range.start.column,
14929 extra_variables: HashMap::default(),
14930 context_range,
14931 })
14932 .templates
14933 .push((kind, task.original_task().clone()));
14934 }
14935
14936 acc
14937 })
14938 }) else {
14939 return;
14940 };
14941
14942 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14943 buffer.language_settings(cx).tasks.prefer_lsp
14944 }) else {
14945 return;
14946 };
14947
14948 let rows = Self::runnable_rows(
14949 project,
14950 display_snapshot,
14951 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14952 new_rows,
14953 cx.clone(),
14954 )
14955 .await;
14956 editor
14957 .update(cx, |editor, _| {
14958 editor.clear_tasks();
14959 for (key, mut value) in rows {
14960 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14961 value.templates.extend(lsp_tasks.templates);
14962 }
14963
14964 editor.insert_tasks(key, value);
14965 }
14966 for (key, value) in lsp_tasks_by_rows {
14967 editor.insert_tasks(key, value);
14968 }
14969 })
14970 .ok();
14971 })
14972 }
14973 fn fetch_runnable_ranges(
14974 snapshot: &DisplaySnapshot,
14975 range: Range<Anchor>,
14976 ) -> Vec<language::RunnableRange> {
14977 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14978 }
14979
14980 fn runnable_rows(
14981 project: Entity<Project>,
14982 snapshot: DisplaySnapshot,
14983 prefer_lsp: bool,
14984 runnable_ranges: Vec<RunnableRange>,
14985 cx: AsyncWindowContext,
14986 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14987 cx.spawn(async move |cx| {
14988 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14989 for mut runnable in runnable_ranges {
14990 let Some(tasks) = cx
14991 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14992 .ok()
14993 else {
14994 continue;
14995 };
14996 let mut tasks = tasks.await;
14997
14998 if prefer_lsp {
14999 tasks.retain(|(task_kind, _)| {
15000 !matches!(task_kind, TaskSourceKind::Language { .. })
15001 });
15002 }
15003 if tasks.is_empty() {
15004 continue;
15005 }
15006
15007 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15008 let Some(row) = snapshot
15009 .buffer_snapshot
15010 .buffer_line_for_row(MultiBufferRow(point.row))
15011 .map(|(_, range)| range.start.row)
15012 else {
15013 continue;
15014 };
15015
15016 let context_range =
15017 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15018 runnable_rows.push((
15019 (runnable.buffer_id, row),
15020 RunnableTasks {
15021 templates: tasks,
15022 offset: snapshot
15023 .buffer_snapshot
15024 .anchor_before(runnable.run_range.start),
15025 context_range,
15026 column: point.column,
15027 extra_variables: runnable.extra_captures,
15028 },
15029 ));
15030 }
15031 runnable_rows
15032 })
15033 }
15034
15035 fn templates_with_tags(
15036 project: &Entity<Project>,
15037 runnable: &mut Runnable,
15038 cx: &mut App,
15039 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15040 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15041 let (worktree_id, file) = project
15042 .buffer_for_id(runnable.buffer, cx)
15043 .and_then(|buffer| buffer.read(cx).file())
15044 .map(|file| (file.worktree_id(cx), file.clone()))
15045 .unzip();
15046
15047 (
15048 project.task_store().read(cx).task_inventory().cloned(),
15049 worktree_id,
15050 file,
15051 )
15052 });
15053
15054 let tags = mem::take(&mut runnable.tags);
15055 let language = runnable.language.clone();
15056 cx.spawn(async move |cx| {
15057 let mut templates_with_tags = Vec::new();
15058 if let Some(inventory) = inventory {
15059 for RunnableTag(tag) in tags {
15060 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15061 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15062 }) else {
15063 return templates_with_tags;
15064 };
15065 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15066 move |(_, template)| {
15067 template.tags.iter().any(|source_tag| source_tag == &tag)
15068 },
15069 ));
15070 }
15071 }
15072 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15073
15074 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15075 // Strongest source wins; if we have worktree tag binding, prefer that to
15076 // global and language bindings;
15077 // if we have a global binding, prefer that to language binding.
15078 let first_mismatch = templates_with_tags
15079 .iter()
15080 .position(|(tag_source, _)| tag_source != leading_tag_source);
15081 if let Some(index) = first_mismatch {
15082 templates_with_tags.truncate(index);
15083 }
15084 }
15085
15086 templates_with_tags
15087 })
15088 }
15089
15090 pub fn move_to_enclosing_bracket(
15091 &mut self,
15092 _: &MoveToEnclosingBracket,
15093 window: &mut Window,
15094 cx: &mut Context<Self>,
15095 ) {
15096 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15097 self.change_selections(Default::default(), window, cx, |s| {
15098 s.move_offsets_with(|snapshot, selection| {
15099 let Some(enclosing_bracket_ranges) =
15100 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15101 else {
15102 return;
15103 };
15104
15105 let mut best_length = usize::MAX;
15106 let mut best_inside = false;
15107 let mut best_in_bracket_range = false;
15108 let mut best_destination = None;
15109 for (open, close) in enclosing_bracket_ranges {
15110 let close = close.to_inclusive();
15111 let length = close.end() - open.start;
15112 let inside = selection.start >= open.end && selection.end <= *close.start();
15113 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15114 || close.contains(&selection.head());
15115
15116 // If best is next to a bracket and current isn't, skip
15117 if !in_bracket_range && best_in_bracket_range {
15118 continue;
15119 }
15120
15121 // Prefer smaller lengths unless best is inside and current isn't
15122 if length > best_length && (best_inside || !inside) {
15123 continue;
15124 }
15125
15126 best_length = length;
15127 best_inside = inside;
15128 best_in_bracket_range = in_bracket_range;
15129 best_destination = Some(
15130 if close.contains(&selection.start) && close.contains(&selection.end) {
15131 if inside { open.end } else { open.start }
15132 } else if inside {
15133 *close.start()
15134 } else {
15135 *close.end()
15136 },
15137 );
15138 }
15139
15140 if let Some(destination) = best_destination {
15141 selection.collapse_to(destination, SelectionGoal::None);
15142 }
15143 })
15144 });
15145 }
15146
15147 pub fn undo_selection(
15148 &mut self,
15149 _: &UndoSelection,
15150 window: &mut Window,
15151 cx: &mut Context<Self>,
15152 ) {
15153 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15154 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15155 self.selection_history.mode = SelectionHistoryMode::Undoing;
15156 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15157 this.end_selection(window, cx);
15158 this.change_selections(
15159 SelectionEffects::scroll(Autoscroll::newest()),
15160 window,
15161 cx,
15162 |s| s.select_anchors(entry.selections.to_vec()),
15163 );
15164 });
15165 self.selection_history.mode = SelectionHistoryMode::Normal;
15166
15167 self.select_next_state = entry.select_next_state;
15168 self.select_prev_state = entry.select_prev_state;
15169 self.add_selections_state = entry.add_selections_state;
15170 }
15171 }
15172
15173 pub fn redo_selection(
15174 &mut self,
15175 _: &RedoSelection,
15176 window: &mut Window,
15177 cx: &mut Context<Self>,
15178 ) {
15179 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15180 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15181 self.selection_history.mode = SelectionHistoryMode::Redoing;
15182 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15183 this.end_selection(window, cx);
15184 this.change_selections(
15185 SelectionEffects::scroll(Autoscroll::newest()),
15186 window,
15187 cx,
15188 |s| s.select_anchors(entry.selections.to_vec()),
15189 );
15190 });
15191 self.selection_history.mode = SelectionHistoryMode::Normal;
15192
15193 self.select_next_state = entry.select_next_state;
15194 self.select_prev_state = entry.select_prev_state;
15195 self.add_selections_state = entry.add_selections_state;
15196 }
15197 }
15198
15199 pub fn expand_excerpts(
15200 &mut self,
15201 action: &ExpandExcerpts,
15202 _: &mut Window,
15203 cx: &mut Context<Self>,
15204 ) {
15205 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15206 }
15207
15208 pub fn expand_excerpts_down(
15209 &mut self,
15210 action: &ExpandExcerptsDown,
15211 _: &mut Window,
15212 cx: &mut Context<Self>,
15213 ) {
15214 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15215 }
15216
15217 pub fn expand_excerpts_up(
15218 &mut self,
15219 action: &ExpandExcerptsUp,
15220 _: &mut Window,
15221 cx: &mut Context<Self>,
15222 ) {
15223 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15224 }
15225
15226 pub fn expand_excerpts_for_direction(
15227 &mut self,
15228 lines: u32,
15229 direction: ExpandExcerptDirection,
15230
15231 cx: &mut Context<Self>,
15232 ) {
15233 let selections = self.selections.disjoint_anchors();
15234
15235 let lines = if lines == 0 {
15236 EditorSettings::get_global(cx).expand_excerpt_lines
15237 } else {
15238 lines
15239 };
15240
15241 self.buffer.update(cx, |buffer, cx| {
15242 let snapshot = buffer.snapshot(cx);
15243 let mut excerpt_ids = selections
15244 .iter()
15245 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15246 .collect::<Vec<_>>();
15247 excerpt_ids.sort();
15248 excerpt_ids.dedup();
15249 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15250 })
15251 }
15252
15253 pub fn expand_excerpt(
15254 &mut self,
15255 excerpt: ExcerptId,
15256 direction: ExpandExcerptDirection,
15257 window: &mut Window,
15258 cx: &mut Context<Self>,
15259 ) {
15260 let current_scroll_position = self.scroll_position(cx);
15261 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15262 let mut should_scroll_up = false;
15263
15264 if direction == ExpandExcerptDirection::Down {
15265 let multi_buffer = self.buffer.read(cx);
15266 let snapshot = multi_buffer.snapshot(cx);
15267 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15268 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15269 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15270 let buffer_snapshot = buffer.read(cx).snapshot();
15271 let excerpt_end_row =
15272 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15273 let last_row = buffer_snapshot.max_point().row;
15274 let lines_below = last_row.saturating_sub(excerpt_end_row);
15275 should_scroll_up = lines_below >= lines_to_expand;
15276 }
15277 }
15278 }
15279 }
15280
15281 self.buffer.update(cx, |buffer, cx| {
15282 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15283 });
15284
15285 if should_scroll_up {
15286 let new_scroll_position =
15287 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15288 self.set_scroll_position(new_scroll_position, window, cx);
15289 }
15290 }
15291
15292 pub fn go_to_singleton_buffer_point(
15293 &mut self,
15294 point: Point,
15295 window: &mut Window,
15296 cx: &mut Context<Self>,
15297 ) {
15298 self.go_to_singleton_buffer_range(point..point, window, cx);
15299 }
15300
15301 pub fn go_to_singleton_buffer_range(
15302 &mut self,
15303 range: Range<Point>,
15304 window: &mut Window,
15305 cx: &mut Context<Self>,
15306 ) {
15307 let multibuffer = self.buffer().read(cx);
15308 let Some(buffer) = multibuffer.as_singleton() else {
15309 return;
15310 };
15311 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15312 return;
15313 };
15314 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15315 return;
15316 };
15317 self.change_selections(
15318 SelectionEffects::default().nav_history(true),
15319 window,
15320 cx,
15321 |s| s.select_anchor_ranges([start..end]),
15322 );
15323 }
15324
15325 pub fn go_to_diagnostic(
15326 &mut self,
15327 action: &GoToDiagnostic,
15328 window: &mut Window,
15329 cx: &mut Context<Self>,
15330 ) {
15331 if !self.diagnostics_enabled() {
15332 return;
15333 }
15334 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15335 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15336 }
15337
15338 pub fn go_to_prev_diagnostic(
15339 &mut self,
15340 action: &GoToPreviousDiagnostic,
15341 window: &mut Window,
15342 cx: &mut Context<Self>,
15343 ) {
15344 if !self.diagnostics_enabled() {
15345 return;
15346 }
15347 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15348 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15349 }
15350
15351 pub fn go_to_diagnostic_impl(
15352 &mut self,
15353 direction: Direction,
15354 severity: GoToDiagnosticSeverityFilter,
15355 window: &mut Window,
15356 cx: &mut Context<Self>,
15357 ) {
15358 let buffer = self.buffer.read(cx).snapshot(cx);
15359 let selection = self.selections.newest::<usize>(cx);
15360
15361 let mut active_group_id = None;
15362 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15363 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15364 active_group_id = Some(active_group.group_id);
15365 }
15366 }
15367
15368 fn filtered(
15369 snapshot: EditorSnapshot,
15370 severity: GoToDiagnosticSeverityFilter,
15371 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15372 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15373 diagnostics
15374 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15375 .filter(|entry| entry.range.start != entry.range.end)
15376 .filter(|entry| !entry.diagnostic.is_unnecessary)
15377 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15378 }
15379
15380 let snapshot = self.snapshot(window, cx);
15381 let before = filtered(
15382 snapshot.clone(),
15383 severity,
15384 buffer
15385 .diagnostics_in_range(0..selection.start)
15386 .filter(|entry| entry.range.start <= selection.start),
15387 );
15388 let after = filtered(
15389 snapshot,
15390 severity,
15391 buffer
15392 .diagnostics_in_range(selection.start..buffer.len())
15393 .filter(|entry| entry.range.start >= selection.start),
15394 );
15395
15396 let mut found: Option<DiagnosticEntry<usize>> = None;
15397 if direction == Direction::Prev {
15398 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15399 {
15400 for diagnostic in prev_diagnostics.into_iter().rev() {
15401 if diagnostic.range.start != selection.start
15402 || active_group_id
15403 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15404 {
15405 found = Some(diagnostic);
15406 break 'outer;
15407 }
15408 }
15409 }
15410 } else {
15411 for diagnostic in after.chain(before) {
15412 if diagnostic.range.start != selection.start
15413 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15414 {
15415 found = Some(diagnostic);
15416 break;
15417 }
15418 }
15419 }
15420 let Some(next_diagnostic) = found else {
15421 return;
15422 };
15423
15424 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15425 return;
15426 };
15427 self.change_selections(Default::default(), window, cx, |s| {
15428 s.select_ranges(vec![
15429 next_diagnostic.range.start..next_diagnostic.range.start,
15430 ])
15431 });
15432 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15433 self.refresh_edit_prediction(false, true, window, cx);
15434 }
15435
15436 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15437 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15438 let snapshot = self.snapshot(window, cx);
15439 let selection = self.selections.newest::<Point>(cx);
15440 self.go_to_hunk_before_or_after_position(
15441 &snapshot,
15442 selection.head(),
15443 Direction::Next,
15444 window,
15445 cx,
15446 );
15447 }
15448
15449 pub fn go_to_hunk_before_or_after_position(
15450 &mut self,
15451 snapshot: &EditorSnapshot,
15452 position: Point,
15453 direction: Direction,
15454 window: &mut Window,
15455 cx: &mut Context<Editor>,
15456 ) {
15457 let row = if direction == Direction::Next {
15458 self.hunk_after_position(snapshot, position)
15459 .map(|hunk| hunk.row_range.start)
15460 } else {
15461 self.hunk_before_position(snapshot, position)
15462 };
15463
15464 if let Some(row) = row {
15465 let destination = Point::new(row.0, 0);
15466 let autoscroll = Autoscroll::center();
15467
15468 self.unfold_ranges(&[destination..destination], false, false, cx);
15469 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15470 s.select_ranges([destination..destination]);
15471 });
15472 }
15473 }
15474
15475 fn hunk_after_position(
15476 &mut self,
15477 snapshot: &EditorSnapshot,
15478 position: Point,
15479 ) -> Option<MultiBufferDiffHunk> {
15480 snapshot
15481 .buffer_snapshot
15482 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15483 .find(|hunk| hunk.row_range.start.0 > position.row)
15484 .or_else(|| {
15485 snapshot
15486 .buffer_snapshot
15487 .diff_hunks_in_range(Point::zero()..position)
15488 .find(|hunk| hunk.row_range.end.0 < position.row)
15489 })
15490 }
15491
15492 fn go_to_prev_hunk(
15493 &mut self,
15494 _: &GoToPreviousHunk,
15495 window: &mut Window,
15496 cx: &mut Context<Self>,
15497 ) {
15498 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15499 let snapshot = self.snapshot(window, cx);
15500 let selection = self.selections.newest::<Point>(cx);
15501 self.go_to_hunk_before_or_after_position(
15502 &snapshot,
15503 selection.head(),
15504 Direction::Prev,
15505 window,
15506 cx,
15507 );
15508 }
15509
15510 fn hunk_before_position(
15511 &mut self,
15512 snapshot: &EditorSnapshot,
15513 position: Point,
15514 ) -> Option<MultiBufferRow> {
15515 snapshot
15516 .buffer_snapshot
15517 .diff_hunk_before(position)
15518 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15519 }
15520
15521 fn go_to_next_change(
15522 &mut self,
15523 _: &GoToNextChange,
15524 window: &mut Window,
15525 cx: &mut Context<Self>,
15526 ) {
15527 if let Some(selections) = self
15528 .change_list
15529 .next_change(1, Direction::Next)
15530 .map(|s| s.to_vec())
15531 {
15532 self.change_selections(Default::default(), window, cx, |s| {
15533 let map = s.display_map();
15534 s.select_display_ranges(selections.iter().map(|a| {
15535 let point = a.to_display_point(&map);
15536 point..point
15537 }))
15538 })
15539 }
15540 }
15541
15542 fn go_to_previous_change(
15543 &mut self,
15544 _: &GoToPreviousChange,
15545 window: &mut Window,
15546 cx: &mut Context<Self>,
15547 ) {
15548 if let Some(selections) = self
15549 .change_list
15550 .next_change(1, Direction::Prev)
15551 .map(|s| s.to_vec())
15552 {
15553 self.change_selections(Default::default(), window, cx, |s| {
15554 let map = s.display_map();
15555 s.select_display_ranges(selections.iter().map(|a| {
15556 let point = a.to_display_point(&map);
15557 point..point
15558 }))
15559 })
15560 }
15561 }
15562
15563 fn go_to_line<T: 'static>(
15564 &mut self,
15565 position: Anchor,
15566 highlight_color: Option<Hsla>,
15567 window: &mut Window,
15568 cx: &mut Context<Self>,
15569 ) {
15570 let snapshot = self.snapshot(window, cx).display_snapshot;
15571 let position = position.to_point(&snapshot.buffer_snapshot);
15572 let start = snapshot
15573 .buffer_snapshot
15574 .clip_point(Point::new(position.row, 0), Bias::Left);
15575 let end = start + Point::new(1, 0);
15576 let start = snapshot.buffer_snapshot.anchor_before(start);
15577 let end = snapshot.buffer_snapshot.anchor_before(end);
15578
15579 self.highlight_rows::<T>(
15580 start..end,
15581 highlight_color
15582 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15583 Default::default(),
15584 cx,
15585 );
15586
15587 if self.buffer.read(cx).is_singleton() {
15588 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15589 }
15590 }
15591
15592 pub fn go_to_definition(
15593 &mut self,
15594 _: &GoToDefinition,
15595 window: &mut Window,
15596 cx: &mut Context<Self>,
15597 ) -> Task<Result<Navigated>> {
15598 let definition =
15599 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15600 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15601 cx.spawn_in(window, async move |editor, cx| {
15602 if definition.await? == Navigated::Yes {
15603 return Ok(Navigated::Yes);
15604 }
15605 match fallback_strategy {
15606 GoToDefinitionFallback::None => Ok(Navigated::No),
15607 GoToDefinitionFallback::FindAllReferences => {
15608 match editor.update_in(cx, |editor, window, cx| {
15609 editor.find_all_references(&FindAllReferences, window, cx)
15610 })? {
15611 Some(references) => references.await,
15612 None => Ok(Navigated::No),
15613 }
15614 }
15615 }
15616 })
15617 }
15618
15619 pub fn go_to_declaration(
15620 &mut self,
15621 _: &GoToDeclaration,
15622 window: &mut Window,
15623 cx: &mut Context<Self>,
15624 ) -> Task<Result<Navigated>> {
15625 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15626 }
15627
15628 pub fn go_to_declaration_split(
15629 &mut self,
15630 _: &GoToDeclaration,
15631 window: &mut Window,
15632 cx: &mut Context<Self>,
15633 ) -> Task<Result<Navigated>> {
15634 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15635 }
15636
15637 pub fn go_to_implementation(
15638 &mut self,
15639 _: &GoToImplementation,
15640 window: &mut Window,
15641 cx: &mut Context<Self>,
15642 ) -> Task<Result<Navigated>> {
15643 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15644 }
15645
15646 pub fn go_to_implementation_split(
15647 &mut self,
15648 _: &GoToImplementationSplit,
15649 window: &mut Window,
15650 cx: &mut Context<Self>,
15651 ) -> Task<Result<Navigated>> {
15652 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15653 }
15654
15655 pub fn go_to_type_definition(
15656 &mut self,
15657 _: &GoToTypeDefinition,
15658 window: &mut Window,
15659 cx: &mut Context<Self>,
15660 ) -> Task<Result<Navigated>> {
15661 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15662 }
15663
15664 pub fn go_to_definition_split(
15665 &mut self,
15666 _: &GoToDefinitionSplit,
15667 window: &mut Window,
15668 cx: &mut Context<Self>,
15669 ) -> Task<Result<Navigated>> {
15670 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15671 }
15672
15673 pub fn go_to_type_definition_split(
15674 &mut self,
15675 _: &GoToTypeDefinitionSplit,
15676 window: &mut Window,
15677 cx: &mut Context<Self>,
15678 ) -> Task<Result<Navigated>> {
15679 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15680 }
15681
15682 fn go_to_definition_of_kind(
15683 &mut self,
15684 kind: GotoDefinitionKind,
15685 split: bool,
15686 window: &mut Window,
15687 cx: &mut Context<Self>,
15688 ) -> Task<Result<Navigated>> {
15689 let Some(provider) = self.semantics_provider.clone() else {
15690 return Task::ready(Ok(Navigated::No));
15691 };
15692 let head = self.selections.newest::<usize>(cx).head();
15693 let buffer = self.buffer.read(cx);
15694 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15695 return Task::ready(Ok(Navigated::No));
15696 };
15697 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15698 return Task::ready(Ok(Navigated::No));
15699 };
15700
15701 cx.spawn_in(window, async move |editor, cx| {
15702 let definitions = definitions.await?;
15703 let navigated = editor
15704 .update_in(cx, |editor, window, cx| {
15705 editor.navigate_to_hover_links(
15706 Some(kind),
15707 definitions
15708 .into_iter()
15709 .filter(|location| {
15710 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15711 })
15712 .map(HoverLink::Text)
15713 .collect::<Vec<_>>(),
15714 split,
15715 window,
15716 cx,
15717 )
15718 })?
15719 .await?;
15720 anyhow::Ok(navigated)
15721 })
15722 }
15723
15724 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15725 let selection = self.selections.newest_anchor();
15726 let head = selection.head();
15727 let tail = selection.tail();
15728
15729 let Some((buffer, start_position)) =
15730 self.buffer.read(cx).text_anchor_for_position(head, cx)
15731 else {
15732 return;
15733 };
15734
15735 let end_position = if head != tail {
15736 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15737 return;
15738 };
15739 Some(pos)
15740 } else {
15741 None
15742 };
15743
15744 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15745 let url = if let Some(end_pos) = end_position {
15746 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15747 } else {
15748 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15749 };
15750
15751 if let Some(url) = url {
15752 editor.update(cx, |_, cx| {
15753 cx.open_url(&url);
15754 })
15755 } else {
15756 Ok(())
15757 }
15758 });
15759
15760 url_finder.detach();
15761 }
15762
15763 pub fn open_selected_filename(
15764 &mut self,
15765 _: &OpenSelectedFilename,
15766 window: &mut Window,
15767 cx: &mut Context<Self>,
15768 ) {
15769 let Some(workspace) = self.workspace() else {
15770 return;
15771 };
15772
15773 let position = self.selections.newest_anchor().head();
15774
15775 let Some((buffer, buffer_position)) =
15776 self.buffer.read(cx).text_anchor_for_position(position, cx)
15777 else {
15778 return;
15779 };
15780
15781 let project = self.project.clone();
15782
15783 cx.spawn_in(window, async move |_, cx| {
15784 let result = find_file(&buffer, project, buffer_position, cx).await;
15785
15786 if let Some((_, path)) = result {
15787 workspace
15788 .update_in(cx, |workspace, window, cx| {
15789 workspace.open_resolved_path(path, window, cx)
15790 })?
15791 .await?;
15792 }
15793 anyhow::Ok(())
15794 })
15795 .detach();
15796 }
15797
15798 pub(crate) fn navigate_to_hover_links(
15799 &mut self,
15800 kind: Option<GotoDefinitionKind>,
15801 definitions: Vec<HoverLink>,
15802 split: bool,
15803 window: &mut Window,
15804 cx: &mut Context<Editor>,
15805 ) -> Task<Result<Navigated>> {
15806 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15807 let mut first_url_or_file = None;
15808 let definitions: Vec<_> = definitions
15809 .into_iter()
15810 .filter_map(|def| match def {
15811 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15812 HoverLink::InlayHint(lsp_location, server_id) => {
15813 let computation =
15814 self.compute_target_location(lsp_location, server_id, window, cx);
15815 Some(cx.background_spawn(computation))
15816 }
15817 HoverLink::Url(url) => {
15818 first_url_or_file = Some(Either::Left(url));
15819 None
15820 }
15821 HoverLink::File(path) => {
15822 first_url_or_file = Some(Either::Right(path));
15823 None
15824 }
15825 })
15826 .collect();
15827
15828 let workspace = self.workspace();
15829
15830 cx.spawn_in(window, async move |editor, acx| {
15831 let mut locations: Vec<Location> = future::join_all(definitions)
15832 .await
15833 .into_iter()
15834 .filter_map(|location| location.transpose())
15835 .collect::<Result<_>>()
15836 .context("location tasks")?;
15837
15838 if locations.len() > 1 {
15839 let Some(workspace) = workspace else {
15840 return Ok(Navigated::No);
15841 };
15842
15843 let tab_kind = match kind {
15844 Some(GotoDefinitionKind::Implementation) => "Implementations",
15845 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
15846 Some(GotoDefinitionKind::Declaration) => "Declarations",
15847 Some(GotoDefinitionKind::Type) => "Types",
15848 };
15849 let title = editor
15850 .update_in(acx, |_, _, cx| {
15851 let target = locations
15852 .iter()
15853 .map(|location| {
15854 location
15855 .buffer
15856 .read(cx)
15857 .text_for_range(location.range.clone())
15858 .collect::<String>()
15859 })
15860 .join(", ");
15861 format!("{tab_kind} for {target}")
15862 })
15863 .context("buffer title")?;
15864
15865 let opened = workspace
15866 .update_in(acx, |workspace, window, cx| {
15867 Self::open_locations_in_multibuffer(
15868 workspace,
15869 locations,
15870 title,
15871 split,
15872 MultibufferSelectionMode::First,
15873 window,
15874 cx,
15875 )
15876 })
15877 .is_ok();
15878
15879 anyhow::Ok(Navigated::from_bool(opened))
15880 } else if locations.is_empty() {
15881 // If there is one definition, just open it directly
15882 match first_url_or_file {
15883 Some(Either::Left(url)) => {
15884 acx.update(|_, cx| cx.open_url(&url))?;
15885 Ok(Navigated::Yes)
15886 }
15887 Some(Either::Right(path)) => {
15888 let Some(workspace) = workspace else {
15889 return Ok(Navigated::No);
15890 };
15891
15892 workspace
15893 .update_in(acx, |workspace, window, cx| {
15894 workspace.open_resolved_path(path, window, cx)
15895 })?
15896 .await?;
15897 Ok(Navigated::Yes)
15898 }
15899 None => Ok(Navigated::No),
15900 }
15901 } else {
15902 let Some(workspace) = workspace else {
15903 return Ok(Navigated::No);
15904 };
15905
15906 let target = locations.pop().unwrap();
15907 editor.update_in(acx, |editor, window, cx| {
15908 let pane = workspace.read(cx).active_pane().clone();
15909
15910 let range = target.range.to_point(target.buffer.read(cx));
15911 let range = editor.range_for_match(&range);
15912 let range = collapse_multiline_range(range);
15913
15914 if !split
15915 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15916 {
15917 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15918 } else {
15919 window.defer(cx, move |window, cx| {
15920 let target_editor: Entity<Self> =
15921 workspace.update(cx, |workspace, cx| {
15922 let pane = if split {
15923 workspace.adjacent_pane(window, cx)
15924 } else {
15925 workspace.active_pane().clone()
15926 };
15927
15928 workspace.open_project_item(
15929 pane,
15930 target.buffer.clone(),
15931 true,
15932 true,
15933 window,
15934 cx,
15935 )
15936 });
15937 target_editor.update(cx, |target_editor, cx| {
15938 // When selecting a definition in a different buffer, disable the nav history
15939 // to avoid creating a history entry at the previous cursor location.
15940 pane.update(cx, |pane, _| pane.disable_history());
15941 target_editor.go_to_singleton_buffer_range(range, window, cx);
15942 pane.update(cx, |pane, _| pane.enable_history());
15943 });
15944 });
15945 }
15946 Navigated::Yes
15947 })
15948 }
15949 })
15950 }
15951
15952 fn compute_target_location(
15953 &self,
15954 lsp_location: lsp::Location,
15955 server_id: LanguageServerId,
15956 window: &mut Window,
15957 cx: &mut Context<Self>,
15958 ) -> Task<anyhow::Result<Option<Location>>> {
15959 let Some(project) = self.project.clone() else {
15960 return Task::ready(Ok(None));
15961 };
15962
15963 cx.spawn_in(window, async move |editor, cx| {
15964 let location_task = editor.update(cx, |_, cx| {
15965 project.update(cx, |project, cx| {
15966 let language_server_name = project
15967 .language_server_statuses(cx)
15968 .find(|(id, _)| server_id == *id)
15969 .map(|(_, status)| status.name.clone());
15970 language_server_name.map(|language_server_name| {
15971 project.open_local_buffer_via_lsp(
15972 lsp_location.uri.clone(),
15973 server_id,
15974 language_server_name,
15975 cx,
15976 )
15977 })
15978 })
15979 })?;
15980 let location = match location_task {
15981 Some(task) => Some({
15982 let target_buffer_handle = task.await.context("open local buffer")?;
15983 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15984 let target_start = target_buffer
15985 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15986 let target_end = target_buffer
15987 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15988 target_buffer.anchor_after(target_start)
15989 ..target_buffer.anchor_before(target_end)
15990 })?;
15991 Location {
15992 buffer: target_buffer_handle,
15993 range,
15994 }
15995 }),
15996 None => None,
15997 };
15998 Ok(location)
15999 })
16000 }
16001
16002 pub fn find_all_references(
16003 &mut self,
16004 _: &FindAllReferences,
16005 window: &mut Window,
16006 cx: &mut Context<Self>,
16007 ) -> Option<Task<Result<Navigated>>> {
16008 let selection = self.selections.newest::<usize>(cx);
16009 let multi_buffer = self.buffer.read(cx);
16010 let head = selection.head();
16011
16012 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16013 let head_anchor = multi_buffer_snapshot.anchor_at(
16014 head,
16015 if head < selection.tail() {
16016 Bias::Right
16017 } else {
16018 Bias::Left
16019 },
16020 );
16021
16022 match self
16023 .find_all_references_task_sources
16024 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16025 {
16026 Ok(_) => {
16027 log::info!(
16028 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16029 );
16030 return None;
16031 }
16032 Err(i) => {
16033 self.find_all_references_task_sources.insert(i, head_anchor);
16034 }
16035 }
16036
16037 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16038 let workspace = self.workspace()?;
16039 let project = workspace.read(cx).project().clone();
16040 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16041 Some(cx.spawn_in(window, async move |editor, cx| {
16042 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16043 if let Ok(i) = editor
16044 .find_all_references_task_sources
16045 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16046 {
16047 editor.find_all_references_task_sources.remove(i);
16048 }
16049 });
16050
16051 let locations = references.await?;
16052 if locations.is_empty() {
16053 return anyhow::Ok(Navigated::No);
16054 }
16055
16056 workspace.update_in(cx, |workspace, window, cx| {
16057 let target = locations
16058 .iter()
16059 .map(|location| {
16060 location
16061 .buffer
16062 .read(cx)
16063 .text_for_range(location.range.clone())
16064 .collect::<String>()
16065 })
16066 .join(", ");
16067 let title = format!("References to {target}");
16068 Self::open_locations_in_multibuffer(
16069 workspace,
16070 locations,
16071 title,
16072 false,
16073 MultibufferSelectionMode::First,
16074 window,
16075 cx,
16076 );
16077 Navigated::Yes
16078 })
16079 }))
16080 }
16081
16082 /// Opens a multibuffer with the given project locations in it
16083 pub fn open_locations_in_multibuffer(
16084 workspace: &mut Workspace,
16085 mut locations: Vec<Location>,
16086 title: String,
16087 split: bool,
16088 multibuffer_selection_mode: MultibufferSelectionMode,
16089 window: &mut Window,
16090 cx: &mut Context<Workspace>,
16091 ) {
16092 if locations.is_empty() {
16093 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16094 return;
16095 }
16096
16097 // If there are multiple definitions, open them in a multibuffer
16098 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16099 let mut locations = locations.into_iter().peekable();
16100 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16101 let capability = workspace.project().read(cx).capability();
16102
16103 let excerpt_buffer = cx.new(|cx| {
16104 let mut multibuffer = MultiBuffer::new(capability);
16105 while let Some(location) = locations.next() {
16106 let buffer = location.buffer.read(cx);
16107 let mut ranges_for_buffer = Vec::new();
16108 let range = location.range.to_point(buffer);
16109 ranges_for_buffer.push(range.clone());
16110
16111 while let Some(next_location) = locations.peek() {
16112 if next_location.buffer == location.buffer {
16113 ranges_for_buffer.push(next_location.range.to_point(buffer));
16114 locations.next();
16115 } else {
16116 break;
16117 }
16118 }
16119
16120 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16121 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16122 PathKey::for_buffer(&location.buffer, cx),
16123 location.buffer.clone(),
16124 ranges_for_buffer,
16125 DEFAULT_MULTIBUFFER_CONTEXT,
16126 cx,
16127 );
16128 ranges.extend(new_ranges)
16129 }
16130
16131 multibuffer.with_title(title)
16132 });
16133
16134 let editor = cx.new(|cx| {
16135 Editor::for_multibuffer(
16136 excerpt_buffer,
16137 Some(workspace.project().clone()),
16138 window,
16139 cx,
16140 )
16141 });
16142 editor.update(cx, |editor, cx| {
16143 match multibuffer_selection_mode {
16144 MultibufferSelectionMode::First => {
16145 if let Some(first_range) = ranges.first() {
16146 editor.change_selections(
16147 SelectionEffects::no_scroll(),
16148 window,
16149 cx,
16150 |selections| {
16151 selections.clear_disjoint();
16152 selections
16153 .select_anchor_ranges(std::iter::once(first_range.clone()));
16154 },
16155 );
16156 }
16157 editor.highlight_background::<Self>(
16158 &ranges,
16159 |theme| theme.colors().editor_highlighted_line_background,
16160 cx,
16161 );
16162 }
16163 MultibufferSelectionMode::All => {
16164 editor.change_selections(
16165 SelectionEffects::no_scroll(),
16166 window,
16167 cx,
16168 |selections| {
16169 selections.clear_disjoint();
16170 selections.select_anchor_ranges(ranges);
16171 },
16172 );
16173 }
16174 }
16175 editor.register_buffers_with_language_servers(cx);
16176 });
16177
16178 let item = Box::new(editor);
16179 let item_id = item.item_id();
16180
16181 if split {
16182 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16183 } else {
16184 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16185 let (preview_item_id, preview_item_idx) =
16186 workspace.active_pane().read_with(cx, |pane, _| {
16187 (pane.preview_item_id(), pane.preview_item_idx())
16188 });
16189
16190 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16191
16192 if let Some(preview_item_id) = preview_item_id {
16193 workspace.active_pane().update(cx, |pane, cx| {
16194 pane.remove_item(preview_item_id, false, false, window, cx);
16195 });
16196 }
16197 } else {
16198 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16199 }
16200 }
16201 workspace.active_pane().update(cx, |pane, cx| {
16202 pane.set_preview_item_id(Some(item_id), cx);
16203 });
16204 }
16205
16206 pub fn rename(
16207 &mut self,
16208 _: &Rename,
16209 window: &mut Window,
16210 cx: &mut Context<Self>,
16211 ) -> Option<Task<Result<()>>> {
16212 use language::ToOffset as _;
16213
16214 let provider = self.semantics_provider.clone()?;
16215 let selection = self.selections.newest_anchor().clone();
16216 let (cursor_buffer, cursor_buffer_position) = self
16217 .buffer
16218 .read(cx)
16219 .text_anchor_for_position(selection.head(), cx)?;
16220 let (tail_buffer, cursor_buffer_position_end) = self
16221 .buffer
16222 .read(cx)
16223 .text_anchor_for_position(selection.tail(), cx)?;
16224 if tail_buffer != cursor_buffer {
16225 return None;
16226 }
16227
16228 let snapshot = cursor_buffer.read(cx).snapshot();
16229 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16230 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16231 let prepare_rename = provider
16232 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16233 .unwrap_or_else(|| Task::ready(Ok(None)));
16234 drop(snapshot);
16235
16236 Some(cx.spawn_in(window, async move |this, cx| {
16237 let rename_range = if let Some(range) = prepare_rename.await? {
16238 Some(range)
16239 } else {
16240 this.update(cx, |this, cx| {
16241 let buffer = this.buffer.read(cx).snapshot(cx);
16242 let mut buffer_highlights = this
16243 .document_highlights_for_position(selection.head(), &buffer)
16244 .filter(|highlight| {
16245 highlight.start.excerpt_id == selection.head().excerpt_id
16246 && highlight.end.excerpt_id == selection.head().excerpt_id
16247 });
16248 buffer_highlights
16249 .next()
16250 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16251 })?
16252 };
16253 if let Some(rename_range) = rename_range {
16254 this.update_in(cx, |this, window, cx| {
16255 let snapshot = cursor_buffer.read(cx).snapshot();
16256 let rename_buffer_range = rename_range.to_offset(&snapshot);
16257 let cursor_offset_in_rename_range =
16258 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16259 let cursor_offset_in_rename_range_end =
16260 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16261
16262 this.take_rename(false, window, cx);
16263 let buffer = this.buffer.read(cx).read(cx);
16264 let cursor_offset = selection.head().to_offset(&buffer);
16265 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16266 let rename_end = rename_start + rename_buffer_range.len();
16267 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16268 let mut old_highlight_id = None;
16269 let old_name: Arc<str> = buffer
16270 .chunks(rename_start..rename_end, true)
16271 .map(|chunk| {
16272 if old_highlight_id.is_none() {
16273 old_highlight_id = chunk.syntax_highlight_id;
16274 }
16275 chunk.text
16276 })
16277 .collect::<String>()
16278 .into();
16279
16280 drop(buffer);
16281
16282 // Position the selection in the rename editor so that it matches the current selection.
16283 this.show_local_selections = false;
16284 let rename_editor = cx.new(|cx| {
16285 let mut editor = Editor::single_line(window, cx);
16286 editor.buffer.update(cx, |buffer, cx| {
16287 buffer.edit([(0..0, old_name.clone())], None, cx)
16288 });
16289 let rename_selection_range = match cursor_offset_in_rename_range
16290 .cmp(&cursor_offset_in_rename_range_end)
16291 {
16292 Ordering::Equal => {
16293 editor.select_all(&SelectAll, window, cx);
16294 return editor;
16295 }
16296 Ordering::Less => {
16297 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16298 }
16299 Ordering::Greater => {
16300 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16301 }
16302 };
16303 if rename_selection_range.end > old_name.len() {
16304 editor.select_all(&SelectAll, window, cx);
16305 } else {
16306 editor.change_selections(Default::default(), window, cx, |s| {
16307 s.select_ranges([rename_selection_range]);
16308 });
16309 }
16310 editor
16311 });
16312 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16313 if e == &EditorEvent::Focused {
16314 cx.emit(EditorEvent::FocusedIn)
16315 }
16316 })
16317 .detach();
16318
16319 let write_highlights =
16320 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16321 let read_highlights =
16322 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16323 let ranges = write_highlights
16324 .iter()
16325 .flat_map(|(_, ranges)| ranges.iter())
16326 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16327 .cloned()
16328 .collect();
16329
16330 this.highlight_text::<Rename>(
16331 ranges,
16332 HighlightStyle {
16333 fade_out: Some(0.6),
16334 ..Default::default()
16335 },
16336 cx,
16337 );
16338 let rename_focus_handle = rename_editor.focus_handle(cx);
16339 window.focus(&rename_focus_handle);
16340 let block_id = this.insert_blocks(
16341 [BlockProperties {
16342 style: BlockStyle::Flex,
16343 placement: BlockPlacement::Below(range.start),
16344 height: Some(1),
16345 render: Arc::new({
16346 let rename_editor = rename_editor.clone();
16347 move |cx: &mut BlockContext| {
16348 let mut text_style = cx.editor_style.text.clone();
16349 if let Some(highlight_style) = old_highlight_id
16350 .and_then(|h| h.style(&cx.editor_style.syntax))
16351 {
16352 text_style = text_style.highlight(highlight_style);
16353 }
16354 div()
16355 .block_mouse_except_scroll()
16356 .pl(cx.anchor_x)
16357 .child(EditorElement::new(
16358 &rename_editor,
16359 EditorStyle {
16360 background: cx.theme().system().transparent,
16361 local_player: cx.editor_style.local_player,
16362 text: text_style,
16363 scrollbar_width: cx.editor_style.scrollbar_width,
16364 syntax: cx.editor_style.syntax.clone(),
16365 status: cx.editor_style.status.clone(),
16366 inlay_hints_style: HighlightStyle {
16367 font_weight: Some(FontWeight::BOLD),
16368 ..make_inlay_hints_style(cx.app)
16369 },
16370 edit_prediction_styles: make_suggestion_styles(
16371 cx.app,
16372 ),
16373 ..EditorStyle::default()
16374 },
16375 ))
16376 .into_any_element()
16377 }
16378 }),
16379 priority: 0,
16380 }],
16381 Some(Autoscroll::fit()),
16382 cx,
16383 )[0];
16384 this.pending_rename = Some(RenameState {
16385 range,
16386 old_name,
16387 editor: rename_editor,
16388 block_id,
16389 });
16390 })?;
16391 }
16392
16393 Ok(())
16394 }))
16395 }
16396
16397 pub fn confirm_rename(
16398 &mut self,
16399 _: &ConfirmRename,
16400 window: &mut Window,
16401 cx: &mut Context<Self>,
16402 ) -> Option<Task<Result<()>>> {
16403 let rename = self.take_rename(false, window, cx)?;
16404 let workspace = self.workspace()?.downgrade();
16405 let (buffer, start) = self
16406 .buffer
16407 .read(cx)
16408 .text_anchor_for_position(rename.range.start, cx)?;
16409 let (end_buffer, _) = self
16410 .buffer
16411 .read(cx)
16412 .text_anchor_for_position(rename.range.end, cx)?;
16413 if buffer != end_buffer {
16414 return None;
16415 }
16416
16417 let old_name = rename.old_name;
16418 let new_name = rename.editor.read(cx).text(cx);
16419
16420 let rename = self.semantics_provider.as_ref()?.perform_rename(
16421 &buffer,
16422 start,
16423 new_name.clone(),
16424 cx,
16425 )?;
16426
16427 Some(cx.spawn_in(window, async move |editor, cx| {
16428 let project_transaction = rename.await?;
16429 Self::open_project_transaction(
16430 &editor,
16431 workspace,
16432 project_transaction,
16433 format!("Rename: {} → {}", old_name, new_name),
16434 cx,
16435 )
16436 .await?;
16437
16438 editor.update(cx, |editor, cx| {
16439 editor.refresh_document_highlights(cx);
16440 })?;
16441 Ok(())
16442 }))
16443 }
16444
16445 fn take_rename(
16446 &mut self,
16447 moving_cursor: bool,
16448 window: &mut Window,
16449 cx: &mut Context<Self>,
16450 ) -> Option<RenameState> {
16451 let rename = self.pending_rename.take()?;
16452 if rename.editor.focus_handle(cx).is_focused(window) {
16453 window.focus(&self.focus_handle);
16454 }
16455
16456 self.remove_blocks(
16457 [rename.block_id].into_iter().collect(),
16458 Some(Autoscroll::fit()),
16459 cx,
16460 );
16461 self.clear_highlights::<Rename>(cx);
16462 self.show_local_selections = true;
16463
16464 if moving_cursor {
16465 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16466 editor.selections.newest::<usize>(cx).head()
16467 });
16468
16469 // Update the selection to match the position of the selection inside
16470 // the rename editor.
16471 let snapshot = self.buffer.read(cx).read(cx);
16472 let rename_range = rename.range.to_offset(&snapshot);
16473 let cursor_in_editor = snapshot
16474 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16475 .min(rename_range.end);
16476 drop(snapshot);
16477
16478 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16479 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16480 });
16481 } else {
16482 self.refresh_document_highlights(cx);
16483 }
16484
16485 Some(rename)
16486 }
16487
16488 pub fn pending_rename(&self) -> Option<&RenameState> {
16489 self.pending_rename.as_ref()
16490 }
16491
16492 fn format(
16493 &mut self,
16494 _: &Format,
16495 window: &mut Window,
16496 cx: &mut Context<Self>,
16497 ) -> Option<Task<Result<()>>> {
16498 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16499
16500 let project = match &self.project {
16501 Some(project) => project.clone(),
16502 None => return None,
16503 };
16504
16505 Some(self.perform_format(
16506 project,
16507 FormatTrigger::Manual,
16508 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16509 window,
16510 cx,
16511 ))
16512 }
16513
16514 fn format_selections(
16515 &mut self,
16516 _: &FormatSelections,
16517 window: &mut Window,
16518 cx: &mut Context<Self>,
16519 ) -> Option<Task<Result<()>>> {
16520 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16521
16522 let project = match &self.project {
16523 Some(project) => project.clone(),
16524 None => return None,
16525 };
16526
16527 let ranges = self
16528 .selections
16529 .all_adjusted(cx)
16530 .into_iter()
16531 .map(|selection| selection.range())
16532 .collect_vec();
16533
16534 Some(self.perform_format(
16535 project,
16536 FormatTrigger::Manual,
16537 FormatTarget::Ranges(ranges),
16538 window,
16539 cx,
16540 ))
16541 }
16542
16543 fn perform_format(
16544 &mut self,
16545 project: Entity<Project>,
16546 trigger: FormatTrigger,
16547 target: FormatTarget,
16548 window: &mut Window,
16549 cx: &mut Context<Self>,
16550 ) -> Task<Result<()>> {
16551 let buffer = self.buffer.clone();
16552 let (buffers, target) = match target {
16553 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16554 FormatTarget::Ranges(selection_ranges) => {
16555 let multi_buffer = buffer.read(cx);
16556 let snapshot = multi_buffer.read(cx);
16557 let mut buffers = HashSet::default();
16558 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16559 BTreeMap::new();
16560 for selection_range in selection_ranges {
16561 for (buffer, buffer_range, _) in
16562 snapshot.range_to_buffer_ranges(selection_range)
16563 {
16564 let buffer_id = buffer.remote_id();
16565 let start = buffer.anchor_before(buffer_range.start);
16566 let end = buffer.anchor_after(buffer_range.end);
16567 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16568 buffer_id_to_ranges
16569 .entry(buffer_id)
16570 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16571 .or_insert_with(|| vec![start..end]);
16572 }
16573 }
16574 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16575 }
16576 };
16577
16578 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16579 let selections_prev = transaction_id_prev
16580 .and_then(|transaction_id_prev| {
16581 // default to selections as they were after the last edit, if we have them,
16582 // instead of how they are now.
16583 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16584 // will take you back to where you made the last edit, instead of staying where you scrolled
16585 self.selection_history
16586 .transaction(transaction_id_prev)
16587 .map(|t| t.0.clone())
16588 })
16589 .unwrap_or_else(|| {
16590 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16591 self.selections.disjoint_anchors()
16592 });
16593
16594 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16595 let format = project.update(cx, |project, cx| {
16596 project.format(buffers, target, true, trigger, cx)
16597 });
16598
16599 cx.spawn_in(window, async move |editor, cx| {
16600 let transaction = futures::select_biased! {
16601 transaction = format.log_err().fuse() => transaction,
16602 () = timeout => {
16603 log::warn!("timed out waiting for formatting");
16604 None
16605 }
16606 };
16607
16608 buffer
16609 .update(cx, |buffer, cx| {
16610 if let Some(transaction) = transaction {
16611 if !buffer.is_singleton() {
16612 buffer.push_transaction(&transaction.0, cx);
16613 }
16614 }
16615 cx.notify();
16616 })
16617 .ok();
16618
16619 if let Some(transaction_id_now) =
16620 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16621 {
16622 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16623 if has_new_transaction {
16624 _ = editor.update(cx, |editor, _| {
16625 editor
16626 .selection_history
16627 .insert_transaction(transaction_id_now, selections_prev);
16628 });
16629 }
16630 }
16631
16632 Ok(())
16633 })
16634 }
16635
16636 fn organize_imports(
16637 &mut self,
16638 _: &OrganizeImports,
16639 window: &mut Window,
16640 cx: &mut Context<Self>,
16641 ) -> Option<Task<Result<()>>> {
16642 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16643 let project = match &self.project {
16644 Some(project) => project.clone(),
16645 None => return None,
16646 };
16647 Some(self.perform_code_action_kind(
16648 project,
16649 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16650 window,
16651 cx,
16652 ))
16653 }
16654
16655 fn perform_code_action_kind(
16656 &mut self,
16657 project: Entity<Project>,
16658 kind: CodeActionKind,
16659 window: &mut Window,
16660 cx: &mut Context<Self>,
16661 ) -> Task<Result<()>> {
16662 let buffer = self.buffer.clone();
16663 let buffers = buffer.read(cx).all_buffers();
16664 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16665 let apply_action = project.update(cx, |project, cx| {
16666 project.apply_code_action_kind(buffers, kind, true, cx)
16667 });
16668 cx.spawn_in(window, async move |_, cx| {
16669 let transaction = futures::select_biased! {
16670 () = timeout => {
16671 log::warn!("timed out waiting for executing code action");
16672 None
16673 }
16674 transaction = apply_action.log_err().fuse() => transaction,
16675 };
16676 buffer
16677 .update(cx, |buffer, cx| {
16678 // check if we need this
16679 if let Some(transaction) = transaction {
16680 if !buffer.is_singleton() {
16681 buffer.push_transaction(&transaction.0, cx);
16682 }
16683 }
16684 cx.notify();
16685 })
16686 .ok();
16687 Ok(())
16688 })
16689 }
16690
16691 pub fn restart_language_server(
16692 &mut self,
16693 _: &RestartLanguageServer,
16694 _: &mut Window,
16695 cx: &mut Context<Self>,
16696 ) {
16697 if let Some(project) = self.project.clone() {
16698 self.buffer.update(cx, |multi_buffer, cx| {
16699 project.update(cx, |project, cx| {
16700 project.restart_language_servers_for_buffers(
16701 multi_buffer.all_buffers().into_iter().collect(),
16702 HashSet::default(),
16703 cx,
16704 );
16705 });
16706 })
16707 }
16708 }
16709
16710 pub fn stop_language_server(
16711 &mut self,
16712 _: &StopLanguageServer,
16713 _: &mut Window,
16714 cx: &mut Context<Self>,
16715 ) {
16716 if let Some(project) = self.project.clone() {
16717 self.buffer.update(cx, |multi_buffer, cx| {
16718 project.update(cx, |project, cx| {
16719 project.stop_language_servers_for_buffers(
16720 multi_buffer.all_buffers().into_iter().collect(),
16721 HashSet::default(),
16722 cx,
16723 );
16724 cx.emit(project::Event::RefreshInlayHints);
16725 });
16726 });
16727 }
16728 }
16729
16730 fn cancel_language_server_work(
16731 workspace: &mut Workspace,
16732 _: &actions::CancelLanguageServerWork,
16733 _: &mut Window,
16734 cx: &mut Context<Workspace>,
16735 ) {
16736 let project = workspace.project();
16737 let buffers = workspace
16738 .active_item(cx)
16739 .and_then(|item| item.act_as::<Editor>(cx))
16740 .map_or(HashSet::default(), |editor| {
16741 editor.read(cx).buffer.read(cx).all_buffers()
16742 });
16743 project.update(cx, |project, cx| {
16744 project.cancel_language_server_work_for_buffers(buffers, cx);
16745 });
16746 }
16747
16748 fn show_character_palette(
16749 &mut self,
16750 _: &ShowCharacterPalette,
16751 window: &mut Window,
16752 _: &mut Context<Self>,
16753 ) {
16754 window.show_character_palette();
16755 }
16756
16757 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16758 if !self.diagnostics_enabled() {
16759 return;
16760 }
16761
16762 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16763 let buffer = self.buffer.read(cx).snapshot(cx);
16764 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16765 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16766 let is_valid = buffer
16767 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16768 .any(|entry| {
16769 entry.diagnostic.is_primary
16770 && !entry.range.is_empty()
16771 && entry.range.start == primary_range_start
16772 && entry.diagnostic.message == active_diagnostics.active_message
16773 });
16774
16775 if !is_valid {
16776 self.dismiss_diagnostics(cx);
16777 }
16778 }
16779 }
16780
16781 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16782 match &self.active_diagnostics {
16783 ActiveDiagnostic::Group(group) => Some(group),
16784 _ => None,
16785 }
16786 }
16787
16788 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16789 if !self.diagnostics_enabled() {
16790 return;
16791 }
16792 self.dismiss_diagnostics(cx);
16793 self.active_diagnostics = ActiveDiagnostic::All;
16794 }
16795
16796 fn activate_diagnostics(
16797 &mut self,
16798 buffer_id: BufferId,
16799 diagnostic: DiagnosticEntry<usize>,
16800 window: &mut Window,
16801 cx: &mut Context<Self>,
16802 ) {
16803 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16804 return;
16805 }
16806 self.dismiss_diagnostics(cx);
16807 let snapshot = self.snapshot(window, cx);
16808 let buffer = self.buffer.read(cx).snapshot(cx);
16809 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16810 return;
16811 };
16812
16813 let diagnostic_group = buffer
16814 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16815 .collect::<Vec<_>>();
16816
16817 let blocks =
16818 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16819
16820 let blocks = self.display_map.update(cx, |display_map, cx| {
16821 display_map.insert_blocks(blocks, cx).into_iter().collect()
16822 });
16823 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16824 active_range: buffer.anchor_before(diagnostic.range.start)
16825 ..buffer.anchor_after(diagnostic.range.end),
16826 active_message: diagnostic.diagnostic.message.clone(),
16827 group_id: diagnostic.diagnostic.group_id,
16828 blocks,
16829 });
16830 cx.notify();
16831 }
16832
16833 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16834 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16835 return;
16836 };
16837
16838 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16839 if let ActiveDiagnostic::Group(group) = prev {
16840 self.display_map.update(cx, |display_map, cx| {
16841 display_map.remove_blocks(group.blocks, cx);
16842 });
16843 cx.notify();
16844 }
16845 }
16846
16847 /// Disable inline diagnostics rendering for this editor.
16848 pub fn disable_inline_diagnostics(&mut self) {
16849 self.inline_diagnostics_enabled = false;
16850 self.inline_diagnostics_update = Task::ready(());
16851 self.inline_diagnostics.clear();
16852 }
16853
16854 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16855 self.diagnostics_enabled = false;
16856 self.dismiss_diagnostics(cx);
16857 self.inline_diagnostics_update = Task::ready(());
16858 self.inline_diagnostics.clear();
16859 }
16860
16861 pub fn diagnostics_enabled(&self) -> bool {
16862 self.diagnostics_enabled && self.mode.is_full()
16863 }
16864
16865 pub fn inline_diagnostics_enabled(&self) -> bool {
16866 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16867 }
16868
16869 pub fn show_inline_diagnostics(&self) -> bool {
16870 self.show_inline_diagnostics
16871 }
16872
16873 pub fn toggle_inline_diagnostics(
16874 &mut self,
16875 _: &ToggleInlineDiagnostics,
16876 window: &mut Window,
16877 cx: &mut Context<Editor>,
16878 ) {
16879 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16880 self.refresh_inline_diagnostics(false, window, cx);
16881 }
16882
16883 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16884 self.diagnostics_max_severity = severity;
16885 self.display_map.update(cx, |display_map, _| {
16886 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16887 });
16888 }
16889
16890 pub fn toggle_diagnostics(
16891 &mut self,
16892 _: &ToggleDiagnostics,
16893 window: &mut Window,
16894 cx: &mut Context<Editor>,
16895 ) {
16896 if !self.diagnostics_enabled() {
16897 return;
16898 }
16899
16900 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16901 EditorSettings::get_global(cx)
16902 .diagnostics_max_severity
16903 .filter(|severity| severity != &DiagnosticSeverity::Off)
16904 .unwrap_or(DiagnosticSeverity::Hint)
16905 } else {
16906 DiagnosticSeverity::Off
16907 };
16908 self.set_max_diagnostics_severity(new_severity, cx);
16909 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16910 self.active_diagnostics = ActiveDiagnostic::None;
16911 self.inline_diagnostics_update = Task::ready(());
16912 self.inline_diagnostics.clear();
16913 } else {
16914 self.refresh_inline_diagnostics(false, window, cx);
16915 }
16916
16917 cx.notify();
16918 }
16919
16920 pub fn toggle_minimap(
16921 &mut self,
16922 _: &ToggleMinimap,
16923 window: &mut Window,
16924 cx: &mut Context<Editor>,
16925 ) {
16926 if self.supports_minimap(cx) {
16927 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16928 }
16929 }
16930
16931 fn refresh_inline_diagnostics(
16932 &mut self,
16933 debounce: bool,
16934 window: &mut Window,
16935 cx: &mut Context<Self>,
16936 ) {
16937 let max_severity = ProjectSettings::get_global(cx)
16938 .diagnostics
16939 .inline
16940 .max_severity
16941 .unwrap_or(self.diagnostics_max_severity);
16942
16943 if !self.inline_diagnostics_enabled()
16944 || !self.show_inline_diagnostics
16945 || max_severity == DiagnosticSeverity::Off
16946 {
16947 self.inline_diagnostics_update = Task::ready(());
16948 self.inline_diagnostics.clear();
16949 return;
16950 }
16951
16952 let debounce_ms = ProjectSettings::get_global(cx)
16953 .diagnostics
16954 .inline
16955 .update_debounce_ms;
16956 let debounce = if debounce && debounce_ms > 0 {
16957 Some(Duration::from_millis(debounce_ms))
16958 } else {
16959 None
16960 };
16961 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16962 if let Some(debounce) = debounce {
16963 cx.background_executor().timer(debounce).await;
16964 }
16965 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16966 editor
16967 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16968 .ok()
16969 }) else {
16970 return;
16971 };
16972
16973 let new_inline_diagnostics = cx
16974 .background_spawn(async move {
16975 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16976 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16977 let message = diagnostic_entry
16978 .diagnostic
16979 .message
16980 .split_once('\n')
16981 .map(|(line, _)| line)
16982 .map(SharedString::new)
16983 .unwrap_or_else(|| {
16984 SharedString::from(diagnostic_entry.diagnostic.message)
16985 });
16986 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16987 let (Ok(i) | Err(i)) = inline_diagnostics
16988 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16989 inline_diagnostics.insert(
16990 i,
16991 (
16992 start_anchor,
16993 InlineDiagnostic {
16994 message,
16995 group_id: diagnostic_entry.diagnostic.group_id,
16996 start: diagnostic_entry.range.start.to_point(&snapshot),
16997 is_primary: diagnostic_entry.diagnostic.is_primary,
16998 severity: diagnostic_entry.diagnostic.severity,
16999 },
17000 ),
17001 );
17002 }
17003 inline_diagnostics
17004 })
17005 .await;
17006
17007 editor
17008 .update(cx, |editor, cx| {
17009 editor.inline_diagnostics = new_inline_diagnostics;
17010 cx.notify();
17011 })
17012 .ok();
17013 });
17014 }
17015
17016 fn pull_diagnostics(
17017 &mut self,
17018 buffer_id: Option<BufferId>,
17019 window: &Window,
17020 cx: &mut Context<Self>,
17021 ) -> Option<()> {
17022 if !self.mode().is_full() {
17023 return None;
17024 }
17025 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17026 .diagnostics
17027 .lsp_pull_diagnostics;
17028 if !pull_diagnostics_settings.enabled {
17029 return None;
17030 }
17031 let project = self.project.as_ref()?.downgrade();
17032 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17033 let mut buffers = self.buffer.read(cx).all_buffers();
17034 if let Some(buffer_id) = buffer_id {
17035 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17036 }
17037
17038 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17039 cx.background_executor().timer(debounce).await;
17040
17041 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17042 buffers
17043 .into_iter()
17044 .filter_map(|buffer| {
17045 project
17046 .update(cx, |project, cx| {
17047 project.lsp_store().update(cx, |lsp_store, cx| {
17048 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17049 })
17050 })
17051 .ok()
17052 })
17053 .collect::<FuturesUnordered<_>>()
17054 }) else {
17055 return;
17056 };
17057
17058 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17059 match pull_task {
17060 Ok(()) => {
17061 if editor
17062 .update_in(cx, |editor, window, cx| {
17063 editor.update_diagnostics_state(window, cx);
17064 })
17065 .is_err()
17066 {
17067 return;
17068 }
17069 }
17070 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17071 }
17072 }
17073 });
17074
17075 Some(())
17076 }
17077
17078 pub fn set_selections_from_remote(
17079 &mut self,
17080 selections: Vec<Selection<Anchor>>,
17081 pending_selection: Option<Selection<Anchor>>,
17082 window: &mut Window,
17083 cx: &mut Context<Self>,
17084 ) {
17085 let old_cursor_position = self.selections.newest_anchor().head();
17086 self.selections.change_with(cx, |s| {
17087 s.select_anchors(selections);
17088 if let Some(pending_selection) = pending_selection {
17089 s.set_pending(pending_selection, SelectMode::Character);
17090 } else {
17091 s.clear_pending();
17092 }
17093 });
17094 self.selections_did_change(
17095 false,
17096 &old_cursor_position,
17097 SelectionEffects::default(),
17098 window,
17099 cx,
17100 );
17101 }
17102
17103 pub fn transact(
17104 &mut self,
17105 window: &mut Window,
17106 cx: &mut Context<Self>,
17107 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17108 ) -> Option<TransactionId> {
17109 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17110 this.start_transaction_at(Instant::now(), window, cx);
17111 update(this, window, cx);
17112 this.end_transaction_at(Instant::now(), cx)
17113 })
17114 }
17115
17116 pub fn start_transaction_at(
17117 &mut self,
17118 now: Instant,
17119 window: &mut Window,
17120 cx: &mut Context<Self>,
17121 ) -> Option<TransactionId> {
17122 self.end_selection(window, cx);
17123 if let Some(tx_id) = self
17124 .buffer
17125 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17126 {
17127 self.selection_history
17128 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17129 cx.emit(EditorEvent::TransactionBegun {
17130 transaction_id: tx_id,
17131 });
17132 Some(tx_id)
17133 } else {
17134 None
17135 }
17136 }
17137
17138 pub fn end_transaction_at(
17139 &mut self,
17140 now: Instant,
17141 cx: &mut Context<Self>,
17142 ) -> Option<TransactionId> {
17143 if let Some(transaction_id) = self
17144 .buffer
17145 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17146 {
17147 if let Some((_, end_selections)) =
17148 self.selection_history.transaction_mut(transaction_id)
17149 {
17150 *end_selections = Some(self.selections.disjoint_anchors());
17151 } else {
17152 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17153 }
17154
17155 cx.emit(EditorEvent::Edited { transaction_id });
17156 Some(transaction_id)
17157 } else {
17158 None
17159 }
17160 }
17161
17162 pub fn modify_transaction_selection_history(
17163 &mut self,
17164 transaction_id: TransactionId,
17165 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17166 ) -> bool {
17167 self.selection_history
17168 .transaction_mut(transaction_id)
17169 .map(modify)
17170 .is_some()
17171 }
17172
17173 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17174 if self.selection_mark_mode {
17175 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17176 s.move_with(|_, sel| {
17177 sel.collapse_to(sel.head(), SelectionGoal::None);
17178 });
17179 })
17180 }
17181 self.selection_mark_mode = true;
17182 cx.notify();
17183 }
17184
17185 pub fn swap_selection_ends(
17186 &mut self,
17187 _: &actions::SwapSelectionEnds,
17188 window: &mut Window,
17189 cx: &mut Context<Self>,
17190 ) {
17191 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17192 s.move_with(|_, sel| {
17193 if sel.start != sel.end {
17194 sel.reversed = !sel.reversed
17195 }
17196 });
17197 });
17198 self.request_autoscroll(Autoscroll::newest(), cx);
17199 cx.notify();
17200 }
17201
17202 pub fn toggle_focus(
17203 workspace: &mut Workspace,
17204 _: &actions::ToggleFocus,
17205 window: &mut Window,
17206 cx: &mut Context<Workspace>,
17207 ) {
17208 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17209 return;
17210 };
17211 workspace.activate_item(&item, true, true, window, cx);
17212 }
17213
17214 pub fn toggle_fold(
17215 &mut self,
17216 _: &actions::ToggleFold,
17217 window: &mut Window,
17218 cx: &mut Context<Self>,
17219 ) {
17220 if self.is_singleton(cx) {
17221 let selection = self.selections.newest::<Point>(cx);
17222
17223 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17224 let range = if selection.is_empty() {
17225 let point = selection.head().to_display_point(&display_map);
17226 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17227 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17228 .to_point(&display_map);
17229 start..end
17230 } else {
17231 selection.range()
17232 };
17233 if display_map.folds_in_range(range).next().is_some() {
17234 self.unfold_lines(&Default::default(), window, cx)
17235 } else {
17236 self.fold(&Default::default(), window, cx)
17237 }
17238 } else {
17239 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17240 let buffer_ids: HashSet<_> = self
17241 .selections
17242 .disjoint_anchor_ranges()
17243 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17244 .collect();
17245
17246 let should_unfold = buffer_ids
17247 .iter()
17248 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17249
17250 for buffer_id in buffer_ids {
17251 if should_unfold {
17252 self.unfold_buffer(buffer_id, cx);
17253 } else {
17254 self.fold_buffer(buffer_id, cx);
17255 }
17256 }
17257 }
17258 }
17259
17260 pub fn toggle_fold_recursive(
17261 &mut self,
17262 _: &actions::ToggleFoldRecursive,
17263 window: &mut Window,
17264 cx: &mut Context<Self>,
17265 ) {
17266 let selection = self.selections.newest::<Point>(cx);
17267
17268 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17269 let range = if selection.is_empty() {
17270 let point = selection.head().to_display_point(&display_map);
17271 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17272 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17273 .to_point(&display_map);
17274 start..end
17275 } else {
17276 selection.range()
17277 };
17278 if display_map.folds_in_range(range).next().is_some() {
17279 self.unfold_recursive(&Default::default(), window, cx)
17280 } else {
17281 self.fold_recursive(&Default::default(), window, cx)
17282 }
17283 }
17284
17285 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17286 if self.is_singleton(cx) {
17287 let mut to_fold = Vec::new();
17288 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17289 let selections = self.selections.all_adjusted(cx);
17290
17291 for selection in selections {
17292 let range = selection.range().sorted();
17293 let buffer_start_row = range.start.row;
17294
17295 if range.start.row != range.end.row {
17296 let mut found = false;
17297 let mut row = range.start.row;
17298 while row <= range.end.row {
17299 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17300 {
17301 found = true;
17302 row = crease.range().end.row + 1;
17303 to_fold.push(crease);
17304 } else {
17305 row += 1
17306 }
17307 }
17308 if found {
17309 continue;
17310 }
17311 }
17312
17313 for row in (0..=range.start.row).rev() {
17314 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17315 if crease.range().end.row >= buffer_start_row {
17316 to_fold.push(crease);
17317 if row <= range.start.row {
17318 break;
17319 }
17320 }
17321 }
17322 }
17323 }
17324
17325 self.fold_creases(to_fold, true, window, cx);
17326 } else {
17327 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17328 let buffer_ids = self
17329 .selections
17330 .disjoint_anchor_ranges()
17331 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17332 .collect::<HashSet<_>>();
17333 for buffer_id in buffer_ids {
17334 self.fold_buffer(buffer_id, cx);
17335 }
17336 }
17337 }
17338
17339 pub fn toggle_fold_all(
17340 &mut self,
17341 _: &actions::ToggleFoldAll,
17342 window: &mut Window,
17343 cx: &mut Context<Self>,
17344 ) {
17345 if self.buffer.read(cx).is_singleton() {
17346 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17347 let has_folds = display_map
17348 .folds_in_range(0..display_map.buffer_snapshot.len())
17349 .next()
17350 .is_some();
17351
17352 if has_folds {
17353 self.unfold_all(&actions::UnfoldAll, window, cx);
17354 } else {
17355 self.fold_all(&actions::FoldAll, window, cx);
17356 }
17357 } else {
17358 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17359 let should_unfold = buffer_ids
17360 .iter()
17361 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17362
17363 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17364 editor
17365 .update_in(cx, |editor, _, cx| {
17366 for buffer_id in buffer_ids {
17367 if should_unfold {
17368 editor.unfold_buffer(buffer_id, cx);
17369 } else {
17370 editor.fold_buffer(buffer_id, cx);
17371 }
17372 }
17373 })
17374 .ok();
17375 });
17376 }
17377 }
17378
17379 fn fold_at_level(
17380 &mut self,
17381 fold_at: &FoldAtLevel,
17382 window: &mut Window,
17383 cx: &mut Context<Self>,
17384 ) {
17385 if !self.buffer.read(cx).is_singleton() {
17386 return;
17387 }
17388
17389 let fold_at_level = fold_at.0;
17390 let snapshot = self.buffer.read(cx).snapshot(cx);
17391 let mut to_fold = Vec::new();
17392 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17393
17394 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17395 while start_row < end_row {
17396 match self
17397 .snapshot(window, cx)
17398 .crease_for_buffer_row(MultiBufferRow(start_row))
17399 {
17400 Some(crease) => {
17401 let nested_start_row = crease.range().start.row + 1;
17402 let nested_end_row = crease.range().end.row;
17403
17404 if current_level < fold_at_level {
17405 stack.push((nested_start_row, nested_end_row, current_level + 1));
17406 } else if current_level == fold_at_level {
17407 to_fold.push(crease);
17408 }
17409
17410 start_row = nested_end_row + 1;
17411 }
17412 None => start_row += 1,
17413 }
17414 }
17415 }
17416
17417 self.fold_creases(to_fold, true, window, cx);
17418 }
17419
17420 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17421 if self.buffer.read(cx).is_singleton() {
17422 let mut fold_ranges = Vec::new();
17423 let snapshot = self.buffer.read(cx).snapshot(cx);
17424
17425 for row in 0..snapshot.max_row().0 {
17426 if let Some(foldable_range) = self
17427 .snapshot(window, cx)
17428 .crease_for_buffer_row(MultiBufferRow(row))
17429 {
17430 fold_ranges.push(foldable_range);
17431 }
17432 }
17433
17434 self.fold_creases(fold_ranges, true, window, cx);
17435 } else {
17436 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17437 editor
17438 .update_in(cx, |editor, _, cx| {
17439 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17440 editor.fold_buffer(buffer_id, cx);
17441 }
17442 })
17443 .ok();
17444 });
17445 }
17446 }
17447
17448 pub fn fold_function_bodies(
17449 &mut self,
17450 _: &actions::FoldFunctionBodies,
17451 window: &mut Window,
17452 cx: &mut Context<Self>,
17453 ) {
17454 let snapshot = self.buffer.read(cx).snapshot(cx);
17455
17456 let ranges = snapshot
17457 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17458 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17459 .collect::<Vec<_>>();
17460
17461 let creases = ranges
17462 .into_iter()
17463 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17464 .collect();
17465
17466 self.fold_creases(creases, true, window, cx);
17467 }
17468
17469 pub fn fold_recursive(
17470 &mut self,
17471 _: &actions::FoldRecursive,
17472 window: &mut Window,
17473 cx: &mut Context<Self>,
17474 ) {
17475 let mut to_fold = Vec::new();
17476 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17477 let selections = self.selections.all_adjusted(cx);
17478
17479 for selection in selections {
17480 let range = selection.range().sorted();
17481 let buffer_start_row = range.start.row;
17482
17483 if range.start.row != range.end.row {
17484 let mut found = false;
17485 for row in range.start.row..=range.end.row {
17486 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17487 found = true;
17488 to_fold.push(crease);
17489 }
17490 }
17491 if found {
17492 continue;
17493 }
17494 }
17495
17496 for row in (0..=range.start.row).rev() {
17497 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17498 if crease.range().end.row >= buffer_start_row {
17499 to_fold.push(crease);
17500 } else {
17501 break;
17502 }
17503 }
17504 }
17505 }
17506
17507 self.fold_creases(to_fold, true, window, cx);
17508 }
17509
17510 pub fn fold_at(
17511 &mut self,
17512 buffer_row: MultiBufferRow,
17513 window: &mut Window,
17514 cx: &mut Context<Self>,
17515 ) {
17516 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17517
17518 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17519 let autoscroll = self
17520 .selections
17521 .all::<Point>(cx)
17522 .iter()
17523 .any(|selection| crease.range().overlaps(&selection.range()));
17524
17525 self.fold_creases(vec![crease], autoscroll, window, cx);
17526 }
17527 }
17528
17529 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17530 if self.is_singleton(cx) {
17531 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17532 let buffer = &display_map.buffer_snapshot;
17533 let selections = self.selections.all::<Point>(cx);
17534 let ranges = selections
17535 .iter()
17536 .map(|s| {
17537 let range = s.display_range(&display_map).sorted();
17538 let mut start = range.start.to_point(&display_map);
17539 let mut end = range.end.to_point(&display_map);
17540 start.column = 0;
17541 end.column = buffer.line_len(MultiBufferRow(end.row));
17542 start..end
17543 })
17544 .collect::<Vec<_>>();
17545
17546 self.unfold_ranges(&ranges, true, true, cx);
17547 } else {
17548 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17549 let buffer_ids = self
17550 .selections
17551 .disjoint_anchor_ranges()
17552 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17553 .collect::<HashSet<_>>();
17554 for buffer_id in buffer_ids {
17555 self.unfold_buffer(buffer_id, cx);
17556 }
17557 }
17558 }
17559
17560 pub fn unfold_recursive(
17561 &mut self,
17562 _: &UnfoldRecursive,
17563 _window: &mut Window,
17564 cx: &mut Context<Self>,
17565 ) {
17566 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17567 let selections = self.selections.all::<Point>(cx);
17568 let ranges = selections
17569 .iter()
17570 .map(|s| {
17571 let mut range = s.display_range(&display_map).sorted();
17572 *range.start.column_mut() = 0;
17573 *range.end.column_mut() = display_map.line_len(range.end.row());
17574 let start = range.start.to_point(&display_map);
17575 let end = range.end.to_point(&display_map);
17576 start..end
17577 })
17578 .collect::<Vec<_>>();
17579
17580 self.unfold_ranges(&ranges, true, true, cx);
17581 }
17582
17583 pub fn unfold_at(
17584 &mut self,
17585 buffer_row: MultiBufferRow,
17586 _window: &mut Window,
17587 cx: &mut Context<Self>,
17588 ) {
17589 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17590
17591 let intersection_range = Point::new(buffer_row.0, 0)
17592 ..Point::new(
17593 buffer_row.0,
17594 display_map.buffer_snapshot.line_len(buffer_row),
17595 );
17596
17597 let autoscroll = self
17598 .selections
17599 .all::<Point>(cx)
17600 .iter()
17601 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17602
17603 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17604 }
17605
17606 pub fn unfold_all(
17607 &mut self,
17608 _: &actions::UnfoldAll,
17609 _window: &mut Window,
17610 cx: &mut Context<Self>,
17611 ) {
17612 if self.buffer.read(cx).is_singleton() {
17613 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17614 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17615 } else {
17616 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17617 editor
17618 .update(cx, |editor, cx| {
17619 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17620 editor.unfold_buffer(buffer_id, cx);
17621 }
17622 })
17623 .ok();
17624 });
17625 }
17626 }
17627
17628 pub fn fold_selected_ranges(
17629 &mut self,
17630 _: &FoldSelectedRanges,
17631 window: &mut Window,
17632 cx: &mut Context<Self>,
17633 ) {
17634 let selections = self.selections.all_adjusted(cx);
17635 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17636 let ranges = selections
17637 .into_iter()
17638 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17639 .collect::<Vec<_>>();
17640 self.fold_creases(ranges, true, window, cx);
17641 }
17642
17643 pub fn fold_ranges<T: ToOffset + Clone>(
17644 &mut self,
17645 ranges: Vec<Range<T>>,
17646 auto_scroll: bool,
17647 window: &mut Window,
17648 cx: &mut Context<Self>,
17649 ) {
17650 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17651 let ranges = ranges
17652 .into_iter()
17653 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17654 .collect::<Vec<_>>();
17655 self.fold_creases(ranges, auto_scroll, window, cx);
17656 }
17657
17658 pub fn fold_creases<T: ToOffset + Clone>(
17659 &mut self,
17660 creases: Vec<Crease<T>>,
17661 auto_scroll: bool,
17662 _window: &mut Window,
17663 cx: &mut Context<Self>,
17664 ) {
17665 if creases.is_empty() {
17666 return;
17667 }
17668
17669 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17670
17671 if auto_scroll {
17672 self.request_autoscroll(Autoscroll::fit(), cx);
17673 }
17674
17675 cx.notify();
17676
17677 self.scrollbar_marker_state.dirty = true;
17678 self.folds_did_change(cx);
17679 }
17680
17681 /// Removes any folds whose ranges intersect any of the given ranges.
17682 pub fn unfold_ranges<T: ToOffset + Clone>(
17683 &mut self,
17684 ranges: &[Range<T>],
17685 inclusive: bool,
17686 auto_scroll: bool,
17687 cx: &mut Context<Self>,
17688 ) {
17689 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17690 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17691 });
17692 self.folds_did_change(cx);
17693 }
17694
17695 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17696 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17697 return;
17698 }
17699 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17700 self.display_map.update(cx, |display_map, cx| {
17701 display_map.fold_buffers([buffer_id], cx)
17702 });
17703 cx.emit(EditorEvent::BufferFoldToggled {
17704 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17705 folded: true,
17706 });
17707 cx.notify();
17708 }
17709
17710 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17711 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17712 return;
17713 }
17714 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17715 self.display_map.update(cx, |display_map, cx| {
17716 display_map.unfold_buffers([buffer_id], cx);
17717 });
17718 cx.emit(EditorEvent::BufferFoldToggled {
17719 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17720 folded: false,
17721 });
17722 cx.notify();
17723 }
17724
17725 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17726 self.display_map.read(cx).is_buffer_folded(buffer)
17727 }
17728
17729 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17730 self.display_map.read(cx).folded_buffers()
17731 }
17732
17733 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17734 self.display_map.update(cx, |display_map, cx| {
17735 display_map.disable_header_for_buffer(buffer_id, cx);
17736 });
17737 cx.notify();
17738 }
17739
17740 /// Removes any folds with the given ranges.
17741 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17742 &mut self,
17743 ranges: &[Range<T>],
17744 type_id: TypeId,
17745 auto_scroll: bool,
17746 cx: &mut Context<Self>,
17747 ) {
17748 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17749 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17750 });
17751 self.folds_did_change(cx);
17752 }
17753
17754 fn remove_folds_with<T: ToOffset + Clone>(
17755 &mut self,
17756 ranges: &[Range<T>],
17757 auto_scroll: bool,
17758 cx: &mut Context<Self>,
17759 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17760 ) {
17761 if ranges.is_empty() {
17762 return;
17763 }
17764
17765 let mut buffers_affected = HashSet::default();
17766 let multi_buffer = self.buffer().read(cx);
17767 for range in ranges {
17768 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17769 buffers_affected.insert(buffer.read(cx).remote_id());
17770 };
17771 }
17772
17773 self.display_map.update(cx, update);
17774
17775 if auto_scroll {
17776 self.request_autoscroll(Autoscroll::fit(), cx);
17777 }
17778
17779 cx.notify();
17780 self.scrollbar_marker_state.dirty = true;
17781 self.active_indent_guides_state.dirty = true;
17782 }
17783
17784 pub fn update_renderer_widths(
17785 &mut self,
17786 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17787 cx: &mut Context<Self>,
17788 ) -> bool {
17789 self.display_map
17790 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17791 }
17792
17793 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17794 self.display_map.read(cx).fold_placeholder.clone()
17795 }
17796
17797 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17798 self.buffer.update(cx, |buffer, cx| {
17799 buffer.set_all_diff_hunks_expanded(cx);
17800 });
17801 }
17802
17803 pub fn expand_all_diff_hunks(
17804 &mut self,
17805 _: &ExpandAllDiffHunks,
17806 _window: &mut Window,
17807 cx: &mut Context<Self>,
17808 ) {
17809 self.buffer.update(cx, |buffer, cx| {
17810 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17811 });
17812 }
17813
17814 pub fn toggle_selected_diff_hunks(
17815 &mut self,
17816 _: &ToggleSelectedDiffHunks,
17817 _window: &mut Window,
17818 cx: &mut Context<Self>,
17819 ) {
17820 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17821 self.toggle_diff_hunks_in_ranges(ranges, cx);
17822 }
17823
17824 pub fn diff_hunks_in_ranges<'a>(
17825 &'a self,
17826 ranges: &'a [Range<Anchor>],
17827 buffer: &'a MultiBufferSnapshot,
17828 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17829 ranges.iter().flat_map(move |range| {
17830 let end_excerpt_id = range.end.excerpt_id;
17831 let range = range.to_point(buffer);
17832 let mut peek_end = range.end;
17833 if range.end.row < buffer.max_row().0 {
17834 peek_end = Point::new(range.end.row + 1, 0);
17835 }
17836 buffer
17837 .diff_hunks_in_range(range.start..peek_end)
17838 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17839 })
17840 }
17841
17842 pub fn has_stageable_diff_hunks_in_ranges(
17843 &self,
17844 ranges: &[Range<Anchor>],
17845 snapshot: &MultiBufferSnapshot,
17846 ) -> bool {
17847 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17848 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17849 }
17850
17851 pub fn toggle_staged_selected_diff_hunks(
17852 &mut self,
17853 _: &::git::ToggleStaged,
17854 _: &mut Window,
17855 cx: &mut Context<Self>,
17856 ) {
17857 let snapshot = self.buffer.read(cx).snapshot(cx);
17858 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17859 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17860 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17861 }
17862
17863 pub fn set_render_diff_hunk_controls(
17864 &mut self,
17865 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17866 cx: &mut Context<Self>,
17867 ) {
17868 self.render_diff_hunk_controls = render_diff_hunk_controls;
17869 cx.notify();
17870 }
17871
17872 pub fn stage_and_next(
17873 &mut self,
17874 _: &::git::StageAndNext,
17875 window: &mut Window,
17876 cx: &mut Context<Self>,
17877 ) {
17878 self.do_stage_or_unstage_and_next(true, window, cx);
17879 }
17880
17881 pub fn unstage_and_next(
17882 &mut self,
17883 _: &::git::UnstageAndNext,
17884 window: &mut Window,
17885 cx: &mut Context<Self>,
17886 ) {
17887 self.do_stage_or_unstage_and_next(false, window, cx);
17888 }
17889
17890 pub fn stage_or_unstage_diff_hunks(
17891 &mut self,
17892 stage: bool,
17893 ranges: Vec<Range<Anchor>>,
17894 cx: &mut Context<Self>,
17895 ) {
17896 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17897 cx.spawn(async move |this, cx| {
17898 task.await?;
17899 this.update(cx, |this, cx| {
17900 let snapshot = this.buffer.read(cx).snapshot(cx);
17901 let chunk_by = this
17902 .diff_hunks_in_ranges(&ranges, &snapshot)
17903 .chunk_by(|hunk| hunk.buffer_id);
17904 for (buffer_id, hunks) in &chunk_by {
17905 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17906 }
17907 })
17908 })
17909 .detach_and_log_err(cx);
17910 }
17911
17912 fn save_buffers_for_ranges_if_needed(
17913 &mut self,
17914 ranges: &[Range<Anchor>],
17915 cx: &mut Context<Editor>,
17916 ) -> Task<Result<()>> {
17917 let multibuffer = self.buffer.read(cx);
17918 let snapshot = multibuffer.read(cx);
17919 let buffer_ids: HashSet<_> = ranges
17920 .iter()
17921 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17922 .collect();
17923 drop(snapshot);
17924
17925 let mut buffers = HashSet::default();
17926 for buffer_id in buffer_ids {
17927 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17928 let buffer = buffer_entity.read(cx);
17929 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17930 {
17931 buffers.insert(buffer_entity);
17932 }
17933 }
17934 }
17935
17936 if let Some(project) = &self.project {
17937 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17938 } else {
17939 Task::ready(Ok(()))
17940 }
17941 }
17942
17943 fn do_stage_or_unstage_and_next(
17944 &mut self,
17945 stage: bool,
17946 window: &mut Window,
17947 cx: &mut Context<Self>,
17948 ) {
17949 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17950
17951 if ranges.iter().any(|range| range.start != range.end) {
17952 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17953 return;
17954 }
17955
17956 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17957 let snapshot = self.snapshot(window, cx);
17958 let position = self.selections.newest::<Point>(cx).head();
17959 let mut row = snapshot
17960 .buffer_snapshot
17961 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17962 .find(|hunk| hunk.row_range.start.0 > position.row)
17963 .map(|hunk| hunk.row_range.start);
17964
17965 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17966 // Outside of the project diff editor, wrap around to the beginning.
17967 if !all_diff_hunks_expanded {
17968 row = row.or_else(|| {
17969 snapshot
17970 .buffer_snapshot
17971 .diff_hunks_in_range(Point::zero()..position)
17972 .find(|hunk| hunk.row_range.end.0 < position.row)
17973 .map(|hunk| hunk.row_range.start)
17974 });
17975 }
17976
17977 if let Some(row) = row {
17978 let destination = Point::new(row.0, 0);
17979 let autoscroll = Autoscroll::center();
17980
17981 self.unfold_ranges(&[destination..destination], false, false, cx);
17982 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17983 s.select_ranges([destination..destination]);
17984 });
17985 }
17986 }
17987
17988 fn do_stage_or_unstage(
17989 &self,
17990 stage: bool,
17991 buffer_id: BufferId,
17992 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17993 cx: &mut App,
17994 ) -> Option<()> {
17995 let project = self.project.as_ref()?;
17996 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17997 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17998 let buffer_snapshot = buffer.read(cx).snapshot();
17999 let file_exists = buffer_snapshot
18000 .file()
18001 .is_some_and(|file| file.disk_state().exists());
18002 diff.update(cx, |diff, cx| {
18003 diff.stage_or_unstage_hunks(
18004 stage,
18005 &hunks
18006 .map(|hunk| buffer_diff::DiffHunk {
18007 buffer_range: hunk.buffer_range,
18008 diff_base_byte_range: hunk.diff_base_byte_range,
18009 secondary_status: hunk.secondary_status,
18010 range: Point::zero()..Point::zero(), // unused
18011 })
18012 .collect::<Vec<_>>(),
18013 &buffer_snapshot,
18014 file_exists,
18015 cx,
18016 )
18017 });
18018 None
18019 }
18020
18021 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18022 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18023 self.buffer
18024 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18025 }
18026
18027 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18028 self.buffer.update(cx, |buffer, cx| {
18029 let ranges = vec![Anchor::min()..Anchor::max()];
18030 if !buffer.all_diff_hunks_expanded()
18031 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18032 {
18033 buffer.collapse_diff_hunks(ranges, cx);
18034 true
18035 } else {
18036 false
18037 }
18038 })
18039 }
18040
18041 fn toggle_diff_hunks_in_ranges(
18042 &mut self,
18043 ranges: Vec<Range<Anchor>>,
18044 cx: &mut Context<Editor>,
18045 ) {
18046 self.buffer.update(cx, |buffer, cx| {
18047 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18048 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18049 })
18050 }
18051
18052 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18053 self.buffer.update(cx, |buffer, cx| {
18054 let snapshot = buffer.snapshot(cx);
18055 let excerpt_id = range.end.excerpt_id;
18056 let point_range = range.to_point(&snapshot);
18057 let expand = !buffer.single_hunk_is_expanded(range, cx);
18058 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18059 })
18060 }
18061
18062 pub(crate) fn apply_all_diff_hunks(
18063 &mut self,
18064 _: &ApplyAllDiffHunks,
18065 window: &mut Window,
18066 cx: &mut Context<Self>,
18067 ) {
18068 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18069
18070 let buffers = self.buffer.read(cx).all_buffers();
18071 for branch_buffer in buffers {
18072 branch_buffer.update(cx, |branch_buffer, cx| {
18073 branch_buffer.merge_into_base(Vec::new(), cx);
18074 });
18075 }
18076
18077 if let Some(project) = self.project.clone() {
18078 self.save(
18079 SaveOptions {
18080 format: true,
18081 autosave: false,
18082 },
18083 project,
18084 window,
18085 cx,
18086 )
18087 .detach_and_log_err(cx);
18088 }
18089 }
18090
18091 pub(crate) fn apply_selected_diff_hunks(
18092 &mut self,
18093 _: &ApplyDiffHunk,
18094 window: &mut Window,
18095 cx: &mut Context<Self>,
18096 ) {
18097 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18098 let snapshot = self.snapshot(window, cx);
18099 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18100 let mut ranges_by_buffer = HashMap::default();
18101 self.transact(window, cx, |editor, _window, cx| {
18102 for hunk in hunks {
18103 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18104 ranges_by_buffer
18105 .entry(buffer.clone())
18106 .or_insert_with(Vec::new)
18107 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18108 }
18109 }
18110
18111 for (buffer, ranges) in ranges_by_buffer {
18112 buffer.update(cx, |buffer, cx| {
18113 buffer.merge_into_base(ranges, cx);
18114 });
18115 }
18116 });
18117
18118 if let Some(project) = self.project.clone() {
18119 self.save(
18120 SaveOptions {
18121 format: true,
18122 autosave: false,
18123 },
18124 project,
18125 window,
18126 cx,
18127 )
18128 .detach_and_log_err(cx);
18129 }
18130 }
18131
18132 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18133 if hovered != self.gutter_hovered {
18134 self.gutter_hovered = hovered;
18135 cx.notify();
18136 }
18137 }
18138
18139 pub fn insert_blocks(
18140 &mut self,
18141 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18142 autoscroll: Option<Autoscroll>,
18143 cx: &mut Context<Self>,
18144 ) -> Vec<CustomBlockId> {
18145 let blocks = self
18146 .display_map
18147 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18148 if let Some(autoscroll) = autoscroll {
18149 self.request_autoscroll(autoscroll, cx);
18150 }
18151 cx.notify();
18152 blocks
18153 }
18154
18155 pub fn resize_blocks(
18156 &mut self,
18157 heights: HashMap<CustomBlockId, u32>,
18158 autoscroll: Option<Autoscroll>,
18159 cx: &mut Context<Self>,
18160 ) {
18161 self.display_map
18162 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18163 if let Some(autoscroll) = autoscroll {
18164 self.request_autoscroll(autoscroll, cx);
18165 }
18166 cx.notify();
18167 }
18168
18169 pub fn replace_blocks(
18170 &mut self,
18171 renderers: HashMap<CustomBlockId, RenderBlock>,
18172 autoscroll: Option<Autoscroll>,
18173 cx: &mut Context<Self>,
18174 ) {
18175 self.display_map
18176 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18177 if let Some(autoscroll) = autoscroll {
18178 self.request_autoscroll(autoscroll, cx);
18179 }
18180 cx.notify();
18181 }
18182
18183 pub fn remove_blocks(
18184 &mut self,
18185 block_ids: HashSet<CustomBlockId>,
18186 autoscroll: Option<Autoscroll>,
18187 cx: &mut Context<Self>,
18188 ) {
18189 self.display_map.update(cx, |display_map, cx| {
18190 display_map.remove_blocks(block_ids, cx)
18191 });
18192 if let Some(autoscroll) = autoscroll {
18193 self.request_autoscroll(autoscroll, cx);
18194 }
18195 cx.notify();
18196 }
18197
18198 pub fn row_for_block(
18199 &self,
18200 block_id: CustomBlockId,
18201 cx: &mut Context<Self>,
18202 ) -> Option<DisplayRow> {
18203 self.display_map
18204 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18205 }
18206
18207 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18208 self.focused_block = Some(focused_block);
18209 }
18210
18211 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18212 self.focused_block.take()
18213 }
18214
18215 pub fn insert_creases(
18216 &mut self,
18217 creases: impl IntoIterator<Item = Crease<Anchor>>,
18218 cx: &mut Context<Self>,
18219 ) -> Vec<CreaseId> {
18220 self.display_map
18221 .update(cx, |map, cx| map.insert_creases(creases, cx))
18222 }
18223
18224 pub fn remove_creases(
18225 &mut self,
18226 ids: impl IntoIterator<Item = CreaseId>,
18227 cx: &mut Context<Self>,
18228 ) -> Vec<(CreaseId, Range<Anchor>)> {
18229 self.display_map
18230 .update(cx, |map, cx| map.remove_creases(ids, cx))
18231 }
18232
18233 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18234 self.display_map
18235 .update(cx, |map, cx| map.snapshot(cx))
18236 .longest_row()
18237 }
18238
18239 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18240 self.display_map
18241 .update(cx, |map, cx| map.snapshot(cx))
18242 .max_point()
18243 }
18244
18245 pub fn text(&self, cx: &App) -> String {
18246 self.buffer.read(cx).read(cx).text()
18247 }
18248
18249 pub fn is_empty(&self, cx: &App) -> bool {
18250 self.buffer.read(cx).read(cx).is_empty()
18251 }
18252
18253 pub fn text_option(&self, cx: &App) -> Option<String> {
18254 let text = self.text(cx);
18255 let text = text.trim();
18256
18257 if text.is_empty() {
18258 return None;
18259 }
18260
18261 Some(text.to_string())
18262 }
18263
18264 pub fn set_text(
18265 &mut self,
18266 text: impl Into<Arc<str>>,
18267 window: &mut Window,
18268 cx: &mut Context<Self>,
18269 ) {
18270 self.transact(window, cx, |this, _, cx| {
18271 this.buffer
18272 .read(cx)
18273 .as_singleton()
18274 .expect("you can only call set_text on editors for singleton buffers")
18275 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18276 });
18277 }
18278
18279 pub fn display_text(&self, cx: &mut App) -> String {
18280 self.display_map
18281 .update(cx, |map, cx| map.snapshot(cx))
18282 .text()
18283 }
18284
18285 fn create_minimap(
18286 &self,
18287 minimap_settings: MinimapSettings,
18288 window: &mut Window,
18289 cx: &mut Context<Self>,
18290 ) -> Option<Entity<Self>> {
18291 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18292 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18293 }
18294
18295 fn initialize_new_minimap(
18296 &self,
18297 minimap_settings: MinimapSettings,
18298 window: &mut Window,
18299 cx: &mut Context<Self>,
18300 ) -> Entity<Self> {
18301 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18302
18303 let mut minimap = Editor::new_internal(
18304 EditorMode::Minimap {
18305 parent: cx.weak_entity(),
18306 },
18307 self.buffer.clone(),
18308 None,
18309 Some(self.display_map.clone()),
18310 window,
18311 cx,
18312 );
18313 minimap.scroll_manager.clone_state(&self.scroll_manager);
18314 minimap.set_text_style_refinement(TextStyleRefinement {
18315 font_size: Some(MINIMAP_FONT_SIZE),
18316 font_weight: Some(MINIMAP_FONT_WEIGHT),
18317 ..Default::default()
18318 });
18319 minimap.update_minimap_configuration(minimap_settings, cx);
18320 cx.new(|_| minimap)
18321 }
18322
18323 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18324 let current_line_highlight = minimap_settings
18325 .current_line_highlight
18326 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18327 self.set_current_line_highlight(Some(current_line_highlight));
18328 }
18329
18330 pub fn minimap(&self) -> Option<&Entity<Self>> {
18331 self.minimap
18332 .as_ref()
18333 .filter(|_| self.minimap_visibility.visible())
18334 }
18335
18336 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18337 let mut wrap_guides = smallvec![];
18338
18339 if self.show_wrap_guides == Some(false) {
18340 return wrap_guides;
18341 }
18342
18343 let settings = self.buffer.read(cx).language_settings(cx);
18344 if settings.show_wrap_guides {
18345 match self.soft_wrap_mode(cx) {
18346 SoftWrap::Column(soft_wrap) => {
18347 wrap_guides.push((soft_wrap as usize, true));
18348 }
18349 SoftWrap::Bounded(soft_wrap) => {
18350 wrap_guides.push((soft_wrap as usize, true));
18351 }
18352 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18353 }
18354 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18355 }
18356
18357 wrap_guides
18358 }
18359
18360 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18361 let settings = self.buffer.read(cx).language_settings(cx);
18362 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18363 match mode {
18364 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18365 SoftWrap::None
18366 }
18367 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18368 language_settings::SoftWrap::PreferredLineLength => {
18369 SoftWrap::Column(settings.preferred_line_length)
18370 }
18371 language_settings::SoftWrap::Bounded => {
18372 SoftWrap::Bounded(settings.preferred_line_length)
18373 }
18374 }
18375 }
18376
18377 pub fn set_soft_wrap_mode(
18378 &mut self,
18379 mode: language_settings::SoftWrap,
18380
18381 cx: &mut Context<Self>,
18382 ) {
18383 self.soft_wrap_mode_override = Some(mode);
18384 cx.notify();
18385 }
18386
18387 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18388 self.hard_wrap = hard_wrap;
18389 cx.notify();
18390 }
18391
18392 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18393 self.text_style_refinement = Some(style);
18394 }
18395
18396 /// called by the Element so we know what style we were most recently rendered with.
18397 pub(crate) fn set_style(
18398 &mut self,
18399 style: EditorStyle,
18400 window: &mut Window,
18401 cx: &mut Context<Self>,
18402 ) {
18403 // We intentionally do not inform the display map about the minimap style
18404 // so that wrapping is not recalculated and stays consistent for the editor
18405 // and its linked minimap.
18406 if !self.mode.is_minimap() {
18407 let rem_size = window.rem_size();
18408 self.display_map.update(cx, |map, cx| {
18409 map.set_font(
18410 style.text.font(),
18411 style.text.font_size.to_pixels(rem_size),
18412 cx,
18413 )
18414 });
18415 }
18416 self.style = Some(style);
18417 }
18418
18419 pub fn style(&self) -> Option<&EditorStyle> {
18420 self.style.as_ref()
18421 }
18422
18423 // Called by the element. This method is not designed to be called outside of the editor
18424 // element's layout code because it does not notify when rewrapping is computed synchronously.
18425 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18426 self.display_map
18427 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18428 }
18429
18430 pub fn set_soft_wrap(&mut self) {
18431 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18432 }
18433
18434 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18435 if self.soft_wrap_mode_override.is_some() {
18436 self.soft_wrap_mode_override.take();
18437 } else {
18438 let soft_wrap = match self.soft_wrap_mode(cx) {
18439 SoftWrap::GitDiff => return,
18440 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18441 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18442 language_settings::SoftWrap::None
18443 }
18444 };
18445 self.soft_wrap_mode_override = Some(soft_wrap);
18446 }
18447 cx.notify();
18448 }
18449
18450 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18451 let Some(workspace) = self.workspace() else {
18452 return;
18453 };
18454 let fs = workspace.read(cx).app_state().fs.clone();
18455 let current_show = TabBarSettings::get_global(cx).show;
18456 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18457 setting.show = Some(!current_show);
18458 });
18459 }
18460
18461 pub fn toggle_indent_guides(
18462 &mut self,
18463 _: &ToggleIndentGuides,
18464 _: &mut Window,
18465 cx: &mut Context<Self>,
18466 ) {
18467 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18468 self.buffer
18469 .read(cx)
18470 .language_settings(cx)
18471 .indent_guides
18472 .enabled
18473 });
18474 self.show_indent_guides = Some(!currently_enabled);
18475 cx.notify();
18476 }
18477
18478 fn should_show_indent_guides(&self) -> Option<bool> {
18479 self.show_indent_guides
18480 }
18481
18482 pub fn toggle_line_numbers(
18483 &mut self,
18484 _: &ToggleLineNumbers,
18485 _: &mut Window,
18486 cx: &mut Context<Self>,
18487 ) {
18488 let mut editor_settings = EditorSettings::get_global(cx).clone();
18489 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18490 EditorSettings::override_global(editor_settings, cx);
18491 }
18492
18493 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18494 if let Some(show_line_numbers) = self.show_line_numbers {
18495 return show_line_numbers;
18496 }
18497 EditorSettings::get_global(cx).gutter.line_numbers
18498 }
18499
18500 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18501 self.use_relative_line_numbers
18502 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18503 }
18504
18505 pub fn toggle_relative_line_numbers(
18506 &mut self,
18507 _: &ToggleRelativeLineNumbers,
18508 _: &mut Window,
18509 cx: &mut Context<Self>,
18510 ) {
18511 let is_relative = self.should_use_relative_line_numbers(cx);
18512 self.set_relative_line_number(Some(!is_relative), cx)
18513 }
18514
18515 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18516 self.use_relative_line_numbers = is_relative;
18517 cx.notify();
18518 }
18519
18520 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18521 self.show_gutter = show_gutter;
18522 cx.notify();
18523 }
18524
18525 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18526 self.show_scrollbars = ScrollbarAxes {
18527 horizontal: show,
18528 vertical: show,
18529 };
18530 cx.notify();
18531 }
18532
18533 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18534 self.show_scrollbars.vertical = show;
18535 cx.notify();
18536 }
18537
18538 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18539 self.show_scrollbars.horizontal = show;
18540 cx.notify();
18541 }
18542
18543 pub fn set_minimap_visibility(
18544 &mut self,
18545 minimap_visibility: MinimapVisibility,
18546 window: &mut Window,
18547 cx: &mut Context<Self>,
18548 ) {
18549 if self.minimap_visibility != minimap_visibility {
18550 if minimap_visibility.visible() && self.minimap.is_none() {
18551 let minimap_settings = EditorSettings::get_global(cx).minimap;
18552 self.minimap =
18553 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18554 }
18555 self.minimap_visibility = minimap_visibility;
18556 cx.notify();
18557 }
18558 }
18559
18560 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18561 self.set_show_scrollbars(false, cx);
18562 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18563 }
18564
18565 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18566 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18567 }
18568
18569 /// Normally the text in full mode and auto height editors is padded on the
18570 /// left side by roughly half a character width for improved hit testing.
18571 ///
18572 /// Use this method to disable this for cases where this is not wanted (e.g.
18573 /// if you want to align the editor text with some other text above or below)
18574 /// or if you want to add this padding to single-line editors.
18575 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18576 self.offset_content = offset_content;
18577 cx.notify();
18578 }
18579
18580 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18581 self.show_line_numbers = Some(show_line_numbers);
18582 cx.notify();
18583 }
18584
18585 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18586 self.disable_expand_excerpt_buttons = true;
18587 cx.notify();
18588 }
18589
18590 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18591 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18592 cx.notify();
18593 }
18594
18595 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18596 self.show_code_actions = Some(show_code_actions);
18597 cx.notify();
18598 }
18599
18600 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18601 self.show_runnables = Some(show_runnables);
18602 cx.notify();
18603 }
18604
18605 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18606 self.show_breakpoints = Some(show_breakpoints);
18607 cx.notify();
18608 }
18609
18610 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18611 if self.display_map.read(cx).masked != masked {
18612 self.display_map.update(cx, |map, _| map.masked = masked);
18613 }
18614 cx.notify()
18615 }
18616
18617 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18618 self.show_wrap_guides = Some(show_wrap_guides);
18619 cx.notify();
18620 }
18621
18622 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18623 self.show_indent_guides = Some(show_indent_guides);
18624 cx.notify();
18625 }
18626
18627 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18628 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18629 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18630 if let Some(dir) = file.abs_path(cx).parent() {
18631 return Some(dir.to_owned());
18632 }
18633 }
18634
18635 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18636 return Some(project_path.path.to_path_buf());
18637 }
18638 }
18639
18640 None
18641 }
18642
18643 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18644 self.active_excerpt(cx)?
18645 .1
18646 .read(cx)
18647 .file()
18648 .and_then(|f| f.as_local())
18649 }
18650
18651 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18652 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18653 let buffer = buffer.read(cx);
18654 if let Some(project_path) = buffer.project_path(cx) {
18655 let project = self.project.as_ref()?.read(cx);
18656 project.absolute_path(&project_path, cx)
18657 } else {
18658 buffer
18659 .file()
18660 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18661 }
18662 })
18663 }
18664
18665 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18666 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18667 let project_path = buffer.read(cx).project_path(cx)?;
18668 let project = self.project.as_ref()?.read(cx);
18669 let entry = project.entry_for_path(&project_path, cx)?;
18670 let path = entry.path.to_path_buf();
18671 Some(path)
18672 })
18673 }
18674
18675 pub fn reveal_in_finder(
18676 &mut self,
18677 _: &RevealInFileManager,
18678 _window: &mut Window,
18679 cx: &mut Context<Self>,
18680 ) {
18681 if let Some(target) = self.target_file(cx) {
18682 cx.reveal_path(&target.abs_path(cx));
18683 }
18684 }
18685
18686 pub fn copy_path(
18687 &mut self,
18688 _: &zed_actions::workspace::CopyPath,
18689 _window: &mut Window,
18690 cx: &mut Context<Self>,
18691 ) {
18692 if let Some(path) = self.target_file_abs_path(cx) {
18693 if let Some(path) = path.to_str() {
18694 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18695 }
18696 }
18697 }
18698
18699 pub fn copy_relative_path(
18700 &mut self,
18701 _: &zed_actions::workspace::CopyRelativePath,
18702 _window: &mut Window,
18703 cx: &mut Context<Self>,
18704 ) {
18705 if let Some(path) = self.target_file_path(cx) {
18706 if let Some(path) = path.to_str() {
18707 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18708 }
18709 }
18710 }
18711
18712 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18713 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18714 buffer.read(cx).project_path(cx)
18715 } else {
18716 None
18717 }
18718 }
18719
18720 // Returns true if the editor handled a go-to-line request
18721 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18722 maybe!({
18723 let breakpoint_store = self.breakpoint_store.as_ref()?;
18724
18725 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18726 else {
18727 self.clear_row_highlights::<ActiveDebugLine>();
18728 return None;
18729 };
18730
18731 let position = active_stack_frame.position;
18732 let buffer_id = position.buffer_id?;
18733 let snapshot = self
18734 .project
18735 .as_ref()?
18736 .read(cx)
18737 .buffer_for_id(buffer_id, cx)?
18738 .read(cx)
18739 .snapshot();
18740
18741 let mut handled = false;
18742 for (id, ExcerptRange { context, .. }) in
18743 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18744 {
18745 if context.start.cmp(&position, &snapshot).is_ge()
18746 || context.end.cmp(&position, &snapshot).is_lt()
18747 {
18748 continue;
18749 }
18750 let snapshot = self.buffer.read(cx).snapshot(cx);
18751 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18752
18753 handled = true;
18754 self.clear_row_highlights::<ActiveDebugLine>();
18755
18756 self.go_to_line::<ActiveDebugLine>(
18757 multibuffer_anchor,
18758 Some(cx.theme().colors().editor_debugger_active_line_background),
18759 window,
18760 cx,
18761 );
18762
18763 cx.notify();
18764 }
18765
18766 handled.then_some(())
18767 })
18768 .is_some()
18769 }
18770
18771 pub fn copy_file_name_without_extension(
18772 &mut self,
18773 _: &CopyFileNameWithoutExtension,
18774 _: &mut Window,
18775 cx: &mut Context<Self>,
18776 ) {
18777 if let Some(file) = self.target_file(cx) {
18778 if let Some(file_stem) = file.path().file_stem() {
18779 if let Some(name) = file_stem.to_str() {
18780 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18781 }
18782 }
18783 }
18784 }
18785
18786 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18787 if let Some(file) = self.target_file(cx) {
18788 if let Some(file_name) = file.path().file_name() {
18789 if let Some(name) = file_name.to_str() {
18790 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18791 }
18792 }
18793 }
18794 }
18795
18796 pub fn toggle_git_blame(
18797 &mut self,
18798 _: &::git::Blame,
18799 window: &mut Window,
18800 cx: &mut Context<Self>,
18801 ) {
18802 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18803
18804 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18805 self.start_git_blame(true, window, cx);
18806 }
18807
18808 cx.notify();
18809 }
18810
18811 pub fn toggle_git_blame_inline(
18812 &mut self,
18813 _: &ToggleGitBlameInline,
18814 window: &mut Window,
18815 cx: &mut Context<Self>,
18816 ) {
18817 self.toggle_git_blame_inline_internal(true, window, cx);
18818 cx.notify();
18819 }
18820
18821 pub fn open_git_blame_commit(
18822 &mut self,
18823 _: &OpenGitBlameCommit,
18824 window: &mut Window,
18825 cx: &mut Context<Self>,
18826 ) {
18827 self.open_git_blame_commit_internal(window, cx);
18828 }
18829
18830 fn open_git_blame_commit_internal(
18831 &mut self,
18832 window: &mut Window,
18833 cx: &mut Context<Self>,
18834 ) -> Option<()> {
18835 let blame = self.blame.as_ref()?;
18836 let snapshot = self.snapshot(window, cx);
18837 let cursor = self.selections.newest::<Point>(cx).head();
18838 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18839 let blame_entry = blame
18840 .update(cx, |blame, cx| {
18841 blame
18842 .blame_for_rows(
18843 &[RowInfo {
18844 buffer_id: Some(buffer.remote_id()),
18845 buffer_row: Some(point.row),
18846 ..Default::default()
18847 }],
18848 cx,
18849 )
18850 .next()
18851 })
18852 .flatten()?;
18853 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18854 let repo = blame.read(cx).repository(cx)?;
18855 let workspace = self.workspace()?.downgrade();
18856 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18857 None
18858 }
18859
18860 pub fn git_blame_inline_enabled(&self) -> bool {
18861 self.git_blame_inline_enabled
18862 }
18863
18864 pub fn toggle_selection_menu(
18865 &mut self,
18866 _: &ToggleSelectionMenu,
18867 _: &mut Window,
18868 cx: &mut Context<Self>,
18869 ) {
18870 self.show_selection_menu = self
18871 .show_selection_menu
18872 .map(|show_selections_menu| !show_selections_menu)
18873 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18874
18875 cx.notify();
18876 }
18877
18878 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18879 self.show_selection_menu
18880 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18881 }
18882
18883 fn start_git_blame(
18884 &mut self,
18885 user_triggered: bool,
18886 window: &mut Window,
18887 cx: &mut Context<Self>,
18888 ) {
18889 if let Some(project) = self.project.as_ref() {
18890 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18891 return;
18892 };
18893
18894 if buffer.read(cx).file().is_none() {
18895 return;
18896 }
18897
18898 let focused = self.focus_handle(cx).contains_focused(window, cx);
18899
18900 let project = project.clone();
18901 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18902 self.blame_subscription =
18903 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18904 self.blame = Some(blame);
18905 }
18906 }
18907
18908 fn toggle_git_blame_inline_internal(
18909 &mut self,
18910 user_triggered: bool,
18911 window: &mut Window,
18912 cx: &mut Context<Self>,
18913 ) {
18914 if self.git_blame_inline_enabled {
18915 self.git_blame_inline_enabled = false;
18916 self.show_git_blame_inline = false;
18917 self.show_git_blame_inline_delay_task.take();
18918 } else {
18919 self.git_blame_inline_enabled = true;
18920 self.start_git_blame_inline(user_triggered, window, cx);
18921 }
18922
18923 cx.notify();
18924 }
18925
18926 fn start_git_blame_inline(
18927 &mut self,
18928 user_triggered: bool,
18929 window: &mut Window,
18930 cx: &mut Context<Self>,
18931 ) {
18932 self.start_git_blame(user_triggered, window, cx);
18933
18934 if ProjectSettings::get_global(cx)
18935 .git
18936 .inline_blame_delay()
18937 .is_some()
18938 {
18939 self.start_inline_blame_timer(window, cx);
18940 } else {
18941 self.show_git_blame_inline = true
18942 }
18943 }
18944
18945 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18946 self.blame.as_ref()
18947 }
18948
18949 pub fn show_git_blame_gutter(&self) -> bool {
18950 self.show_git_blame_gutter
18951 }
18952
18953 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18954 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18955 }
18956
18957 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18958 self.show_git_blame_inline
18959 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18960 && !self.newest_selection_head_on_empty_line(cx)
18961 && self.has_blame_entries(cx)
18962 }
18963
18964 fn has_blame_entries(&self, cx: &App) -> bool {
18965 self.blame()
18966 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18967 }
18968
18969 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18970 let cursor_anchor = self.selections.newest_anchor().head();
18971
18972 let snapshot = self.buffer.read(cx).snapshot(cx);
18973 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18974
18975 snapshot.line_len(buffer_row) == 0
18976 }
18977
18978 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18979 let buffer_and_selection = maybe!({
18980 let selection = self.selections.newest::<Point>(cx);
18981 let selection_range = selection.range();
18982
18983 let multi_buffer = self.buffer().read(cx);
18984 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18985 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18986
18987 let (buffer, range, _) = if selection.reversed {
18988 buffer_ranges.first()
18989 } else {
18990 buffer_ranges.last()
18991 }?;
18992
18993 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18994 ..text::ToPoint::to_point(&range.end, &buffer).row;
18995 Some((
18996 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18997 selection,
18998 ))
18999 });
19000
19001 let Some((buffer, selection)) = buffer_and_selection else {
19002 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19003 };
19004
19005 let Some(project) = self.project.as_ref() else {
19006 return Task::ready(Err(anyhow!("editor does not have project")));
19007 };
19008
19009 project.update(cx, |project, cx| {
19010 project.get_permalink_to_line(&buffer, selection, cx)
19011 })
19012 }
19013
19014 pub fn copy_permalink_to_line(
19015 &mut self,
19016 _: &CopyPermalinkToLine,
19017 window: &mut Window,
19018 cx: &mut Context<Self>,
19019 ) {
19020 let permalink_task = self.get_permalink_to_line(cx);
19021 let workspace = self.workspace();
19022
19023 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19024 Ok(permalink) => {
19025 cx.update(|_, cx| {
19026 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19027 })
19028 .ok();
19029 }
19030 Err(err) => {
19031 let message = format!("Failed to copy permalink: {err}");
19032
19033 anyhow::Result::<()>::Err(err).log_err();
19034
19035 if let Some(workspace) = workspace {
19036 workspace
19037 .update_in(cx, |workspace, _, cx| {
19038 struct CopyPermalinkToLine;
19039
19040 workspace.show_toast(
19041 Toast::new(
19042 NotificationId::unique::<CopyPermalinkToLine>(),
19043 message,
19044 ),
19045 cx,
19046 )
19047 })
19048 .ok();
19049 }
19050 }
19051 })
19052 .detach();
19053 }
19054
19055 pub fn copy_file_location(
19056 &mut self,
19057 _: &CopyFileLocation,
19058 _: &mut Window,
19059 cx: &mut Context<Self>,
19060 ) {
19061 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19062 if let Some(file) = self.target_file(cx) {
19063 if let Some(path) = file.path().to_str() {
19064 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19065 }
19066 }
19067 }
19068
19069 pub fn open_permalink_to_line(
19070 &mut self,
19071 _: &OpenPermalinkToLine,
19072 window: &mut Window,
19073 cx: &mut Context<Self>,
19074 ) {
19075 let permalink_task = self.get_permalink_to_line(cx);
19076 let workspace = self.workspace();
19077
19078 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19079 Ok(permalink) => {
19080 cx.update(|_, cx| {
19081 cx.open_url(permalink.as_ref());
19082 })
19083 .ok();
19084 }
19085 Err(err) => {
19086 let message = format!("Failed to open permalink: {err}");
19087
19088 anyhow::Result::<()>::Err(err).log_err();
19089
19090 if let Some(workspace) = workspace {
19091 workspace
19092 .update(cx, |workspace, cx| {
19093 struct OpenPermalinkToLine;
19094
19095 workspace.show_toast(
19096 Toast::new(
19097 NotificationId::unique::<OpenPermalinkToLine>(),
19098 message,
19099 ),
19100 cx,
19101 )
19102 })
19103 .ok();
19104 }
19105 }
19106 })
19107 .detach();
19108 }
19109
19110 pub fn insert_uuid_v4(
19111 &mut self,
19112 _: &InsertUuidV4,
19113 window: &mut Window,
19114 cx: &mut Context<Self>,
19115 ) {
19116 self.insert_uuid(UuidVersion::V4, window, cx);
19117 }
19118
19119 pub fn insert_uuid_v7(
19120 &mut self,
19121 _: &InsertUuidV7,
19122 window: &mut Window,
19123 cx: &mut Context<Self>,
19124 ) {
19125 self.insert_uuid(UuidVersion::V7, window, cx);
19126 }
19127
19128 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19129 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19130 self.transact(window, cx, |this, window, cx| {
19131 let edits = this
19132 .selections
19133 .all::<Point>(cx)
19134 .into_iter()
19135 .map(|selection| {
19136 let uuid = match version {
19137 UuidVersion::V4 => uuid::Uuid::new_v4(),
19138 UuidVersion::V7 => uuid::Uuid::now_v7(),
19139 };
19140
19141 (selection.range(), uuid.to_string())
19142 });
19143 this.edit(edits, cx);
19144 this.refresh_edit_prediction(true, false, window, cx);
19145 });
19146 }
19147
19148 pub fn open_selections_in_multibuffer(
19149 &mut self,
19150 _: &OpenSelectionsInMultibuffer,
19151 window: &mut Window,
19152 cx: &mut Context<Self>,
19153 ) {
19154 let multibuffer = self.buffer.read(cx);
19155
19156 let Some(buffer) = multibuffer.as_singleton() else {
19157 return;
19158 };
19159
19160 let Some(workspace) = self.workspace() else {
19161 return;
19162 };
19163
19164 let title = multibuffer.title(cx).to_string();
19165
19166 let locations = self
19167 .selections
19168 .all_anchors(cx)
19169 .into_iter()
19170 .map(|selection| Location {
19171 buffer: buffer.clone(),
19172 range: selection.start.text_anchor..selection.end.text_anchor,
19173 })
19174 .collect::<Vec<_>>();
19175
19176 cx.spawn_in(window, async move |_, cx| {
19177 workspace.update_in(cx, |workspace, window, cx| {
19178 Self::open_locations_in_multibuffer(
19179 workspace,
19180 locations,
19181 format!("Selections for '{title}'"),
19182 false,
19183 MultibufferSelectionMode::All,
19184 window,
19185 cx,
19186 );
19187 })
19188 })
19189 .detach();
19190 }
19191
19192 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19193 /// last highlight added will be used.
19194 ///
19195 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19196 pub fn highlight_rows<T: 'static>(
19197 &mut self,
19198 range: Range<Anchor>,
19199 color: Hsla,
19200 options: RowHighlightOptions,
19201 cx: &mut Context<Self>,
19202 ) {
19203 let snapshot = self.buffer().read(cx).snapshot(cx);
19204 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19205 let ix = row_highlights.binary_search_by(|highlight| {
19206 Ordering::Equal
19207 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19208 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19209 });
19210
19211 if let Err(mut ix) = ix {
19212 let index = post_inc(&mut self.highlight_order);
19213
19214 // If this range intersects with the preceding highlight, then merge it with
19215 // the preceding highlight. Otherwise insert a new highlight.
19216 let mut merged = false;
19217 if ix > 0 {
19218 let prev_highlight = &mut row_highlights[ix - 1];
19219 if prev_highlight
19220 .range
19221 .end
19222 .cmp(&range.start, &snapshot)
19223 .is_ge()
19224 {
19225 ix -= 1;
19226 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19227 prev_highlight.range.end = range.end;
19228 }
19229 merged = true;
19230 prev_highlight.index = index;
19231 prev_highlight.color = color;
19232 prev_highlight.options = options;
19233 }
19234 }
19235
19236 if !merged {
19237 row_highlights.insert(
19238 ix,
19239 RowHighlight {
19240 range: range.clone(),
19241 index,
19242 color,
19243 options,
19244 type_id: TypeId::of::<T>(),
19245 },
19246 );
19247 }
19248
19249 // If any of the following highlights intersect with this one, merge them.
19250 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19251 let highlight = &row_highlights[ix];
19252 if next_highlight
19253 .range
19254 .start
19255 .cmp(&highlight.range.end, &snapshot)
19256 .is_le()
19257 {
19258 if next_highlight
19259 .range
19260 .end
19261 .cmp(&highlight.range.end, &snapshot)
19262 .is_gt()
19263 {
19264 row_highlights[ix].range.end = next_highlight.range.end;
19265 }
19266 row_highlights.remove(ix + 1);
19267 } else {
19268 break;
19269 }
19270 }
19271 }
19272 }
19273
19274 /// Remove any highlighted row ranges of the given type that intersect the
19275 /// given ranges.
19276 pub fn remove_highlighted_rows<T: 'static>(
19277 &mut self,
19278 ranges_to_remove: Vec<Range<Anchor>>,
19279 cx: &mut Context<Self>,
19280 ) {
19281 let snapshot = self.buffer().read(cx).snapshot(cx);
19282 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19283 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19284 row_highlights.retain(|highlight| {
19285 while let Some(range_to_remove) = ranges_to_remove.peek() {
19286 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19287 Ordering::Less | Ordering::Equal => {
19288 ranges_to_remove.next();
19289 }
19290 Ordering::Greater => {
19291 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19292 Ordering::Less | Ordering::Equal => {
19293 return false;
19294 }
19295 Ordering::Greater => break,
19296 }
19297 }
19298 }
19299 }
19300
19301 true
19302 })
19303 }
19304
19305 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19306 pub fn clear_row_highlights<T: 'static>(&mut self) {
19307 self.highlighted_rows.remove(&TypeId::of::<T>());
19308 }
19309
19310 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19311 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19312 self.highlighted_rows
19313 .get(&TypeId::of::<T>())
19314 .map_or(&[] as &[_], |vec| vec.as_slice())
19315 .iter()
19316 .map(|highlight| (highlight.range.clone(), highlight.color))
19317 }
19318
19319 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19320 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19321 /// Allows to ignore certain kinds of highlights.
19322 pub fn highlighted_display_rows(
19323 &self,
19324 window: &mut Window,
19325 cx: &mut App,
19326 ) -> BTreeMap<DisplayRow, LineHighlight> {
19327 let snapshot = self.snapshot(window, cx);
19328 let mut used_highlight_orders = HashMap::default();
19329 self.highlighted_rows
19330 .iter()
19331 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19332 .fold(
19333 BTreeMap::<DisplayRow, LineHighlight>::new(),
19334 |mut unique_rows, highlight| {
19335 let start = highlight.range.start.to_display_point(&snapshot);
19336 let end = highlight.range.end.to_display_point(&snapshot);
19337 let start_row = start.row().0;
19338 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19339 && end.column() == 0
19340 {
19341 end.row().0.saturating_sub(1)
19342 } else {
19343 end.row().0
19344 };
19345 for row in start_row..=end_row {
19346 let used_index =
19347 used_highlight_orders.entry(row).or_insert(highlight.index);
19348 if highlight.index >= *used_index {
19349 *used_index = highlight.index;
19350 unique_rows.insert(
19351 DisplayRow(row),
19352 LineHighlight {
19353 include_gutter: highlight.options.include_gutter,
19354 border: None,
19355 background: highlight.color.into(),
19356 type_id: Some(highlight.type_id),
19357 },
19358 );
19359 }
19360 }
19361 unique_rows
19362 },
19363 )
19364 }
19365
19366 pub fn highlighted_display_row_for_autoscroll(
19367 &self,
19368 snapshot: &DisplaySnapshot,
19369 ) -> Option<DisplayRow> {
19370 self.highlighted_rows
19371 .values()
19372 .flat_map(|highlighted_rows| highlighted_rows.iter())
19373 .filter_map(|highlight| {
19374 if highlight.options.autoscroll {
19375 Some(highlight.range.start.to_display_point(snapshot).row())
19376 } else {
19377 None
19378 }
19379 })
19380 .min()
19381 }
19382
19383 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19384 self.highlight_background::<SearchWithinRange>(
19385 ranges,
19386 |colors| colors.colors().editor_document_highlight_read_background,
19387 cx,
19388 )
19389 }
19390
19391 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19392 self.breadcrumb_header = Some(new_header);
19393 }
19394
19395 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19396 self.clear_background_highlights::<SearchWithinRange>(cx);
19397 }
19398
19399 pub fn highlight_background<T: 'static>(
19400 &mut self,
19401 ranges: &[Range<Anchor>],
19402 color_fetcher: fn(&Theme) -> Hsla,
19403 cx: &mut Context<Self>,
19404 ) {
19405 self.background_highlights.insert(
19406 HighlightKey::Type(TypeId::of::<T>()),
19407 (color_fetcher, Arc::from(ranges)),
19408 );
19409 self.scrollbar_marker_state.dirty = true;
19410 cx.notify();
19411 }
19412
19413 pub fn highlight_background_key<T: 'static>(
19414 &mut self,
19415 key: usize,
19416 ranges: &[Range<Anchor>],
19417 color_fetcher: fn(&Theme) -> Hsla,
19418 cx: &mut Context<Self>,
19419 ) {
19420 self.background_highlights.insert(
19421 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19422 (color_fetcher, Arc::from(ranges)),
19423 );
19424 self.scrollbar_marker_state.dirty = true;
19425 cx.notify();
19426 }
19427
19428 pub fn clear_background_highlights<T: 'static>(
19429 &mut self,
19430 cx: &mut Context<Self>,
19431 ) -> Option<BackgroundHighlight> {
19432 let text_highlights = self
19433 .background_highlights
19434 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19435 if !text_highlights.1.is_empty() {
19436 self.scrollbar_marker_state.dirty = true;
19437 cx.notify();
19438 }
19439 Some(text_highlights)
19440 }
19441
19442 pub fn highlight_gutter<T: 'static>(
19443 &mut self,
19444 ranges: impl Into<Vec<Range<Anchor>>>,
19445 color_fetcher: fn(&App) -> Hsla,
19446 cx: &mut Context<Self>,
19447 ) {
19448 self.gutter_highlights
19449 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19450 cx.notify();
19451 }
19452
19453 pub fn clear_gutter_highlights<T: 'static>(
19454 &mut self,
19455 cx: &mut Context<Self>,
19456 ) -> Option<GutterHighlight> {
19457 cx.notify();
19458 self.gutter_highlights.remove(&TypeId::of::<T>())
19459 }
19460
19461 pub fn insert_gutter_highlight<T: 'static>(
19462 &mut self,
19463 range: Range<Anchor>,
19464 color_fetcher: fn(&App) -> Hsla,
19465 cx: &mut Context<Self>,
19466 ) {
19467 let snapshot = self.buffer().read(cx).snapshot(cx);
19468 let mut highlights = self
19469 .gutter_highlights
19470 .remove(&TypeId::of::<T>())
19471 .map(|(_, highlights)| highlights)
19472 .unwrap_or_default();
19473 let ix = highlights.binary_search_by(|highlight| {
19474 Ordering::Equal
19475 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19476 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19477 });
19478 if let Err(ix) = ix {
19479 highlights.insert(ix, range);
19480 }
19481 self.gutter_highlights
19482 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19483 }
19484
19485 pub fn remove_gutter_highlights<T: 'static>(
19486 &mut self,
19487 ranges_to_remove: Vec<Range<Anchor>>,
19488 cx: &mut Context<Self>,
19489 ) {
19490 let snapshot = self.buffer().read(cx).snapshot(cx);
19491 let Some((color_fetcher, mut gutter_highlights)) =
19492 self.gutter_highlights.remove(&TypeId::of::<T>())
19493 else {
19494 return;
19495 };
19496 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19497 gutter_highlights.retain(|highlight| {
19498 while let Some(range_to_remove) = ranges_to_remove.peek() {
19499 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19500 Ordering::Less | Ordering::Equal => {
19501 ranges_to_remove.next();
19502 }
19503 Ordering::Greater => {
19504 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19505 Ordering::Less | Ordering::Equal => {
19506 return false;
19507 }
19508 Ordering::Greater => break,
19509 }
19510 }
19511 }
19512 }
19513
19514 true
19515 });
19516 self.gutter_highlights
19517 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19518 }
19519
19520 #[cfg(feature = "test-support")]
19521 pub fn all_text_highlights(
19522 &self,
19523 window: &mut Window,
19524 cx: &mut Context<Self>,
19525 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19526 let snapshot = self.snapshot(window, cx);
19527 self.display_map.update(cx, |display_map, _| {
19528 display_map
19529 .all_text_highlights()
19530 .map(|highlight| {
19531 let (style, ranges) = highlight.as_ref();
19532 (
19533 *style,
19534 ranges
19535 .iter()
19536 .map(|range| range.clone().to_display_points(&snapshot))
19537 .collect(),
19538 )
19539 })
19540 .collect()
19541 })
19542 }
19543
19544 #[cfg(feature = "test-support")]
19545 pub fn all_text_background_highlights(
19546 &self,
19547 window: &mut Window,
19548 cx: &mut Context<Self>,
19549 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19550 let snapshot = self.snapshot(window, cx);
19551 let buffer = &snapshot.buffer_snapshot;
19552 let start = buffer.anchor_before(0);
19553 let end = buffer.anchor_after(buffer.len());
19554 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19555 }
19556
19557 #[cfg(feature = "test-support")]
19558 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19559 let snapshot = self.buffer().read(cx).snapshot(cx);
19560
19561 let highlights = self
19562 .background_highlights
19563 .get(&HighlightKey::Type(TypeId::of::<
19564 items::BufferSearchHighlights,
19565 >()));
19566
19567 if let Some((_color, ranges)) = highlights {
19568 ranges
19569 .iter()
19570 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19571 .collect_vec()
19572 } else {
19573 vec![]
19574 }
19575 }
19576
19577 fn document_highlights_for_position<'a>(
19578 &'a self,
19579 position: Anchor,
19580 buffer: &'a MultiBufferSnapshot,
19581 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19582 let read_highlights = self
19583 .background_highlights
19584 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19585 .map(|h| &h.1);
19586 let write_highlights = self
19587 .background_highlights
19588 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19589 .map(|h| &h.1);
19590 let left_position = position.bias_left(buffer);
19591 let right_position = position.bias_right(buffer);
19592 read_highlights
19593 .into_iter()
19594 .chain(write_highlights)
19595 .flat_map(move |ranges| {
19596 let start_ix = match ranges.binary_search_by(|probe| {
19597 let cmp = probe.end.cmp(&left_position, buffer);
19598 if cmp.is_ge() {
19599 Ordering::Greater
19600 } else {
19601 Ordering::Less
19602 }
19603 }) {
19604 Ok(i) | Err(i) => i,
19605 };
19606
19607 ranges[start_ix..]
19608 .iter()
19609 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19610 })
19611 }
19612
19613 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19614 self.background_highlights
19615 .get(&HighlightKey::Type(TypeId::of::<T>()))
19616 .map_or(false, |(_, highlights)| !highlights.is_empty())
19617 }
19618
19619 pub fn background_highlights_in_range(
19620 &self,
19621 search_range: Range<Anchor>,
19622 display_snapshot: &DisplaySnapshot,
19623 theme: &Theme,
19624 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19625 let mut results = Vec::new();
19626 for (color_fetcher, ranges) in self.background_highlights.values() {
19627 let color = color_fetcher(theme);
19628 let start_ix = match ranges.binary_search_by(|probe| {
19629 let cmp = probe
19630 .end
19631 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19632 if cmp.is_gt() {
19633 Ordering::Greater
19634 } else {
19635 Ordering::Less
19636 }
19637 }) {
19638 Ok(i) | Err(i) => i,
19639 };
19640 for range in &ranges[start_ix..] {
19641 if range
19642 .start
19643 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19644 .is_ge()
19645 {
19646 break;
19647 }
19648
19649 let start = range.start.to_display_point(display_snapshot);
19650 let end = range.end.to_display_point(display_snapshot);
19651 results.push((start..end, color))
19652 }
19653 }
19654 results
19655 }
19656
19657 pub fn background_highlight_row_ranges<T: 'static>(
19658 &self,
19659 search_range: Range<Anchor>,
19660 display_snapshot: &DisplaySnapshot,
19661 count: usize,
19662 ) -> Vec<RangeInclusive<DisplayPoint>> {
19663 let mut results = Vec::new();
19664 let Some((_, ranges)) = self
19665 .background_highlights
19666 .get(&HighlightKey::Type(TypeId::of::<T>()))
19667 else {
19668 return vec![];
19669 };
19670
19671 let start_ix = match ranges.binary_search_by(|probe| {
19672 let cmp = probe
19673 .end
19674 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19675 if cmp.is_gt() {
19676 Ordering::Greater
19677 } else {
19678 Ordering::Less
19679 }
19680 }) {
19681 Ok(i) | Err(i) => i,
19682 };
19683 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19684 if let (Some(start_display), Some(end_display)) = (start, end) {
19685 results.push(
19686 start_display.to_display_point(display_snapshot)
19687 ..=end_display.to_display_point(display_snapshot),
19688 );
19689 }
19690 };
19691 let mut start_row: Option<Point> = None;
19692 let mut end_row: Option<Point> = None;
19693 if ranges.len() > count {
19694 return Vec::new();
19695 }
19696 for range in &ranges[start_ix..] {
19697 if range
19698 .start
19699 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19700 .is_ge()
19701 {
19702 break;
19703 }
19704 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19705 if let Some(current_row) = &end_row {
19706 if end.row == current_row.row {
19707 continue;
19708 }
19709 }
19710 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19711 if start_row.is_none() {
19712 assert_eq!(end_row, None);
19713 start_row = Some(start);
19714 end_row = Some(end);
19715 continue;
19716 }
19717 if let Some(current_end) = end_row.as_mut() {
19718 if start.row > current_end.row + 1 {
19719 push_region(start_row, end_row);
19720 start_row = Some(start);
19721 end_row = Some(end);
19722 } else {
19723 // Merge two hunks.
19724 *current_end = end;
19725 }
19726 } else {
19727 unreachable!();
19728 }
19729 }
19730 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19731 push_region(start_row, end_row);
19732 results
19733 }
19734
19735 pub fn gutter_highlights_in_range(
19736 &self,
19737 search_range: Range<Anchor>,
19738 display_snapshot: &DisplaySnapshot,
19739 cx: &App,
19740 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19741 let mut results = Vec::new();
19742 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19743 let color = color_fetcher(cx);
19744 let start_ix = match ranges.binary_search_by(|probe| {
19745 let cmp = probe
19746 .end
19747 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19748 if cmp.is_gt() {
19749 Ordering::Greater
19750 } else {
19751 Ordering::Less
19752 }
19753 }) {
19754 Ok(i) | Err(i) => i,
19755 };
19756 for range in &ranges[start_ix..] {
19757 if range
19758 .start
19759 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19760 .is_ge()
19761 {
19762 break;
19763 }
19764
19765 let start = range.start.to_display_point(display_snapshot);
19766 let end = range.end.to_display_point(display_snapshot);
19767 results.push((start..end, color))
19768 }
19769 }
19770 results
19771 }
19772
19773 /// Get the text ranges corresponding to the redaction query
19774 pub fn redacted_ranges(
19775 &self,
19776 search_range: Range<Anchor>,
19777 display_snapshot: &DisplaySnapshot,
19778 cx: &App,
19779 ) -> Vec<Range<DisplayPoint>> {
19780 display_snapshot
19781 .buffer_snapshot
19782 .redacted_ranges(search_range, |file| {
19783 if let Some(file) = file {
19784 file.is_private()
19785 && EditorSettings::get(
19786 Some(SettingsLocation {
19787 worktree_id: file.worktree_id(cx),
19788 path: file.path().as_ref(),
19789 }),
19790 cx,
19791 )
19792 .redact_private_values
19793 } else {
19794 false
19795 }
19796 })
19797 .map(|range| {
19798 range.start.to_display_point(display_snapshot)
19799 ..range.end.to_display_point(display_snapshot)
19800 })
19801 .collect()
19802 }
19803
19804 pub fn highlight_text_key<T: 'static>(
19805 &mut self,
19806 key: usize,
19807 ranges: Vec<Range<Anchor>>,
19808 style: HighlightStyle,
19809 cx: &mut Context<Self>,
19810 ) {
19811 self.display_map.update(cx, |map, _| {
19812 map.highlight_text(
19813 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19814 ranges,
19815 style,
19816 );
19817 });
19818 cx.notify();
19819 }
19820
19821 pub fn highlight_text<T: 'static>(
19822 &mut self,
19823 ranges: Vec<Range<Anchor>>,
19824 style: HighlightStyle,
19825 cx: &mut Context<Self>,
19826 ) {
19827 self.display_map.update(cx, |map, _| {
19828 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19829 });
19830 cx.notify();
19831 }
19832
19833 pub(crate) fn highlight_inlays<T: 'static>(
19834 &mut self,
19835 highlights: Vec<InlayHighlight>,
19836 style: HighlightStyle,
19837 cx: &mut Context<Self>,
19838 ) {
19839 self.display_map.update(cx, |map, _| {
19840 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19841 });
19842 cx.notify();
19843 }
19844
19845 pub fn text_highlights<'a, T: 'static>(
19846 &'a self,
19847 cx: &'a App,
19848 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19849 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19850 }
19851
19852 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19853 let cleared = self
19854 .display_map
19855 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19856 if cleared {
19857 cx.notify();
19858 }
19859 }
19860
19861 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19862 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19863 && self.focus_handle.is_focused(window)
19864 }
19865
19866 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19867 self.show_cursor_when_unfocused = is_enabled;
19868 cx.notify();
19869 }
19870
19871 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19872 cx.notify();
19873 }
19874
19875 fn on_debug_session_event(
19876 &mut self,
19877 _session: Entity<Session>,
19878 event: &SessionEvent,
19879 cx: &mut Context<Self>,
19880 ) {
19881 match event {
19882 SessionEvent::InvalidateInlineValue => {
19883 self.refresh_inline_values(cx);
19884 }
19885 _ => {}
19886 }
19887 }
19888
19889 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19890 let Some(project) = self.project.clone() else {
19891 return;
19892 };
19893
19894 if !self.inline_value_cache.enabled {
19895 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19896 self.splice_inlays(&inlays, Vec::new(), cx);
19897 return;
19898 }
19899
19900 let current_execution_position = self
19901 .highlighted_rows
19902 .get(&TypeId::of::<ActiveDebugLine>())
19903 .and_then(|lines| lines.last().map(|line| line.range.end));
19904
19905 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19906 let inline_values = editor
19907 .update(cx, |editor, cx| {
19908 let Some(current_execution_position) = current_execution_position else {
19909 return Some(Task::ready(Ok(Vec::new())));
19910 };
19911
19912 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19913 let snapshot = buffer.snapshot(cx);
19914
19915 let excerpt = snapshot.excerpt_containing(
19916 current_execution_position..current_execution_position,
19917 )?;
19918
19919 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19920 })?;
19921
19922 let range =
19923 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19924
19925 project.inline_values(buffer, range, cx)
19926 })
19927 .ok()
19928 .flatten()?
19929 .await
19930 .context("refreshing debugger inlays")
19931 .log_err()?;
19932
19933 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19934
19935 for (buffer_id, inline_value) in inline_values
19936 .into_iter()
19937 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19938 {
19939 buffer_inline_values
19940 .entry(buffer_id)
19941 .or_default()
19942 .push(inline_value);
19943 }
19944
19945 editor
19946 .update(cx, |editor, cx| {
19947 let snapshot = editor.buffer.read(cx).snapshot(cx);
19948 let mut new_inlays = Vec::default();
19949
19950 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19951 let buffer_id = buffer_snapshot.remote_id();
19952 buffer_inline_values
19953 .get(&buffer_id)
19954 .into_iter()
19955 .flatten()
19956 .for_each(|hint| {
19957 let inlay = Inlay::debugger(
19958 post_inc(&mut editor.next_inlay_id),
19959 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19960 hint.text(),
19961 );
19962 if !inlay.text.chars().contains(&'\n') {
19963 new_inlays.push(inlay);
19964 }
19965 });
19966 }
19967
19968 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19969 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19970
19971 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19972 })
19973 .ok()?;
19974 Some(())
19975 });
19976 }
19977
19978 fn on_buffer_event(
19979 &mut self,
19980 multibuffer: &Entity<MultiBuffer>,
19981 event: &multi_buffer::Event,
19982 window: &mut Window,
19983 cx: &mut Context<Self>,
19984 ) {
19985 match event {
19986 multi_buffer::Event::Edited {
19987 singleton_buffer_edited,
19988 edited_buffer,
19989 } => {
19990 self.scrollbar_marker_state.dirty = true;
19991 self.active_indent_guides_state.dirty = true;
19992 self.refresh_active_diagnostics(cx);
19993 self.refresh_code_actions(window, cx);
19994 self.refresh_selected_text_highlights(true, window, cx);
19995 self.refresh_single_line_folds(window, cx);
19996 refresh_matching_bracket_highlights(self, window, cx);
19997 if self.has_active_edit_prediction() {
19998 self.update_visible_edit_prediction(window, cx);
19999 }
20000 if let Some(project) = self.project.as_ref() {
20001 if let Some(edited_buffer) = edited_buffer {
20002 project.update(cx, |project, cx| {
20003 self.registered_buffers
20004 .entry(edited_buffer.read(cx).remote_id())
20005 .or_insert_with(|| {
20006 project
20007 .register_buffer_with_language_servers(&edited_buffer, cx)
20008 });
20009 });
20010 }
20011 }
20012 cx.emit(EditorEvent::BufferEdited);
20013 cx.emit(SearchEvent::MatchesInvalidated);
20014
20015 if let Some(buffer) = edited_buffer {
20016 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20017 }
20018
20019 if *singleton_buffer_edited {
20020 if let Some(buffer) = edited_buffer {
20021 if buffer.read(cx).file().is_none() {
20022 cx.emit(EditorEvent::TitleChanged);
20023 }
20024 }
20025 if let Some(project) = &self.project {
20026 #[allow(clippy::mutable_key_type)]
20027 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20028 multibuffer
20029 .all_buffers()
20030 .into_iter()
20031 .filter_map(|buffer| {
20032 buffer.update(cx, |buffer, cx| {
20033 let language = buffer.language()?;
20034 let should_discard = project.update(cx, |project, cx| {
20035 project.is_local()
20036 && !project.has_language_servers_for(buffer, cx)
20037 });
20038 should_discard.not().then_some(language.clone())
20039 })
20040 })
20041 .collect::<HashSet<_>>()
20042 });
20043 if !languages_affected.is_empty() {
20044 self.refresh_inlay_hints(
20045 InlayHintRefreshReason::BufferEdited(languages_affected),
20046 cx,
20047 );
20048 }
20049 }
20050 }
20051
20052 let Some(project) = &self.project else { return };
20053 let (telemetry, is_via_ssh) = {
20054 let project = project.read(cx);
20055 let telemetry = project.client().telemetry().clone();
20056 let is_via_ssh = project.is_via_ssh();
20057 (telemetry, is_via_ssh)
20058 };
20059 refresh_linked_ranges(self, window, cx);
20060 telemetry.log_edit_event("editor", is_via_ssh);
20061 }
20062 multi_buffer::Event::ExcerptsAdded {
20063 buffer,
20064 predecessor,
20065 excerpts,
20066 } => {
20067 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20068 let buffer_id = buffer.read(cx).remote_id();
20069 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
20070 if let Some(project) = &self.project {
20071 update_uncommitted_diff_for_buffer(
20072 cx.entity(),
20073 project,
20074 [buffer.clone()],
20075 self.buffer.clone(),
20076 cx,
20077 )
20078 .detach();
20079 }
20080 }
20081 self.update_lsp_data(false, Some(buffer_id), window, cx);
20082 cx.emit(EditorEvent::ExcerptsAdded {
20083 buffer: buffer.clone(),
20084 predecessor: *predecessor,
20085 excerpts: excerpts.clone(),
20086 });
20087 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20088 }
20089 multi_buffer::Event::ExcerptsRemoved {
20090 ids,
20091 removed_buffer_ids,
20092 } => {
20093 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20094 let buffer = self.buffer.read(cx);
20095 self.registered_buffers
20096 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20097 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20098 cx.emit(EditorEvent::ExcerptsRemoved {
20099 ids: ids.clone(),
20100 removed_buffer_ids: removed_buffer_ids.clone(),
20101 });
20102 }
20103 multi_buffer::Event::ExcerptsEdited {
20104 excerpt_ids,
20105 buffer_ids,
20106 } => {
20107 self.display_map.update(cx, |map, cx| {
20108 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20109 });
20110 cx.emit(EditorEvent::ExcerptsEdited {
20111 ids: excerpt_ids.clone(),
20112 });
20113 }
20114 multi_buffer::Event::ExcerptsExpanded { ids } => {
20115 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20116 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20117 }
20118 multi_buffer::Event::Reparsed(buffer_id) => {
20119 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20120 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20121
20122 cx.emit(EditorEvent::Reparsed(*buffer_id));
20123 }
20124 multi_buffer::Event::DiffHunksToggled => {
20125 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20126 }
20127 multi_buffer::Event::LanguageChanged(buffer_id) => {
20128 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20129 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20130 cx.emit(EditorEvent::Reparsed(*buffer_id));
20131 cx.notify();
20132 }
20133 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20134 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20135 multi_buffer::Event::FileHandleChanged
20136 | multi_buffer::Event::Reloaded
20137 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20138 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20139 multi_buffer::Event::DiagnosticsUpdated => {
20140 self.update_diagnostics_state(window, cx);
20141 }
20142 _ => {}
20143 };
20144 }
20145
20146 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20147 if !self.diagnostics_enabled() {
20148 return;
20149 }
20150 self.refresh_active_diagnostics(cx);
20151 self.refresh_inline_diagnostics(true, window, cx);
20152 self.scrollbar_marker_state.dirty = true;
20153 cx.notify();
20154 }
20155
20156 pub fn start_temporary_diff_override(&mut self) {
20157 self.load_diff_task.take();
20158 self.temporary_diff_override = true;
20159 }
20160
20161 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20162 self.temporary_diff_override = false;
20163 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20164 self.buffer.update(cx, |buffer, cx| {
20165 buffer.set_all_diff_hunks_collapsed(cx);
20166 });
20167
20168 if let Some(project) = self.project.clone() {
20169 self.load_diff_task = Some(
20170 update_uncommitted_diff_for_buffer(
20171 cx.entity(),
20172 &project,
20173 self.buffer.read(cx).all_buffers(),
20174 self.buffer.clone(),
20175 cx,
20176 )
20177 .shared(),
20178 );
20179 }
20180 }
20181
20182 fn on_display_map_changed(
20183 &mut self,
20184 _: Entity<DisplayMap>,
20185 _: &mut Window,
20186 cx: &mut Context<Self>,
20187 ) {
20188 cx.notify();
20189 }
20190
20191 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20192 if self.diagnostics_enabled() {
20193 let new_severity = EditorSettings::get_global(cx)
20194 .diagnostics_max_severity
20195 .unwrap_or(DiagnosticSeverity::Hint);
20196 self.set_max_diagnostics_severity(new_severity, cx);
20197 }
20198 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20199 self.update_edit_prediction_settings(cx);
20200 self.refresh_edit_prediction(true, false, window, cx);
20201 self.refresh_inline_values(cx);
20202 self.refresh_inlay_hints(
20203 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20204 self.selections.newest_anchor().head(),
20205 &self.buffer.read(cx).snapshot(cx),
20206 cx,
20207 )),
20208 cx,
20209 );
20210
20211 let old_cursor_shape = self.cursor_shape;
20212
20213 {
20214 let editor_settings = EditorSettings::get_global(cx);
20215 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20216 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20217 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20218 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20219 }
20220
20221 if old_cursor_shape != self.cursor_shape {
20222 cx.emit(EditorEvent::CursorShapeChanged);
20223 }
20224
20225 let project_settings = ProjectSettings::get_global(cx);
20226 self.serialize_dirty_buffers =
20227 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20228
20229 if self.mode.is_full() {
20230 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20231 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20232 if self.show_inline_diagnostics != show_inline_diagnostics {
20233 self.show_inline_diagnostics = show_inline_diagnostics;
20234 self.refresh_inline_diagnostics(false, window, cx);
20235 }
20236
20237 if self.git_blame_inline_enabled != inline_blame_enabled {
20238 self.toggle_git_blame_inline_internal(false, window, cx);
20239 }
20240
20241 let minimap_settings = EditorSettings::get_global(cx).minimap;
20242 if self.minimap_visibility != MinimapVisibility::Disabled {
20243 if self.minimap_visibility.settings_visibility()
20244 != minimap_settings.minimap_enabled()
20245 {
20246 self.set_minimap_visibility(
20247 MinimapVisibility::for_mode(self.mode(), cx),
20248 window,
20249 cx,
20250 );
20251 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20252 minimap_entity.update(cx, |minimap_editor, cx| {
20253 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20254 })
20255 }
20256 }
20257 }
20258
20259 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20260 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20261 }) {
20262 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20263 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20264 }
20265 self.refresh_colors(false, None, window, cx);
20266 }
20267
20268 cx.notify();
20269 }
20270
20271 pub fn set_searchable(&mut self, searchable: bool) {
20272 self.searchable = searchable;
20273 }
20274
20275 pub fn searchable(&self) -> bool {
20276 self.searchable
20277 }
20278
20279 fn open_proposed_changes_editor(
20280 &mut self,
20281 _: &OpenProposedChangesEditor,
20282 window: &mut Window,
20283 cx: &mut Context<Self>,
20284 ) {
20285 let Some(workspace) = self.workspace() else {
20286 cx.propagate();
20287 return;
20288 };
20289
20290 let selections = self.selections.all::<usize>(cx);
20291 let multi_buffer = self.buffer.read(cx);
20292 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20293 let mut new_selections_by_buffer = HashMap::default();
20294 for selection in selections {
20295 for (buffer, range, _) in
20296 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20297 {
20298 let mut range = range.to_point(buffer);
20299 range.start.column = 0;
20300 range.end.column = buffer.line_len(range.end.row);
20301 new_selections_by_buffer
20302 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20303 .or_insert(Vec::new())
20304 .push(range)
20305 }
20306 }
20307
20308 let proposed_changes_buffers = new_selections_by_buffer
20309 .into_iter()
20310 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20311 .collect::<Vec<_>>();
20312 let proposed_changes_editor = cx.new(|cx| {
20313 ProposedChangesEditor::new(
20314 "Proposed changes",
20315 proposed_changes_buffers,
20316 self.project.clone(),
20317 window,
20318 cx,
20319 )
20320 });
20321
20322 window.defer(cx, move |window, cx| {
20323 workspace.update(cx, |workspace, cx| {
20324 workspace.active_pane().update(cx, |pane, cx| {
20325 pane.add_item(
20326 Box::new(proposed_changes_editor),
20327 true,
20328 true,
20329 None,
20330 window,
20331 cx,
20332 );
20333 });
20334 });
20335 });
20336 }
20337
20338 pub fn open_excerpts_in_split(
20339 &mut self,
20340 _: &OpenExcerptsSplit,
20341 window: &mut Window,
20342 cx: &mut Context<Self>,
20343 ) {
20344 self.open_excerpts_common(None, true, window, cx)
20345 }
20346
20347 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20348 self.open_excerpts_common(None, false, window, cx)
20349 }
20350
20351 fn open_excerpts_common(
20352 &mut self,
20353 jump_data: Option<JumpData>,
20354 split: bool,
20355 window: &mut Window,
20356 cx: &mut Context<Self>,
20357 ) {
20358 let Some(workspace) = self.workspace() else {
20359 cx.propagate();
20360 return;
20361 };
20362
20363 if self.buffer.read(cx).is_singleton() {
20364 cx.propagate();
20365 return;
20366 }
20367
20368 let mut new_selections_by_buffer = HashMap::default();
20369 match &jump_data {
20370 Some(JumpData::MultiBufferPoint {
20371 excerpt_id,
20372 position,
20373 anchor,
20374 line_offset_from_top,
20375 }) => {
20376 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20377 if let Some(buffer) = multi_buffer_snapshot
20378 .buffer_id_for_excerpt(*excerpt_id)
20379 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20380 {
20381 let buffer_snapshot = buffer.read(cx).snapshot();
20382 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20383 language::ToPoint::to_point(anchor, &buffer_snapshot)
20384 } else {
20385 buffer_snapshot.clip_point(*position, Bias::Left)
20386 };
20387 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20388 new_selections_by_buffer.insert(
20389 buffer,
20390 (
20391 vec![jump_to_offset..jump_to_offset],
20392 Some(*line_offset_from_top),
20393 ),
20394 );
20395 }
20396 }
20397 Some(JumpData::MultiBufferRow {
20398 row,
20399 line_offset_from_top,
20400 }) => {
20401 let point = MultiBufferPoint::new(row.0, 0);
20402 if let Some((buffer, buffer_point, _)) =
20403 self.buffer.read(cx).point_to_buffer_point(point, cx)
20404 {
20405 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20406 new_selections_by_buffer
20407 .entry(buffer)
20408 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20409 .0
20410 .push(buffer_offset..buffer_offset)
20411 }
20412 }
20413 None => {
20414 let selections = self.selections.all::<usize>(cx);
20415 let multi_buffer = self.buffer.read(cx);
20416 for selection in selections {
20417 for (snapshot, range, _, anchor) in multi_buffer
20418 .snapshot(cx)
20419 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20420 {
20421 if let Some(anchor) = anchor {
20422 // selection is in a deleted hunk
20423 let Some(buffer_id) = anchor.buffer_id else {
20424 continue;
20425 };
20426 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20427 continue;
20428 };
20429 let offset = text::ToOffset::to_offset(
20430 &anchor.text_anchor,
20431 &buffer_handle.read(cx).snapshot(),
20432 );
20433 let range = offset..offset;
20434 new_selections_by_buffer
20435 .entry(buffer_handle)
20436 .or_insert((Vec::new(), None))
20437 .0
20438 .push(range)
20439 } else {
20440 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20441 else {
20442 continue;
20443 };
20444 new_selections_by_buffer
20445 .entry(buffer_handle)
20446 .or_insert((Vec::new(), None))
20447 .0
20448 .push(range)
20449 }
20450 }
20451 }
20452 }
20453 }
20454
20455 new_selections_by_buffer
20456 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20457
20458 if new_selections_by_buffer.is_empty() {
20459 return;
20460 }
20461
20462 // We defer the pane interaction because we ourselves are a workspace item
20463 // and activating a new item causes the pane to call a method on us reentrantly,
20464 // which panics if we're on the stack.
20465 window.defer(cx, move |window, cx| {
20466 workspace.update(cx, |workspace, cx| {
20467 let pane = if split {
20468 workspace.adjacent_pane(window, cx)
20469 } else {
20470 workspace.active_pane().clone()
20471 };
20472
20473 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20474 let editor = buffer
20475 .read(cx)
20476 .file()
20477 .is_none()
20478 .then(|| {
20479 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20480 // so `workspace.open_project_item` will never find them, always opening a new editor.
20481 // Instead, we try to activate the existing editor in the pane first.
20482 let (editor, pane_item_index) =
20483 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20484 let editor = item.downcast::<Editor>()?;
20485 let singleton_buffer =
20486 editor.read(cx).buffer().read(cx).as_singleton()?;
20487 if singleton_buffer == buffer {
20488 Some((editor, i))
20489 } else {
20490 None
20491 }
20492 })?;
20493 pane.update(cx, |pane, cx| {
20494 pane.activate_item(pane_item_index, true, true, window, cx)
20495 });
20496 Some(editor)
20497 })
20498 .flatten()
20499 .unwrap_or_else(|| {
20500 workspace.open_project_item::<Self>(
20501 pane.clone(),
20502 buffer,
20503 true,
20504 true,
20505 window,
20506 cx,
20507 )
20508 });
20509
20510 editor.update(cx, |editor, cx| {
20511 let autoscroll = match scroll_offset {
20512 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20513 None => Autoscroll::newest(),
20514 };
20515 let nav_history = editor.nav_history.take();
20516 editor.change_selections(
20517 SelectionEffects::scroll(autoscroll),
20518 window,
20519 cx,
20520 |s| {
20521 s.select_ranges(ranges);
20522 },
20523 );
20524 editor.nav_history = nav_history;
20525 });
20526 }
20527 })
20528 });
20529 }
20530
20531 // For now, don't allow opening excerpts in buffers that aren't backed by
20532 // regular project files.
20533 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20534 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20535 }
20536
20537 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20538 let snapshot = self.buffer.read(cx).read(cx);
20539 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20540 Some(
20541 ranges
20542 .iter()
20543 .map(move |range| {
20544 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20545 })
20546 .collect(),
20547 )
20548 }
20549
20550 fn selection_replacement_ranges(
20551 &self,
20552 range: Range<OffsetUtf16>,
20553 cx: &mut App,
20554 ) -> Vec<Range<OffsetUtf16>> {
20555 let selections = self.selections.all::<OffsetUtf16>(cx);
20556 let newest_selection = selections
20557 .iter()
20558 .max_by_key(|selection| selection.id)
20559 .unwrap();
20560 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20561 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20562 let snapshot = self.buffer.read(cx).read(cx);
20563 selections
20564 .into_iter()
20565 .map(|mut selection| {
20566 selection.start.0 =
20567 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20568 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20569 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20570 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20571 })
20572 .collect()
20573 }
20574
20575 fn report_editor_event(
20576 &self,
20577 reported_event: ReportEditorEvent,
20578 file_extension: Option<String>,
20579 cx: &App,
20580 ) {
20581 if cfg!(any(test, feature = "test-support")) {
20582 return;
20583 }
20584
20585 let Some(project) = &self.project else { return };
20586
20587 // If None, we are in a file without an extension
20588 let file = self
20589 .buffer
20590 .read(cx)
20591 .as_singleton()
20592 .and_then(|b| b.read(cx).file());
20593 let file_extension = file_extension.or(file
20594 .as_ref()
20595 .and_then(|file| Path::new(file.file_name(cx)).extension())
20596 .and_then(|e| e.to_str())
20597 .map(|a| a.to_string()));
20598
20599 let vim_mode = vim_enabled(cx);
20600
20601 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20602 let copilot_enabled = edit_predictions_provider
20603 == language::language_settings::EditPredictionProvider::Copilot;
20604 let copilot_enabled_for_language = self
20605 .buffer
20606 .read(cx)
20607 .language_settings(cx)
20608 .show_edit_predictions;
20609
20610 let project = project.read(cx);
20611 let event_type = reported_event.event_type();
20612
20613 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20614 telemetry::event!(
20615 event_type,
20616 type = if auto_saved {"autosave"} else {"manual"},
20617 file_extension,
20618 vim_mode,
20619 copilot_enabled,
20620 copilot_enabled_for_language,
20621 edit_predictions_provider,
20622 is_via_ssh = project.is_via_ssh(),
20623 );
20624 } else {
20625 telemetry::event!(
20626 event_type,
20627 file_extension,
20628 vim_mode,
20629 copilot_enabled,
20630 copilot_enabled_for_language,
20631 edit_predictions_provider,
20632 is_via_ssh = project.is_via_ssh(),
20633 );
20634 };
20635 }
20636
20637 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20638 /// with each line being an array of {text, highlight} objects.
20639 fn copy_highlight_json(
20640 &mut self,
20641 _: &CopyHighlightJson,
20642 window: &mut Window,
20643 cx: &mut Context<Self>,
20644 ) {
20645 #[derive(Serialize)]
20646 struct Chunk<'a> {
20647 text: String,
20648 highlight: Option<&'a str>,
20649 }
20650
20651 let snapshot = self.buffer.read(cx).snapshot(cx);
20652 let range = self
20653 .selected_text_range(false, window, cx)
20654 .and_then(|selection| {
20655 if selection.range.is_empty() {
20656 None
20657 } else {
20658 Some(selection.range)
20659 }
20660 })
20661 .unwrap_or_else(|| 0..snapshot.len());
20662
20663 let chunks = snapshot.chunks(range, true);
20664 let mut lines = Vec::new();
20665 let mut line: VecDeque<Chunk> = VecDeque::new();
20666
20667 let Some(style) = self.style.as_ref() else {
20668 return;
20669 };
20670
20671 for chunk in chunks {
20672 let highlight = chunk
20673 .syntax_highlight_id
20674 .and_then(|id| id.name(&style.syntax));
20675 let mut chunk_lines = chunk.text.split('\n').peekable();
20676 while let Some(text) = chunk_lines.next() {
20677 let mut merged_with_last_token = false;
20678 if let Some(last_token) = line.back_mut() {
20679 if last_token.highlight == highlight {
20680 last_token.text.push_str(text);
20681 merged_with_last_token = true;
20682 }
20683 }
20684
20685 if !merged_with_last_token {
20686 line.push_back(Chunk {
20687 text: text.into(),
20688 highlight,
20689 });
20690 }
20691
20692 if chunk_lines.peek().is_some() {
20693 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20694 line.pop_front();
20695 }
20696 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20697 line.pop_back();
20698 }
20699
20700 lines.push(mem::take(&mut line));
20701 }
20702 }
20703 }
20704
20705 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20706 return;
20707 };
20708 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20709 }
20710
20711 pub fn open_context_menu(
20712 &mut self,
20713 _: &OpenContextMenu,
20714 window: &mut Window,
20715 cx: &mut Context<Self>,
20716 ) {
20717 self.request_autoscroll(Autoscroll::newest(), cx);
20718 let position = self.selections.newest_display(cx).start;
20719 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20720 }
20721
20722 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20723 &self.inlay_hint_cache
20724 }
20725
20726 pub fn replay_insert_event(
20727 &mut self,
20728 text: &str,
20729 relative_utf16_range: Option<Range<isize>>,
20730 window: &mut Window,
20731 cx: &mut Context<Self>,
20732 ) {
20733 if !self.input_enabled {
20734 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20735 return;
20736 }
20737 if let Some(relative_utf16_range) = relative_utf16_range {
20738 let selections = self.selections.all::<OffsetUtf16>(cx);
20739 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20740 let new_ranges = selections.into_iter().map(|range| {
20741 let start = OffsetUtf16(
20742 range
20743 .head()
20744 .0
20745 .saturating_add_signed(relative_utf16_range.start),
20746 );
20747 let end = OffsetUtf16(
20748 range
20749 .head()
20750 .0
20751 .saturating_add_signed(relative_utf16_range.end),
20752 );
20753 start..end
20754 });
20755 s.select_ranges(new_ranges);
20756 });
20757 }
20758
20759 self.handle_input(text, window, cx);
20760 }
20761
20762 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20763 let Some(provider) = self.semantics_provider.as_ref() else {
20764 return false;
20765 };
20766
20767 let mut supports = false;
20768 self.buffer().update(cx, |this, cx| {
20769 this.for_each_buffer(|buffer| {
20770 supports |= provider.supports_inlay_hints(buffer, cx);
20771 });
20772 });
20773
20774 supports
20775 }
20776
20777 pub fn is_focused(&self, window: &Window) -> bool {
20778 self.focus_handle.is_focused(window)
20779 }
20780
20781 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20782 cx.emit(EditorEvent::Focused);
20783
20784 if let Some(descendant) = self
20785 .last_focused_descendant
20786 .take()
20787 .and_then(|descendant| descendant.upgrade())
20788 {
20789 window.focus(&descendant);
20790 } else {
20791 if let Some(blame) = self.blame.as_ref() {
20792 blame.update(cx, GitBlame::focus)
20793 }
20794
20795 self.blink_manager.update(cx, BlinkManager::enable);
20796 self.show_cursor_names(window, cx);
20797 self.buffer.update(cx, |buffer, cx| {
20798 buffer.finalize_last_transaction(cx);
20799 if self.leader_id.is_none() {
20800 buffer.set_active_selections(
20801 &self.selections.disjoint_anchors(),
20802 self.selections.line_mode,
20803 self.cursor_shape,
20804 cx,
20805 );
20806 }
20807 });
20808 }
20809 }
20810
20811 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20812 cx.emit(EditorEvent::FocusedIn)
20813 }
20814
20815 fn handle_focus_out(
20816 &mut self,
20817 event: FocusOutEvent,
20818 _window: &mut Window,
20819 cx: &mut Context<Self>,
20820 ) {
20821 if event.blurred != self.focus_handle {
20822 self.last_focused_descendant = Some(event.blurred);
20823 }
20824 self.selection_drag_state = SelectionDragState::None;
20825 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20826 }
20827
20828 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20829 self.blink_manager.update(cx, BlinkManager::disable);
20830 self.buffer
20831 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20832
20833 if let Some(blame) = self.blame.as_ref() {
20834 blame.update(cx, GitBlame::blur)
20835 }
20836 if !self.hover_state.focused(window, cx) {
20837 hide_hover(self, cx);
20838 }
20839 if !self
20840 .context_menu
20841 .borrow()
20842 .as_ref()
20843 .is_some_and(|context_menu| context_menu.focused(window, cx))
20844 {
20845 self.hide_context_menu(window, cx);
20846 }
20847 self.discard_edit_prediction(false, cx);
20848 cx.emit(EditorEvent::Blurred);
20849 cx.notify();
20850 }
20851
20852 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20853 let mut pending: String = window
20854 .pending_input_keystrokes()
20855 .into_iter()
20856 .flatten()
20857 .filter_map(|keystroke| {
20858 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20859 keystroke.key_char.clone()
20860 } else {
20861 None
20862 }
20863 })
20864 .collect();
20865
20866 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20867 pending = "".to_string();
20868 }
20869
20870 let existing_pending = self
20871 .text_highlights::<PendingInput>(cx)
20872 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20873 if existing_pending.is_none() && pending.is_empty() {
20874 return;
20875 }
20876 let transaction =
20877 self.transact(window, cx, |this, window, cx| {
20878 let selections = this.selections.all::<usize>(cx);
20879 let edits = selections
20880 .iter()
20881 .map(|selection| (selection.end..selection.end, pending.clone()));
20882 this.edit(edits, cx);
20883 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20884 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20885 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20886 }));
20887 });
20888 if let Some(existing_ranges) = existing_pending {
20889 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20890 this.edit(edits, cx);
20891 }
20892 });
20893
20894 let snapshot = self.snapshot(window, cx);
20895 let ranges = self
20896 .selections
20897 .all::<usize>(cx)
20898 .into_iter()
20899 .map(|selection| {
20900 snapshot.buffer_snapshot.anchor_after(selection.end)
20901 ..snapshot
20902 .buffer_snapshot
20903 .anchor_before(selection.end + pending.len())
20904 })
20905 .collect();
20906
20907 if pending.is_empty() {
20908 self.clear_highlights::<PendingInput>(cx);
20909 } else {
20910 self.highlight_text::<PendingInput>(
20911 ranges,
20912 HighlightStyle {
20913 underline: Some(UnderlineStyle {
20914 thickness: px(1.),
20915 color: None,
20916 wavy: false,
20917 }),
20918 ..Default::default()
20919 },
20920 cx,
20921 );
20922 }
20923
20924 self.ime_transaction = self.ime_transaction.or(transaction);
20925 if let Some(transaction) = self.ime_transaction {
20926 self.buffer.update(cx, |buffer, cx| {
20927 buffer.group_until_transaction(transaction, cx);
20928 });
20929 }
20930
20931 if self.text_highlights::<PendingInput>(cx).is_none() {
20932 self.ime_transaction.take();
20933 }
20934 }
20935
20936 pub fn register_action_renderer(
20937 &mut self,
20938 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20939 ) -> Subscription {
20940 let id = self.next_editor_action_id.post_inc();
20941 self.editor_actions
20942 .borrow_mut()
20943 .insert(id, Box::new(listener));
20944
20945 let editor_actions = self.editor_actions.clone();
20946 Subscription::new(move || {
20947 editor_actions.borrow_mut().remove(&id);
20948 })
20949 }
20950
20951 pub fn register_action<A: Action>(
20952 &mut self,
20953 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20954 ) -> Subscription {
20955 let id = self.next_editor_action_id.post_inc();
20956 let listener = Arc::new(listener);
20957 self.editor_actions.borrow_mut().insert(
20958 id,
20959 Box::new(move |_, window, _| {
20960 let listener = listener.clone();
20961 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20962 let action = action.downcast_ref().unwrap();
20963 if phase == DispatchPhase::Bubble {
20964 listener(action, window, cx)
20965 }
20966 })
20967 }),
20968 );
20969
20970 let editor_actions = self.editor_actions.clone();
20971 Subscription::new(move || {
20972 editor_actions.borrow_mut().remove(&id);
20973 })
20974 }
20975
20976 pub fn file_header_size(&self) -> u32 {
20977 FILE_HEADER_HEIGHT
20978 }
20979
20980 pub fn restore(
20981 &mut self,
20982 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20983 window: &mut Window,
20984 cx: &mut Context<Self>,
20985 ) {
20986 let workspace = self.workspace();
20987 let project = self.project.as_ref();
20988 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20989 let mut tasks = Vec::new();
20990 for (buffer_id, changes) in revert_changes {
20991 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20992 buffer.update(cx, |buffer, cx| {
20993 buffer.edit(
20994 changes
20995 .into_iter()
20996 .map(|(range, text)| (range, text.to_string())),
20997 None,
20998 cx,
20999 );
21000 });
21001
21002 if let Some(project) =
21003 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21004 {
21005 project.update(cx, |project, cx| {
21006 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21007 })
21008 }
21009 }
21010 }
21011 tasks
21012 });
21013 cx.spawn_in(window, async move |_, cx| {
21014 for (buffer, task) in save_tasks {
21015 let result = task.await;
21016 if result.is_err() {
21017 let Some(path) = buffer
21018 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21019 .ok()
21020 else {
21021 continue;
21022 };
21023 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21024 let Some(task) = cx
21025 .update_window_entity(&workspace, |workspace, window, cx| {
21026 workspace
21027 .open_path_preview(path, None, false, false, false, window, cx)
21028 })
21029 .ok()
21030 else {
21031 continue;
21032 };
21033 task.await.log_err();
21034 }
21035 }
21036 }
21037 })
21038 .detach();
21039 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21040 selections.refresh()
21041 });
21042 }
21043
21044 pub fn to_pixel_point(
21045 &self,
21046 source: multi_buffer::Anchor,
21047 editor_snapshot: &EditorSnapshot,
21048 window: &mut Window,
21049 ) -> Option<gpui::Point<Pixels>> {
21050 let source_point = source.to_display_point(editor_snapshot);
21051 self.display_to_pixel_point(source_point, editor_snapshot, window)
21052 }
21053
21054 pub fn display_to_pixel_point(
21055 &self,
21056 source: DisplayPoint,
21057 editor_snapshot: &EditorSnapshot,
21058 window: &mut Window,
21059 ) -> Option<gpui::Point<Pixels>> {
21060 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21061 let text_layout_details = self.text_layout_details(window);
21062 let scroll_top = text_layout_details
21063 .scroll_anchor
21064 .scroll_position(editor_snapshot)
21065 .y;
21066
21067 if source.row().as_f32() < scroll_top.floor() {
21068 return None;
21069 }
21070 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21071 let source_y = line_height * (source.row().as_f32() - scroll_top);
21072 Some(gpui::Point::new(source_x, source_y))
21073 }
21074
21075 pub fn has_visible_completions_menu(&self) -> bool {
21076 !self.edit_prediction_preview_is_active()
21077 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
21078 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21079 })
21080 }
21081
21082 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21083 if self.mode.is_minimap() {
21084 return;
21085 }
21086 self.addons
21087 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21088 }
21089
21090 pub fn unregister_addon<T: Addon>(&mut self) {
21091 self.addons.remove(&std::any::TypeId::of::<T>());
21092 }
21093
21094 pub fn addon<T: Addon>(&self) -> Option<&T> {
21095 let type_id = std::any::TypeId::of::<T>();
21096 self.addons
21097 .get(&type_id)
21098 .and_then(|item| item.to_any().downcast_ref::<T>())
21099 }
21100
21101 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21102 let type_id = std::any::TypeId::of::<T>();
21103 self.addons
21104 .get_mut(&type_id)
21105 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21106 }
21107
21108 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21109 let text_layout_details = self.text_layout_details(window);
21110 let style = &text_layout_details.editor_style;
21111 let font_id = window.text_system().resolve_font(&style.text.font());
21112 let font_size = style.text.font_size.to_pixels(window.rem_size());
21113 let line_height = style.text.line_height_in_pixels(window.rem_size());
21114 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21115 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21116
21117 CharacterDimensions {
21118 em_width,
21119 em_advance,
21120 line_height,
21121 }
21122 }
21123
21124 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21125 self.load_diff_task.clone()
21126 }
21127
21128 fn read_metadata_from_db(
21129 &mut self,
21130 item_id: u64,
21131 workspace_id: WorkspaceId,
21132 window: &mut Window,
21133 cx: &mut Context<Editor>,
21134 ) {
21135 if self.is_singleton(cx)
21136 && !self.mode.is_minimap()
21137 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21138 {
21139 let buffer_snapshot = OnceCell::new();
21140
21141 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
21142 if !folds.is_empty() {
21143 let snapshot =
21144 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21145 self.fold_ranges(
21146 folds
21147 .into_iter()
21148 .map(|(start, end)| {
21149 snapshot.clip_offset(start, Bias::Left)
21150 ..snapshot.clip_offset(end, Bias::Right)
21151 })
21152 .collect(),
21153 false,
21154 window,
21155 cx,
21156 );
21157 }
21158 }
21159
21160 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21161 if !selections.is_empty() {
21162 let snapshot =
21163 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21164 // skip adding the initial selection to selection history
21165 self.selection_history.mode = SelectionHistoryMode::Skipping;
21166 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21167 s.select_ranges(selections.into_iter().map(|(start, end)| {
21168 snapshot.clip_offset(start, Bias::Left)
21169 ..snapshot.clip_offset(end, Bias::Right)
21170 }));
21171 });
21172 self.selection_history.mode = SelectionHistoryMode::Normal;
21173 }
21174 };
21175 }
21176
21177 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21178 }
21179
21180 fn update_lsp_data(
21181 &mut self,
21182 ignore_cache: bool,
21183 for_buffer: Option<BufferId>,
21184 window: &mut Window,
21185 cx: &mut Context<'_, Self>,
21186 ) {
21187 self.pull_diagnostics(for_buffer, window, cx);
21188 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21189 }
21190}
21191
21192fn vim_enabled(cx: &App) -> bool {
21193 cx.global::<SettingsStore>()
21194 .raw_user_settings()
21195 .get("vim_mode")
21196 == Some(&serde_json::Value::Bool(true))
21197}
21198
21199fn process_completion_for_edit(
21200 completion: &Completion,
21201 intent: CompletionIntent,
21202 buffer: &Entity<Buffer>,
21203 cursor_position: &text::Anchor,
21204 cx: &mut Context<Editor>,
21205) -> CompletionEdit {
21206 let buffer = buffer.read(cx);
21207 let buffer_snapshot = buffer.snapshot();
21208 let (snippet, new_text) = if completion.is_snippet() {
21209 // Workaround for typescript language server issues so that methods don't expand within
21210 // strings and functions with type expressions. The previous point is used because the query
21211 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21212 let mut snippet_source = completion.new_text.clone();
21213 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21214 previous_point.column = previous_point.column.saturating_sub(1);
21215 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21216 if scope.prefers_label_for_snippet_in_completion() {
21217 if let Some(label) = completion.label() {
21218 if matches!(
21219 completion.kind(),
21220 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21221 ) {
21222 snippet_source = label;
21223 }
21224 }
21225 }
21226 }
21227 match Snippet::parse(&snippet_source).log_err() {
21228 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21229 None => (None, completion.new_text.clone()),
21230 }
21231 } else {
21232 (None, completion.new_text.clone())
21233 };
21234
21235 let mut range_to_replace = {
21236 let replace_range = &completion.replace_range;
21237 if let CompletionSource::Lsp {
21238 insert_range: Some(insert_range),
21239 ..
21240 } = &completion.source
21241 {
21242 debug_assert_eq!(
21243 insert_range.start, replace_range.start,
21244 "insert_range and replace_range should start at the same position"
21245 );
21246 debug_assert!(
21247 insert_range
21248 .start
21249 .cmp(&cursor_position, &buffer_snapshot)
21250 .is_le(),
21251 "insert_range should start before or at cursor position"
21252 );
21253 debug_assert!(
21254 replace_range
21255 .start
21256 .cmp(&cursor_position, &buffer_snapshot)
21257 .is_le(),
21258 "replace_range should start before or at cursor position"
21259 );
21260
21261 let should_replace = match intent {
21262 CompletionIntent::CompleteWithInsert => false,
21263 CompletionIntent::CompleteWithReplace => true,
21264 CompletionIntent::Complete | CompletionIntent::Compose => {
21265 let insert_mode =
21266 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21267 .completions
21268 .lsp_insert_mode;
21269 match insert_mode {
21270 LspInsertMode::Insert => false,
21271 LspInsertMode::Replace => true,
21272 LspInsertMode::ReplaceSubsequence => {
21273 let mut text_to_replace = buffer.chars_for_range(
21274 buffer.anchor_before(replace_range.start)
21275 ..buffer.anchor_after(replace_range.end),
21276 );
21277 let mut current_needle = text_to_replace.next();
21278 for haystack_ch in completion.label.text.chars() {
21279 if let Some(needle_ch) = current_needle {
21280 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21281 current_needle = text_to_replace.next();
21282 }
21283 }
21284 }
21285 current_needle.is_none()
21286 }
21287 LspInsertMode::ReplaceSuffix => {
21288 if replace_range
21289 .end
21290 .cmp(&cursor_position, &buffer_snapshot)
21291 .is_gt()
21292 {
21293 let range_after_cursor = *cursor_position..replace_range.end;
21294 let text_after_cursor = buffer
21295 .text_for_range(
21296 buffer.anchor_before(range_after_cursor.start)
21297 ..buffer.anchor_after(range_after_cursor.end),
21298 )
21299 .collect::<String>()
21300 .to_ascii_lowercase();
21301 completion
21302 .label
21303 .text
21304 .to_ascii_lowercase()
21305 .ends_with(&text_after_cursor)
21306 } else {
21307 true
21308 }
21309 }
21310 }
21311 }
21312 };
21313
21314 if should_replace {
21315 replace_range.clone()
21316 } else {
21317 insert_range.clone()
21318 }
21319 } else {
21320 replace_range.clone()
21321 }
21322 };
21323
21324 if range_to_replace
21325 .end
21326 .cmp(&cursor_position, &buffer_snapshot)
21327 .is_lt()
21328 {
21329 range_to_replace.end = *cursor_position;
21330 }
21331
21332 CompletionEdit {
21333 new_text,
21334 replace_range: range_to_replace.to_offset(&buffer),
21335 snippet,
21336 }
21337}
21338
21339struct CompletionEdit {
21340 new_text: String,
21341 replace_range: Range<usize>,
21342 snippet: Option<Snippet>,
21343}
21344
21345fn insert_extra_newline_brackets(
21346 buffer: &MultiBufferSnapshot,
21347 range: Range<usize>,
21348 language: &language::LanguageScope,
21349) -> bool {
21350 let leading_whitespace_len = buffer
21351 .reversed_chars_at(range.start)
21352 .take_while(|c| c.is_whitespace() && *c != '\n')
21353 .map(|c| c.len_utf8())
21354 .sum::<usize>();
21355 let trailing_whitespace_len = buffer
21356 .chars_at(range.end)
21357 .take_while(|c| c.is_whitespace() && *c != '\n')
21358 .map(|c| c.len_utf8())
21359 .sum::<usize>();
21360 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21361
21362 language.brackets().any(|(pair, enabled)| {
21363 let pair_start = pair.start.trim_end();
21364 let pair_end = pair.end.trim_start();
21365
21366 enabled
21367 && pair.newline
21368 && buffer.contains_str_at(range.end, pair_end)
21369 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21370 })
21371}
21372
21373fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21374 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21375 [(buffer, range, _)] => (*buffer, range.clone()),
21376 _ => return false,
21377 };
21378 let pair = {
21379 let mut result: Option<BracketMatch> = None;
21380
21381 for pair in buffer
21382 .all_bracket_ranges(range.clone())
21383 .filter(move |pair| {
21384 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21385 })
21386 {
21387 let len = pair.close_range.end - pair.open_range.start;
21388
21389 if let Some(existing) = &result {
21390 let existing_len = existing.close_range.end - existing.open_range.start;
21391 if len > existing_len {
21392 continue;
21393 }
21394 }
21395
21396 result = Some(pair);
21397 }
21398
21399 result
21400 };
21401 let Some(pair) = pair else {
21402 return false;
21403 };
21404 pair.newline_only
21405 && buffer
21406 .chars_for_range(pair.open_range.end..range.start)
21407 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21408 .all(|c| c.is_whitespace() && c != '\n')
21409}
21410
21411fn update_uncommitted_diff_for_buffer(
21412 editor: Entity<Editor>,
21413 project: &Entity<Project>,
21414 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21415 buffer: Entity<MultiBuffer>,
21416 cx: &mut App,
21417) -> Task<()> {
21418 let mut tasks = Vec::new();
21419 project.update(cx, |project, cx| {
21420 for buffer in buffers {
21421 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21422 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21423 }
21424 }
21425 });
21426 cx.spawn(async move |cx| {
21427 let diffs = future::join_all(tasks).await;
21428 if editor
21429 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21430 .unwrap_or(false)
21431 {
21432 return;
21433 }
21434
21435 buffer
21436 .update(cx, |buffer, cx| {
21437 for diff in diffs.into_iter().flatten() {
21438 buffer.add_diff(diff, cx);
21439 }
21440 })
21441 .ok();
21442 })
21443}
21444
21445fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21446 let tab_size = tab_size.get() as usize;
21447 let mut width = offset;
21448
21449 for ch in text.chars() {
21450 width += if ch == '\t' {
21451 tab_size - (width % tab_size)
21452 } else {
21453 1
21454 };
21455 }
21456
21457 width - offset
21458}
21459
21460#[cfg(test)]
21461mod tests {
21462 use super::*;
21463
21464 #[test]
21465 fn test_string_size_with_expanded_tabs() {
21466 let nz = |val| NonZeroU32::new(val).unwrap();
21467 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21468 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21469 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21470 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21471 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21472 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21473 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21474 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21475 }
21476}
21477
21478/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21479struct WordBreakingTokenizer<'a> {
21480 input: &'a str,
21481}
21482
21483impl<'a> WordBreakingTokenizer<'a> {
21484 fn new(input: &'a str) -> Self {
21485 Self { input }
21486 }
21487}
21488
21489fn is_char_ideographic(ch: char) -> bool {
21490 use unicode_script::Script::*;
21491 use unicode_script::UnicodeScript;
21492 matches!(ch.script(), Han | Tangut | Yi)
21493}
21494
21495fn is_grapheme_ideographic(text: &str) -> bool {
21496 text.chars().any(is_char_ideographic)
21497}
21498
21499fn is_grapheme_whitespace(text: &str) -> bool {
21500 text.chars().any(|x| x.is_whitespace())
21501}
21502
21503fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21504 text.chars().next().map_or(false, |ch| {
21505 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21506 })
21507}
21508
21509#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21510enum WordBreakToken<'a> {
21511 Word { token: &'a str, grapheme_len: usize },
21512 InlineWhitespace { token: &'a str, grapheme_len: usize },
21513 Newline,
21514}
21515
21516impl<'a> Iterator for WordBreakingTokenizer<'a> {
21517 /// Yields a span, the count of graphemes in the token, and whether it was
21518 /// whitespace. Note that it also breaks at word boundaries.
21519 type Item = WordBreakToken<'a>;
21520
21521 fn next(&mut self) -> Option<Self::Item> {
21522 use unicode_segmentation::UnicodeSegmentation;
21523 if self.input.is_empty() {
21524 return None;
21525 }
21526
21527 let mut iter = self.input.graphemes(true).peekable();
21528 let mut offset = 0;
21529 let mut grapheme_len = 0;
21530 if let Some(first_grapheme) = iter.next() {
21531 let is_newline = first_grapheme == "\n";
21532 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21533 offset += first_grapheme.len();
21534 grapheme_len += 1;
21535 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21536 if let Some(grapheme) = iter.peek().copied() {
21537 if should_stay_with_preceding_ideograph(grapheme) {
21538 offset += grapheme.len();
21539 grapheme_len += 1;
21540 }
21541 }
21542 } else {
21543 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21544 let mut next_word_bound = words.peek().copied();
21545 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21546 next_word_bound = words.next();
21547 }
21548 while let Some(grapheme) = iter.peek().copied() {
21549 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21550 break;
21551 };
21552 if is_grapheme_whitespace(grapheme) != is_whitespace
21553 || (grapheme == "\n") != is_newline
21554 {
21555 break;
21556 };
21557 offset += grapheme.len();
21558 grapheme_len += 1;
21559 iter.next();
21560 }
21561 }
21562 let token = &self.input[..offset];
21563 self.input = &self.input[offset..];
21564 if token == "\n" {
21565 Some(WordBreakToken::Newline)
21566 } else if is_whitespace {
21567 Some(WordBreakToken::InlineWhitespace {
21568 token,
21569 grapheme_len,
21570 })
21571 } else {
21572 Some(WordBreakToken::Word {
21573 token,
21574 grapheme_len,
21575 })
21576 }
21577 } else {
21578 None
21579 }
21580 }
21581}
21582
21583#[test]
21584fn test_word_breaking_tokenizer() {
21585 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21586 ("", &[]),
21587 (" ", &[whitespace(" ", 2)]),
21588 ("Ʒ", &[word("Ʒ", 1)]),
21589 ("Ǽ", &[word("Ǽ", 1)]),
21590 ("⋑", &[word("⋑", 1)]),
21591 ("⋑⋑", &[word("⋑⋑", 2)]),
21592 (
21593 "原理,进而",
21594 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21595 ),
21596 (
21597 "hello world",
21598 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21599 ),
21600 (
21601 "hello, world",
21602 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21603 ),
21604 (
21605 " hello world",
21606 &[
21607 whitespace(" ", 2),
21608 word("hello", 5),
21609 whitespace(" ", 1),
21610 word("world", 5),
21611 ],
21612 ),
21613 (
21614 "这是什么 \n 钢笔",
21615 &[
21616 word("这", 1),
21617 word("是", 1),
21618 word("什", 1),
21619 word("么", 1),
21620 whitespace(" ", 1),
21621 newline(),
21622 whitespace(" ", 1),
21623 word("钢", 1),
21624 word("笔", 1),
21625 ],
21626 ),
21627 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21628 ];
21629
21630 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21631 WordBreakToken::Word {
21632 token,
21633 grapheme_len,
21634 }
21635 }
21636
21637 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21638 WordBreakToken::InlineWhitespace {
21639 token,
21640 grapheme_len,
21641 }
21642 }
21643
21644 fn newline() -> WordBreakToken<'static> {
21645 WordBreakToken::Newline
21646 }
21647
21648 for (input, result) in tests {
21649 assert_eq!(
21650 WordBreakingTokenizer::new(input)
21651 .collect::<Vec<_>>()
21652 .as_slice(),
21653 *result,
21654 );
21655 }
21656}
21657
21658fn wrap_with_prefix(
21659 first_line_prefix: String,
21660 subsequent_lines_prefix: String,
21661 unwrapped_text: String,
21662 wrap_column: usize,
21663 tab_size: NonZeroU32,
21664 preserve_existing_whitespace: bool,
21665) -> String {
21666 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21667 let subsequent_lines_prefix_len =
21668 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21669 let mut wrapped_text = String::new();
21670 let mut current_line = first_line_prefix.clone();
21671 let mut is_first_line = true;
21672
21673 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21674 let mut current_line_len = first_line_prefix_len;
21675 let mut in_whitespace = false;
21676 for token in tokenizer {
21677 let have_preceding_whitespace = in_whitespace;
21678 match token {
21679 WordBreakToken::Word {
21680 token,
21681 grapheme_len,
21682 } => {
21683 in_whitespace = false;
21684 let current_prefix_len = if is_first_line {
21685 first_line_prefix_len
21686 } else {
21687 subsequent_lines_prefix_len
21688 };
21689 if current_line_len + grapheme_len > wrap_column
21690 && current_line_len != current_prefix_len
21691 {
21692 wrapped_text.push_str(current_line.trim_end());
21693 wrapped_text.push('\n');
21694 is_first_line = false;
21695 current_line = subsequent_lines_prefix.clone();
21696 current_line_len = subsequent_lines_prefix_len;
21697 }
21698 current_line.push_str(token);
21699 current_line_len += grapheme_len;
21700 }
21701 WordBreakToken::InlineWhitespace {
21702 mut token,
21703 mut grapheme_len,
21704 } => {
21705 in_whitespace = true;
21706 if have_preceding_whitespace && !preserve_existing_whitespace {
21707 continue;
21708 }
21709 if !preserve_existing_whitespace {
21710 token = " ";
21711 grapheme_len = 1;
21712 }
21713 let current_prefix_len = if is_first_line {
21714 first_line_prefix_len
21715 } else {
21716 subsequent_lines_prefix_len
21717 };
21718 if current_line_len + grapheme_len > wrap_column {
21719 wrapped_text.push_str(current_line.trim_end());
21720 wrapped_text.push('\n');
21721 is_first_line = false;
21722 current_line = subsequent_lines_prefix.clone();
21723 current_line_len = subsequent_lines_prefix_len;
21724 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21725 current_line.push_str(token);
21726 current_line_len += grapheme_len;
21727 }
21728 }
21729 WordBreakToken::Newline => {
21730 in_whitespace = true;
21731 let current_prefix_len = if is_first_line {
21732 first_line_prefix_len
21733 } else {
21734 subsequent_lines_prefix_len
21735 };
21736 if preserve_existing_whitespace {
21737 wrapped_text.push_str(current_line.trim_end());
21738 wrapped_text.push('\n');
21739 is_first_line = false;
21740 current_line = subsequent_lines_prefix.clone();
21741 current_line_len = subsequent_lines_prefix_len;
21742 } else if have_preceding_whitespace {
21743 continue;
21744 } else if current_line_len + 1 > wrap_column
21745 && current_line_len != current_prefix_len
21746 {
21747 wrapped_text.push_str(current_line.trim_end());
21748 wrapped_text.push('\n');
21749 is_first_line = false;
21750 current_line = subsequent_lines_prefix.clone();
21751 current_line_len = subsequent_lines_prefix_len;
21752 } else if current_line_len != current_prefix_len {
21753 current_line.push(' ');
21754 current_line_len += 1;
21755 }
21756 }
21757 }
21758 }
21759
21760 if !current_line.is_empty() {
21761 wrapped_text.push_str(¤t_line);
21762 }
21763 wrapped_text
21764}
21765
21766#[test]
21767fn test_wrap_with_prefix() {
21768 assert_eq!(
21769 wrap_with_prefix(
21770 "# ".to_string(),
21771 "# ".to_string(),
21772 "abcdefg".to_string(),
21773 4,
21774 NonZeroU32::new(4).unwrap(),
21775 false,
21776 ),
21777 "# abcdefg"
21778 );
21779 assert_eq!(
21780 wrap_with_prefix(
21781 "".to_string(),
21782 "".to_string(),
21783 "\thello world".to_string(),
21784 8,
21785 NonZeroU32::new(4).unwrap(),
21786 false,
21787 ),
21788 "hello\nworld"
21789 );
21790 assert_eq!(
21791 wrap_with_prefix(
21792 "// ".to_string(),
21793 "// ".to_string(),
21794 "xx \nyy zz aa bb cc".to_string(),
21795 12,
21796 NonZeroU32::new(4).unwrap(),
21797 false,
21798 ),
21799 "// xx yy zz\n// aa bb cc"
21800 );
21801 assert_eq!(
21802 wrap_with_prefix(
21803 String::new(),
21804 String::new(),
21805 "这是什么 \n 钢笔".to_string(),
21806 3,
21807 NonZeroU32::new(4).unwrap(),
21808 false,
21809 ),
21810 "这是什\n么 钢\n笔"
21811 );
21812}
21813
21814pub trait CollaborationHub {
21815 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21816 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21817 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21818}
21819
21820impl CollaborationHub for Entity<Project> {
21821 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21822 self.read(cx).collaborators()
21823 }
21824
21825 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21826 self.read(cx).user_store().read(cx).participant_indices()
21827 }
21828
21829 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21830 let this = self.read(cx);
21831 let user_ids = this.collaborators().values().map(|c| c.user_id);
21832 this.user_store().read(cx).participant_names(user_ids, cx)
21833 }
21834}
21835
21836pub trait SemanticsProvider {
21837 fn hover(
21838 &self,
21839 buffer: &Entity<Buffer>,
21840 position: text::Anchor,
21841 cx: &mut App,
21842 ) -> Option<Task<Vec<project::Hover>>>;
21843
21844 fn inline_values(
21845 &self,
21846 buffer_handle: Entity<Buffer>,
21847 range: Range<text::Anchor>,
21848 cx: &mut App,
21849 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21850
21851 fn inlay_hints(
21852 &self,
21853 buffer_handle: Entity<Buffer>,
21854 range: Range<text::Anchor>,
21855 cx: &mut App,
21856 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21857
21858 fn resolve_inlay_hint(
21859 &self,
21860 hint: InlayHint,
21861 buffer_handle: Entity<Buffer>,
21862 server_id: LanguageServerId,
21863 cx: &mut App,
21864 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21865
21866 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21867
21868 fn document_highlights(
21869 &self,
21870 buffer: &Entity<Buffer>,
21871 position: text::Anchor,
21872 cx: &mut App,
21873 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21874
21875 fn definitions(
21876 &self,
21877 buffer: &Entity<Buffer>,
21878 position: text::Anchor,
21879 kind: GotoDefinitionKind,
21880 cx: &mut App,
21881 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21882
21883 fn range_for_rename(
21884 &self,
21885 buffer: &Entity<Buffer>,
21886 position: text::Anchor,
21887 cx: &mut App,
21888 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21889
21890 fn perform_rename(
21891 &self,
21892 buffer: &Entity<Buffer>,
21893 position: text::Anchor,
21894 new_name: String,
21895 cx: &mut App,
21896 ) -> Option<Task<Result<ProjectTransaction>>>;
21897}
21898
21899pub trait CompletionProvider {
21900 fn completions(
21901 &self,
21902 excerpt_id: ExcerptId,
21903 buffer: &Entity<Buffer>,
21904 buffer_position: text::Anchor,
21905 trigger: CompletionContext,
21906 window: &mut Window,
21907 cx: &mut Context<Editor>,
21908 ) -> Task<Result<Vec<CompletionResponse>>>;
21909
21910 fn resolve_completions(
21911 &self,
21912 _buffer: Entity<Buffer>,
21913 _completion_indices: Vec<usize>,
21914 _completions: Rc<RefCell<Box<[Completion]>>>,
21915 _cx: &mut Context<Editor>,
21916 ) -> Task<Result<bool>> {
21917 Task::ready(Ok(false))
21918 }
21919
21920 fn apply_additional_edits_for_completion(
21921 &self,
21922 _buffer: Entity<Buffer>,
21923 _completions: Rc<RefCell<Box<[Completion]>>>,
21924 _completion_index: usize,
21925 _push_to_history: bool,
21926 _cx: &mut Context<Editor>,
21927 ) -> Task<Result<Option<language::Transaction>>> {
21928 Task::ready(Ok(None))
21929 }
21930
21931 fn is_completion_trigger(
21932 &self,
21933 buffer: &Entity<Buffer>,
21934 position: language::Anchor,
21935 text: &str,
21936 trigger_in_words: bool,
21937 menu_is_open: bool,
21938 cx: &mut Context<Editor>,
21939 ) -> bool;
21940
21941 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21942
21943 fn sort_completions(&self) -> bool {
21944 true
21945 }
21946
21947 fn filter_completions(&self) -> bool {
21948 true
21949 }
21950}
21951
21952pub trait CodeActionProvider {
21953 fn id(&self) -> Arc<str>;
21954
21955 fn code_actions(
21956 &self,
21957 buffer: &Entity<Buffer>,
21958 range: Range<text::Anchor>,
21959 window: &mut Window,
21960 cx: &mut App,
21961 ) -> Task<Result<Vec<CodeAction>>>;
21962
21963 fn apply_code_action(
21964 &self,
21965 buffer_handle: Entity<Buffer>,
21966 action: CodeAction,
21967 excerpt_id: ExcerptId,
21968 push_to_history: bool,
21969 window: &mut Window,
21970 cx: &mut App,
21971 ) -> Task<Result<ProjectTransaction>>;
21972}
21973
21974impl CodeActionProvider for Entity<Project> {
21975 fn id(&self) -> Arc<str> {
21976 "project".into()
21977 }
21978
21979 fn code_actions(
21980 &self,
21981 buffer: &Entity<Buffer>,
21982 range: Range<text::Anchor>,
21983 _window: &mut Window,
21984 cx: &mut App,
21985 ) -> Task<Result<Vec<CodeAction>>> {
21986 self.update(cx, |project, cx| {
21987 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21988 let code_actions = project.code_actions(buffer, range, None, cx);
21989 cx.background_spawn(async move {
21990 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21991 Ok(code_lens_actions
21992 .context("code lens fetch")?
21993 .into_iter()
21994 .chain(code_actions.context("code action fetch")?)
21995 .collect())
21996 })
21997 })
21998 }
21999
22000 fn apply_code_action(
22001 &self,
22002 buffer_handle: Entity<Buffer>,
22003 action: CodeAction,
22004 _excerpt_id: ExcerptId,
22005 push_to_history: bool,
22006 _window: &mut Window,
22007 cx: &mut App,
22008 ) -> Task<Result<ProjectTransaction>> {
22009 self.update(cx, |project, cx| {
22010 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22011 })
22012 }
22013}
22014
22015fn snippet_completions(
22016 project: &Project,
22017 buffer: &Entity<Buffer>,
22018 buffer_position: text::Anchor,
22019 cx: &mut App,
22020) -> Task<Result<CompletionResponse>> {
22021 let languages = buffer.read(cx).languages_at(buffer_position);
22022 let snippet_store = project.snippets().read(cx);
22023
22024 let scopes: Vec<_> = languages
22025 .iter()
22026 .filter_map(|language| {
22027 let language_name = language.lsp_id();
22028 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22029
22030 if snippets.is_empty() {
22031 None
22032 } else {
22033 Some((language.default_scope(), snippets))
22034 }
22035 })
22036 .collect();
22037
22038 if scopes.is_empty() {
22039 return Task::ready(Ok(CompletionResponse {
22040 completions: vec![],
22041 is_incomplete: false,
22042 }));
22043 }
22044
22045 let snapshot = buffer.read(cx).text_snapshot();
22046 let chars: String = snapshot
22047 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22048 .collect();
22049 let executor = cx.background_executor().clone();
22050
22051 cx.background_spawn(async move {
22052 let mut is_incomplete = false;
22053 let mut completions: Vec<Completion> = Vec::new();
22054 for (scope, snippets) in scopes.into_iter() {
22055 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22056 let mut last_word = chars
22057 .chars()
22058 .take_while(|c| classifier.is_word(*c))
22059 .collect::<String>();
22060 last_word = last_word.chars().rev().collect();
22061
22062 if last_word.is_empty() {
22063 return Ok(CompletionResponse {
22064 completions: vec![],
22065 is_incomplete: true,
22066 });
22067 }
22068
22069 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22070 let to_lsp = |point: &text::Anchor| {
22071 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22072 point_to_lsp(end)
22073 };
22074 let lsp_end = to_lsp(&buffer_position);
22075
22076 let candidates = snippets
22077 .iter()
22078 .enumerate()
22079 .flat_map(|(ix, snippet)| {
22080 snippet
22081 .prefix
22082 .iter()
22083 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
22084 })
22085 .collect::<Vec<StringMatchCandidate>>();
22086
22087 const MAX_RESULTS: usize = 100;
22088 let mut matches = fuzzy::match_strings(
22089 &candidates,
22090 &last_word,
22091 last_word.chars().any(|c| c.is_uppercase()),
22092 true,
22093 MAX_RESULTS,
22094 &Default::default(),
22095 executor.clone(),
22096 )
22097 .await;
22098
22099 if matches.len() >= MAX_RESULTS {
22100 is_incomplete = true;
22101 }
22102
22103 // Remove all candidates where the query's start does not match the start of any word in the candidate
22104 if let Some(query_start) = last_word.chars().next() {
22105 matches.retain(|string_match| {
22106 split_words(&string_match.string).any(|word| {
22107 // Check that the first codepoint of the word as lowercase matches the first
22108 // codepoint of the query as lowercase
22109 word.chars()
22110 .flat_map(|codepoint| codepoint.to_lowercase())
22111 .zip(query_start.to_lowercase())
22112 .all(|(word_cp, query_cp)| word_cp == query_cp)
22113 })
22114 });
22115 }
22116
22117 let matched_strings = matches
22118 .into_iter()
22119 .map(|m| m.string)
22120 .collect::<HashSet<_>>();
22121
22122 completions.extend(snippets.iter().filter_map(|snippet| {
22123 let matching_prefix = snippet
22124 .prefix
22125 .iter()
22126 .find(|prefix| matched_strings.contains(*prefix))?;
22127 let start = as_offset - last_word.len();
22128 let start = snapshot.anchor_before(start);
22129 let range = start..buffer_position;
22130 let lsp_start = to_lsp(&start);
22131 let lsp_range = lsp::Range {
22132 start: lsp_start,
22133 end: lsp_end,
22134 };
22135 Some(Completion {
22136 replace_range: range,
22137 new_text: snippet.body.clone(),
22138 source: CompletionSource::Lsp {
22139 insert_range: None,
22140 server_id: LanguageServerId(usize::MAX),
22141 resolved: true,
22142 lsp_completion: Box::new(lsp::CompletionItem {
22143 label: snippet.prefix.first().unwrap().clone(),
22144 kind: Some(CompletionItemKind::SNIPPET),
22145 label_details: snippet.description.as_ref().map(|description| {
22146 lsp::CompletionItemLabelDetails {
22147 detail: Some(description.clone()),
22148 description: None,
22149 }
22150 }),
22151 insert_text_format: Some(InsertTextFormat::SNIPPET),
22152 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22153 lsp::InsertReplaceEdit {
22154 new_text: snippet.body.clone(),
22155 insert: lsp_range,
22156 replace: lsp_range,
22157 },
22158 )),
22159 filter_text: Some(snippet.body.clone()),
22160 sort_text: Some(char::MAX.to_string()),
22161 ..lsp::CompletionItem::default()
22162 }),
22163 lsp_defaults: None,
22164 },
22165 label: CodeLabel {
22166 text: matching_prefix.clone(),
22167 runs: Vec::new(),
22168 filter_range: 0..matching_prefix.len(),
22169 },
22170 icon_path: None,
22171 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22172 single_line: snippet.name.clone().into(),
22173 plain_text: snippet
22174 .description
22175 .clone()
22176 .map(|description| description.into()),
22177 }),
22178 insert_text_mode: None,
22179 confirm: None,
22180 })
22181 }))
22182 }
22183
22184 Ok(CompletionResponse {
22185 completions,
22186 is_incomplete,
22187 })
22188 })
22189}
22190
22191impl CompletionProvider for Entity<Project> {
22192 fn completions(
22193 &self,
22194 _excerpt_id: ExcerptId,
22195 buffer: &Entity<Buffer>,
22196 buffer_position: text::Anchor,
22197 options: CompletionContext,
22198 _window: &mut Window,
22199 cx: &mut Context<Editor>,
22200 ) -> Task<Result<Vec<CompletionResponse>>> {
22201 self.update(cx, |project, cx| {
22202 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22203 let project_completions = project.completions(buffer, buffer_position, options, cx);
22204 cx.background_spawn(async move {
22205 let mut responses = project_completions.await?;
22206 let snippets = snippets.await?;
22207 if !snippets.completions.is_empty() {
22208 responses.push(snippets);
22209 }
22210 Ok(responses)
22211 })
22212 })
22213 }
22214
22215 fn resolve_completions(
22216 &self,
22217 buffer: Entity<Buffer>,
22218 completion_indices: Vec<usize>,
22219 completions: Rc<RefCell<Box<[Completion]>>>,
22220 cx: &mut Context<Editor>,
22221 ) -> Task<Result<bool>> {
22222 self.update(cx, |project, cx| {
22223 project.lsp_store().update(cx, |lsp_store, cx| {
22224 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22225 })
22226 })
22227 }
22228
22229 fn apply_additional_edits_for_completion(
22230 &self,
22231 buffer: Entity<Buffer>,
22232 completions: Rc<RefCell<Box<[Completion]>>>,
22233 completion_index: usize,
22234 push_to_history: bool,
22235 cx: &mut Context<Editor>,
22236 ) -> Task<Result<Option<language::Transaction>>> {
22237 self.update(cx, |project, cx| {
22238 project.lsp_store().update(cx, |lsp_store, cx| {
22239 lsp_store.apply_additional_edits_for_completion(
22240 buffer,
22241 completions,
22242 completion_index,
22243 push_to_history,
22244 cx,
22245 )
22246 })
22247 })
22248 }
22249
22250 fn is_completion_trigger(
22251 &self,
22252 buffer: &Entity<Buffer>,
22253 position: language::Anchor,
22254 text: &str,
22255 trigger_in_words: bool,
22256 menu_is_open: bool,
22257 cx: &mut Context<Editor>,
22258 ) -> bool {
22259 let mut chars = text.chars();
22260 let char = if let Some(char) = chars.next() {
22261 char
22262 } else {
22263 return false;
22264 };
22265 if chars.next().is_some() {
22266 return false;
22267 }
22268
22269 let buffer = buffer.read(cx);
22270 let snapshot = buffer.snapshot();
22271 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22272 return false;
22273 }
22274 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22275 if trigger_in_words && classifier.is_word(char) {
22276 return true;
22277 }
22278
22279 buffer.completion_triggers().contains(text)
22280 }
22281}
22282
22283impl SemanticsProvider for Entity<Project> {
22284 fn hover(
22285 &self,
22286 buffer: &Entity<Buffer>,
22287 position: text::Anchor,
22288 cx: &mut App,
22289 ) -> Option<Task<Vec<project::Hover>>> {
22290 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22291 }
22292
22293 fn document_highlights(
22294 &self,
22295 buffer: &Entity<Buffer>,
22296 position: text::Anchor,
22297 cx: &mut App,
22298 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22299 Some(self.update(cx, |project, cx| {
22300 project.document_highlights(buffer, position, cx)
22301 }))
22302 }
22303
22304 fn definitions(
22305 &self,
22306 buffer: &Entity<Buffer>,
22307 position: text::Anchor,
22308 kind: GotoDefinitionKind,
22309 cx: &mut App,
22310 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22311 Some(self.update(cx, |project, cx| match kind {
22312 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22313 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22314 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22315 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22316 }))
22317 }
22318
22319 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22320 self.update(cx, |project, cx| {
22321 if project
22322 .active_debug_session(cx)
22323 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22324 {
22325 return true;
22326 }
22327
22328 buffer.update(cx, |buffer, cx| {
22329 project.any_language_server_supports_inlay_hints(buffer, cx)
22330 })
22331 })
22332 }
22333
22334 fn inline_values(
22335 &self,
22336 buffer_handle: Entity<Buffer>,
22337 range: Range<text::Anchor>,
22338 cx: &mut App,
22339 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22340 self.update(cx, |project, cx| {
22341 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22342
22343 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22344 })
22345 }
22346
22347 fn inlay_hints(
22348 &self,
22349 buffer_handle: Entity<Buffer>,
22350 range: Range<text::Anchor>,
22351 cx: &mut App,
22352 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22353 Some(self.update(cx, |project, cx| {
22354 project.inlay_hints(buffer_handle, range, cx)
22355 }))
22356 }
22357
22358 fn resolve_inlay_hint(
22359 &self,
22360 hint: InlayHint,
22361 buffer_handle: Entity<Buffer>,
22362 server_id: LanguageServerId,
22363 cx: &mut App,
22364 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22365 Some(self.update(cx, |project, cx| {
22366 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22367 }))
22368 }
22369
22370 fn range_for_rename(
22371 &self,
22372 buffer: &Entity<Buffer>,
22373 position: text::Anchor,
22374 cx: &mut App,
22375 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22376 Some(self.update(cx, |project, cx| {
22377 let buffer = buffer.clone();
22378 let task = project.prepare_rename(buffer.clone(), position, cx);
22379 cx.spawn(async move |_, cx| {
22380 Ok(match task.await? {
22381 PrepareRenameResponse::Success(range) => Some(range),
22382 PrepareRenameResponse::InvalidPosition => None,
22383 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22384 // Fallback on using TreeSitter info to determine identifier range
22385 buffer.read_with(cx, |buffer, _| {
22386 let snapshot = buffer.snapshot();
22387 let (range, kind) = snapshot.surrounding_word(position, false);
22388 if kind != Some(CharKind::Word) {
22389 return None;
22390 }
22391 Some(
22392 snapshot.anchor_before(range.start)
22393 ..snapshot.anchor_after(range.end),
22394 )
22395 })?
22396 }
22397 })
22398 })
22399 }))
22400 }
22401
22402 fn perform_rename(
22403 &self,
22404 buffer: &Entity<Buffer>,
22405 position: text::Anchor,
22406 new_name: String,
22407 cx: &mut App,
22408 ) -> Option<Task<Result<ProjectTransaction>>> {
22409 Some(self.update(cx, |project, cx| {
22410 project.perform_rename(buffer.clone(), position, new_name, cx)
22411 }))
22412 }
22413}
22414
22415fn inlay_hint_settings(
22416 location: Anchor,
22417 snapshot: &MultiBufferSnapshot,
22418 cx: &mut Context<Editor>,
22419) -> InlayHintSettings {
22420 let file = snapshot.file_at(location);
22421 let language = snapshot.language_at(location).map(|l| l.name());
22422 language_settings(language, file, cx).inlay_hints
22423}
22424
22425fn consume_contiguous_rows(
22426 contiguous_row_selections: &mut Vec<Selection<Point>>,
22427 selection: &Selection<Point>,
22428 display_map: &DisplaySnapshot,
22429 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22430) -> (MultiBufferRow, MultiBufferRow) {
22431 contiguous_row_selections.push(selection.clone());
22432 let start_row = starting_row(selection, display_map);
22433 let mut end_row = ending_row(selection, display_map);
22434
22435 while let Some(next_selection) = selections.peek() {
22436 if next_selection.start.row <= end_row.0 {
22437 end_row = ending_row(next_selection, display_map);
22438 contiguous_row_selections.push(selections.next().unwrap().clone());
22439 } else {
22440 break;
22441 }
22442 }
22443 (start_row, end_row)
22444}
22445
22446fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22447 if selection.start.column > 0 {
22448 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22449 } else {
22450 MultiBufferRow(selection.start.row)
22451 }
22452}
22453
22454fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22455 if next_selection.end.column > 0 || next_selection.is_empty() {
22456 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22457 } else {
22458 MultiBufferRow(next_selection.end.row)
22459 }
22460}
22461
22462impl EditorSnapshot {
22463 pub fn remote_selections_in_range<'a>(
22464 &'a self,
22465 range: &'a Range<Anchor>,
22466 collaboration_hub: &dyn CollaborationHub,
22467 cx: &'a App,
22468 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22469 let participant_names = collaboration_hub.user_names(cx);
22470 let participant_indices = collaboration_hub.user_participant_indices(cx);
22471 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22472 let collaborators_by_replica_id = collaborators_by_peer_id
22473 .values()
22474 .map(|collaborator| (collaborator.replica_id, collaborator))
22475 .collect::<HashMap<_, _>>();
22476 self.buffer_snapshot
22477 .selections_in_range(range, false)
22478 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22479 if replica_id == AGENT_REPLICA_ID {
22480 Some(RemoteSelection {
22481 replica_id,
22482 selection,
22483 cursor_shape,
22484 line_mode,
22485 collaborator_id: CollaboratorId::Agent,
22486 user_name: Some("Agent".into()),
22487 color: cx.theme().players().agent(),
22488 })
22489 } else {
22490 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22491 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22492 let user_name = participant_names.get(&collaborator.user_id).cloned();
22493 Some(RemoteSelection {
22494 replica_id,
22495 selection,
22496 cursor_shape,
22497 line_mode,
22498 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22499 user_name,
22500 color: if let Some(index) = participant_index {
22501 cx.theme().players().color_for_participant(index.0)
22502 } else {
22503 cx.theme().players().absent()
22504 },
22505 })
22506 }
22507 })
22508 }
22509
22510 pub fn hunks_for_ranges(
22511 &self,
22512 ranges: impl IntoIterator<Item = Range<Point>>,
22513 ) -> Vec<MultiBufferDiffHunk> {
22514 let mut hunks = Vec::new();
22515 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22516 HashMap::default();
22517 for query_range in ranges {
22518 let query_rows =
22519 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22520 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22521 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22522 ) {
22523 // Include deleted hunks that are adjacent to the query range, because
22524 // otherwise they would be missed.
22525 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22526 if hunk.status().is_deleted() {
22527 intersects_range |= hunk.row_range.start == query_rows.end;
22528 intersects_range |= hunk.row_range.end == query_rows.start;
22529 }
22530 if intersects_range {
22531 if !processed_buffer_rows
22532 .entry(hunk.buffer_id)
22533 .or_default()
22534 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22535 {
22536 continue;
22537 }
22538 hunks.push(hunk);
22539 }
22540 }
22541 }
22542
22543 hunks
22544 }
22545
22546 fn display_diff_hunks_for_rows<'a>(
22547 &'a self,
22548 display_rows: Range<DisplayRow>,
22549 folded_buffers: &'a HashSet<BufferId>,
22550 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22551 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22552 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22553
22554 self.buffer_snapshot
22555 .diff_hunks_in_range(buffer_start..buffer_end)
22556 .filter_map(|hunk| {
22557 if folded_buffers.contains(&hunk.buffer_id) {
22558 return None;
22559 }
22560
22561 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22562 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22563
22564 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22565 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22566
22567 let display_hunk = if hunk_display_start.column() != 0 {
22568 DisplayDiffHunk::Folded {
22569 display_row: hunk_display_start.row(),
22570 }
22571 } else {
22572 let mut end_row = hunk_display_end.row();
22573 if hunk_display_end.column() > 0 {
22574 end_row.0 += 1;
22575 }
22576 let is_created_file = hunk.is_created_file();
22577 DisplayDiffHunk::Unfolded {
22578 status: hunk.status(),
22579 diff_base_byte_range: hunk.diff_base_byte_range,
22580 display_row_range: hunk_display_start.row()..end_row,
22581 multi_buffer_range: Anchor::range_in_buffer(
22582 hunk.excerpt_id,
22583 hunk.buffer_id,
22584 hunk.buffer_range,
22585 ),
22586 is_created_file,
22587 }
22588 };
22589
22590 Some(display_hunk)
22591 })
22592 }
22593
22594 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22595 self.display_snapshot.buffer_snapshot.language_at(position)
22596 }
22597
22598 pub fn is_focused(&self) -> bool {
22599 self.is_focused
22600 }
22601
22602 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22603 self.placeholder_text.as_ref()
22604 }
22605
22606 pub fn scroll_position(&self) -> gpui::Point<f32> {
22607 self.scroll_anchor.scroll_position(&self.display_snapshot)
22608 }
22609
22610 fn gutter_dimensions(
22611 &self,
22612 font_id: FontId,
22613 font_size: Pixels,
22614 max_line_number_width: Pixels,
22615 cx: &App,
22616 ) -> Option<GutterDimensions> {
22617 if !self.show_gutter {
22618 return None;
22619 }
22620
22621 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22622 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22623
22624 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22625 matches!(
22626 ProjectSettings::get_global(cx).git.git_gutter,
22627 Some(GitGutterSetting::TrackedFiles)
22628 )
22629 });
22630 let gutter_settings = EditorSettings::get_global(cx).gutter;
22631 let show_line_numbers = self
22632 .show_line_numbers
22633 .unwrap_or(gutter_settings.line_numbers);
22634 let line_gutter_width = if show_line_numbers {
22635 // Avoid flicker-like gutter resizes when the line number gains another digit by
22636 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22637 let min_width_for_number_on_gutter =
22638 ch_advance * gutter_settings.min_line_number_digits as f32;
22639 max_line_number_width.max(min_width_for_number_on_gutter)
22640 } else {
22641 0.0.into()
22642 };
22643
22644 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22645 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22646
22647 let git_blame_entries_width =
22648 self.git_blame_gutter_max_author_length
22649 .map(|max_author_length| {
22650 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22651 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22652
22653 /// The number of characters to dedicate to gaps and margins.
22654 const SPACING_WIDTH: usize = 4;
22655
22656 let max_char_count = max_author_length.min(renderer.max_author_length())
22657 + ::git::SHORT_SHA_LENGTH
22658 + MAX_RELATIVE_TIMESTAMP.len()
22659 + SPACING_WIDTH;
22660
22661 ch_advance * max_char_count
22662 });
22663
22664 let is_singleton = self.buffer_snapshot.is_singleton();
22665
22666 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22667 left_padding += if !is_singleton {
22668 ch_width * 4.0
22669 } else if show_runnables || show_breakpoints {
22670 ch_width * 3.0
22671 } else if show_git_gutter && show_line_numbers {
22672 ch_width * 2.0
22673 } else if show_git_gutter || show_line_numbers {
22674 ch_width
22675 } else {
22676 px(0.)
22677 };
22678
22679 let shows_folds = is_singleton && gutter_settings.folds;
22680
22681 let right_padding = if shows_folds && show_line_numbers {
22682 ch_width * 4.0
22683 } else if shows_folds || (!is_singleton && show_line_numbers) {
22684 ch_width * 3.0
22685 } else if show_line_numbers {
22686 ch_width
22687 } else {
22688 px(0.)
22689 };
22690
22691 Some(GutterDimensions {
22692 left_padding,
22693 right_padding,
22694 width: line_gutter_width + left_padding + right_padding,
22695 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22696 git_blame_entries_width,
22697 })
22698 }
22699
22700 pub fn render_crease_toggle(
22701 &self,
22702 buffer_row: MultiBufferRow,
22703 row_contains_cursor: bool,
22704 editor: Entity<Editor>,
22705 window: &mut Window,
22706 cx: &mut App,
22707 ) -> Option<AnyElement> {
22708 let folded = self.is_line_folded(buffer_row);
22709 let mut is_foldable = false;
22710
22711 if let Some(crease) = self
22712 .crease_snapshot
22713 .query_row(buffer_row, &self.buffer_snapshot)
22714 {
22715 is_foldable = true;
22716 match crease {
22717 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22718 if let Some(render_toggle) = render_toggle {
22719 let toggle_callback =
22720 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22721 if folded {
22722 editor.update(cx, |editor, cx| {
22723 editor.fold_at(buffer_row, window, cx)
22724 });
22725 } else {
22726 editor.update(cx, |editor, cx| {
22727 editor.unfold_at(buffer_row, window, cx)
22728 });
22729 }
22730 });
22731 return Some((render_toggle)(
22732 buffer_row,
22733 folded,
22734 toggle_callback,
22735 window,
22736 cx,
22737 ));
22738 }
22739 }
22740 }
22741 }
22742
22743 is_foldable |= self.starts_indent(buffer_row);
22744
22745 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22746 Some(
22747 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22748 .toggle_state(folded)
22749 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22750 if folded {
22751 this.unfold_at(buffer_row, window, cx);
22752 } else {
22753 this.fold_at(buffer_row, window, cx);
22754 }
22755 }))
22756 .into_any_element(),
22757 )
22758 } else {
22759 None
22760 }
22761 }
22762
22763 pub fn render_crease_trailer(
22764 &self,
22765 buffer_row: MultiBufferRow,
22766 window: &mut Window,
22767 cx: &mut App,
22768 ) -> Option<AnyElement> {
22769 let folded = self.is_line_folded(buffer_row);
22770 if let Crease::Inline { render_trailer, .. } = self
22771 .crease_snapshot
22772 .query_row(buffer_row, &self.buffer_snapshot)?
22773 {
22774 let render_trailer = render_trailer.as_ref()?;
22775 Some(render_trailer(buffer_row, folded, window, cx))
22776 } else {
22777 None
22778 }
22779 }
22780}
22781
22782impl Deref for EditorSnapshot {
22783 type Target = DisplaySnapshot;
22784
22785 fn deref(&self) -> &Self::Target {
22786 &self.display_snapshot
22787 }
22788}
22789
22790#[derive(Clone, Debug, PartialEq, Eq)]
22791pub enum EditorEvent {
22792 InputIgnored {
22793 text: Arc<str>,
22794 },
22795 InputHandled {
22796 utf16_range_to_replace: Option<Range<isize>>,
22797 text: Arc<str>,
22798 },
22799 ExcerptsAdded {
22800 buffer: Entity<Buffer>,
22801 predecessor: ExcerptId,
22802 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22803 },
22804 ExcerptsRemoved {
22805 ids: Vec<ExcerptId>,
22806 removed_buffer_ids: Vec<BufferId>,
22807 },
22808 BufferFoldToggled {
22809 ids: Vec<ExcerptId>,
22810 folded: bool,
22811 },
22812 ExcerptsEdited {
22813 ids: Vec<ExcerptId>,
22814 },
22815 ExcerptsExpanded {
22816 ids: Vec<ExcerptId>,
22817 },
22818 BufferEdited,
22819 Edited {
22820 transaction_id: clock::Lamport,
22821 },
22822 Reparsed(BufferId),
22823 Focused,
22824 FocusedIn,
22825 Blurred,
22826 DirtyChanged,
22827 Saved,
22828 TitleChanged,
22829 DiffBaseChanged,
22830 SelectionsChanged {
22831 local: bool,
22832 },
22833 ScrollPositionChanged {
22834 local: bool,
22835 autoscroll: bool,
22836 },
22837 Closed,
22838 TransactionUndone {
22839 transaction_id: clock::Lamport,
22840 },
22841 TransactionBegun {
22842 transaction_id: clock::Lamport,
22843 },
22844 Reloaded,
22845 CursorShapeChanged,
22846 PushedToNavHistory {
22847 anchor: Anchor,
22848 is_deactivate: bool,
22849 },
22850}
22851
22852impl EventEmitter<EditorEvent> for Editor {}
22853
22854impl Focusable for Editor {
22855 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22856 self.focus_handle.clone()
22857 }
22858}
22859
22860impl Render for Editor {
22861 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22862 let settings = ThemeSettings::get_global(cx);
22863
22864 let mut text_style = match self.mode {
22865 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22866 color: cx.theme().colors().editor_foreground,
22867 font_family: settings.ui_font.family.clone(),
22868 font_features: settings.ui_font.features.clone(),
22869 font_fallbacks: settings.ui_font.fallbacks.clone(),
22870 font_size: rems(0.875).into(),
22871 font_weight: settings.ui_font.weight,
22872 line_height: relative(settings.buffer_line_height.value()),
22873 ..Default::default()
22874 },
22875 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22876 color: cx.theme().colors().editor_foreground,
22877 font_family: settings.buffer_font.family.clone(),
22878 font_features: settings.buffer_font.features.clone(),
22879 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22880 font_size: settings.buffer_font_size(cx).into(),
22881 font_weight: settings.buffer_font.weight,
22882 line_height: relative(settings.buffer_line_height.value()),
22883 ..Default::default()
22884 },
22885 };
22886 if let Some(text_style_refinement) = &self.text_style_refinement {
22887 text_style.refine(text_style_refinement)
22888 }
22889
22890 let background = match self.mode {
22891 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22892 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22893 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22894 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22895 };
22896
22897 EditorElement::new(
22898 &cx.entity(),
22899 EditorStyle {
22900 background,
22901 border: cx.theme().colors().border,
22902 local_player: cx.theme().players().local(),
22903 text: text_style,
22904 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22905 syntax: cx.theme().syntax().clone(),
22906 status: cx.theme().status().clone(),
22907 inlay_hints_style: make_inlay_hints_style(cx),
22908 edit_prediction_styles: make_suggestion_styles(cx),
22909 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22910 show_underlines: self.diagnostics_enabled(),
22911 },
22912 )
22913 }
22914}
22915
22916impl EntityInputHandler for Editor {
22917 fn text_for_range(
22918 &mut self,
22919 range_utf16: Range<usize>,
22920 adjusted_range: &mut Option<Range<usize>>,
22921 _: &mut Window,
22922 cx: &mut Context<Self>,
22923 ) -> Option<String> {
22924 let snapshot = self.buffer.read(cx).read(cx);
22925 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22926 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22927 if (start.0..end.0) != range_utf16 {
22928 adjusted_range.replace(start.0..end.0);
22929 }
22930 Some(snapshot.text_for_range(start..end).collect())
22931 }
22932
22933 fn selected_text_range(
22934 &mut self,
22935 ignore_disabled_input: bool,
22936 _: &mut Window,
22937 cx: &mut Context<Self>,
22938 ) -> Option<UTF16Selection> {
22939 // Prevent the IME menu from appearing when holding down an alphabetic key
22940 // while input is disabled.
22941 if !ignore_disabled_input && !self.input_enabled {
22942 return None;
22943 }
22944
22945 let selection = self.selections.newest::<OffsetUtf16>(cx);
22946 let range = selection.range();
22947
22948 Some(UTF16Selection {
22949 range: range.start.0..range.end.0,
22950 reversed: selection.reversed,
22951 })
22952 }
22953
22954 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22955 let snapshot = self.buffer.read(cx).read(cx);
22956 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22957 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22958 }
22959
22960 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22961 self.clear_highlights::<InputComposition>(cx);
22962 self.ime_transaction.take();
22963 }
22964
22965 fn replace_text_in_range(
22966 &mut self,
22967 range_utf16: Option<Range<usize>>,
22968 text: &str,
22969 window: &mut Window,
22970 cx: &mut Context<Self>,
22971 ) {
22972 if !self.input_enabled {
22973 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22974 return;
22975 }
22976
22977 self.transact(window, cx, |this, window, cx| {
22978 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22979 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22980 Some(this.selection_replacement_ranges(range_utf16, cx))
22981 } else {
22982 this.marked_text_ranges(cx)
22983 };
22984
22985 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22986 let newest_selection_id = this.selections.newest_anchor().id;
22987 this.selections
22988 .all::<OffsetUtf16>(cx)
22989 .iter()
22990 .zip(ranges_to_replace.iter())
22991 .find_map(|(selection, range)| {
22992 if selection.id == newest_selection_id {
22993 Some(
22994 (range.start.0 as isize - selection.head().0 as isize)
22995 ..(range.end.0 as isize - selection.head().0 as isize),
22996 )
22997 } else {
22998 None
22999 }
23000 })
23001 });
23002
23003 cx.emit(EditorEvent::InputHandled {
23004 utf16_range_to_replace: range_to_replace,
23005 text: text.into(),
23006 });
23007
23008 if let Some(new_selected_ranges) = new_selected_ranges {
23009 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23010 selections.select_ranges(new_selected_ranges)
23011 });
23012 this.backspace(&Default::default(), window, cx);
23013 }
23014
23015 this.handle_input(text, window, cx);
23016 });
23017
23018 if let Some(transaction) = self.ime_transaction {
23019 self.buffer.update(cx, |buffer, cx| {
23020 buffer.group_until_transaction(transaction, cx);
23021 });
23022 }
23023
23024 self.unmark_text(window, cx);
23025 }
23026
23027 fn replace_and_mark_text_in_range(
23028 &mut self,
23029 range_utf16: Option<Range<usize>>,
23030 text: &str,
23031 new_selected_range_utf16: Option<Range<usize>>,
23032 window: &mut Window,
23033 cx: &mut Context<Self>,
23034 ) {
23035 if !self.input_enabled {
23036 return;
23037 }
23038
23039 let transaction = self.transact(window, cx, |this, window, cx| {
23040 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23041 let snapshot = this.buffer.read(cx).read(cx);
23042 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23043 for marked_range in &mut marked_ranges {
23044 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23045 marked_range.start.0 += relative_range_utf16.start;
23046 marked_range.start =
23047 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23048 marked_range.end =
23049 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23050 }
23051 }
23052 Some(marked_ranges)
23053 } else if let Some(range_utf16) = range_utf16 {
23054 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23055 Some(this.selection_replacement_ranges(range_utf16, cx))
23056 } else {
23057 None
23058 };
23059
23060 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23061 let newest_selection_id = this.selections.newest_anchor().id;
23062 this.selections
23063 .all::<OffsetUtf16>(cx)
23064 .iter()
23065 .zip(ranges_to_replace.iter())
23066 .find_map(|(selection, range)| {
23067 if selection.id == newest_selection_id {
23068 Some(
23069 (range.start.0 as isize - selection.head().0 as isize)
23070 ..(range.end.0 as isize - selection.head().0 as isize),
23071 )
23072 } else {
23073 None
23074 }
23075 })
23076 });
23077
23078 cx.emit(EditorEvent::InputHandled {
23079 utf16_range_to_replace: range_to_replace,
23080 text: text.into(),
23081 });
23082
23083 if let Some(ranges) = ranges_to_replace {
23084 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23085 s.select_ranges(ranges)
23086 });
23087 }
23088
23089 let marked_ranges = {
23090 let snapshot = this.buffer.read(cx).read(cx);
23091 this.selections
23092 .disjoint_anchors()
23093 .iter()
23094 .map(|selection| {
23095 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23096 })
23097 .collect::<Vec<_>>()
23098 };
23099
23100 if text.is_empty() {
23101 this.unmark_text(window, cx);
23102 } else {
23103 this.highlight_text::<InputComposition>(
23104 marked_ranges.clone(),
23105 HighlightStyle {
23106 underline: Some(UnderlineStyle {
23107 thickness: px(1.),
23108 color: None,
23109 wavy: false,
23110 }),
23111 ..Default::default()
23112 },
23113 cx,
23114 );
23115 }
23116
23117 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23118 let use_autoclose = this.use_autoclose;
23119 let use_auto_surround = this.use_auto_surround;
23120 this.set_use_autoclose(false);
23121 this.set_use_auto_surround(false);
23122 this.handle_input(text, window, cx);
23123 this.set_use_autoclose(use_autoclose);
23124 this.set_use_auto_surround(use_auto_surround);
23125
23126 if let Some(new_selected_range) = new_selected_range_utf16 {
23127 let snapshot = this.buffer.read(cx).read(cx);
23128 let new_selected_ranges = marked_ranges
23129 .into_iter()
23130 .map(|marked_range| {
23131 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23132 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23133 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23134 snapshot.clip_offset_utf16(new_start, Bias::Left)
23135 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23136 })
23137 .collect::<Vec<_>>();
23138
23139 drop(snapshot);
23140 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23141 selections.select_ranges(new_selected_ranges)
23142 });
23143 }
23144 });
23145
23146 self.ime_transaction = self.ime_transaction.or(transaction);
23147 if let Some(transaction) = self.ime_transaction {
23148 self.buffer.update(cx, |buffer, cx| {
23149 buffer.group_until_transaction(transaction, cx);
23150 });
23151 }
23152
23153 if self.text_highlights::<InputComposition>(cx).is_none() {
23154 self.ime_transaction.take();
23155 }
23156 }
23157
23158 fn bounds_for_range(
23159 &mut self,
23160 range_utf16: Range<usize>,
23161 element_bounds: gpui::Bounds<Pixels>,
23162 window: &mut Window,
23163 cx: &mut Context<Self>,
23164 ) -> Option<gpui::Bounds<Pixels>> {
23165 let text_layout_details = self.text_layout_details(window);
23166 let CharacterDimensions {
23167 em_width,
23168 em_advance,
23169 line_height,
23170 } = self.character_dimensions(window);
23171
23172 let snapshot = self.snapshot(window, cx);
23173 let scroll_position = snapshot.scroll_position();
23174 let scroll_left = scroll_position.x * em_advance;
23175
23176 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23177 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23178 + self.gutter_dimensions.full_width();
23179 let y = line_height * (start.row().as_f32() - scroll_position.y);
23180
23181 Some(Bounds {
23182 origin: element_bounds.origin + point(x, y),
23183 size: size(em_width, line_height),
23184 })
23185 }
23186
23187 fn character_index_for_point(
23188 &mut self,
23189 point: gpui::Point<Pixels>,
23190 _window: &mut Window,
23191 _cx: &mut Context<Self>,
23192 ) -> Option<usize> {
23193 let position_map = self.last_position_map.as_ref()?;
23194 if !position_map.text_hitbox.contains(&point) {
23195 return None;
23196 }
23197 let display_point = position_map.point_for_position(point).previous_valid;
23198 let anchor = position_map
23199 .snapshot
23200 .display_point_to_anchor(display_point, Bias::Left);
23201 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23202 Some(utf16_offset.0)
23203 }
23204}
23205
23206trait SelectionExt {
23207 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23208 fn spanned_rows(
23209 &self,
23210 include_end_if_at_line_start: bool,
23211 map: &DisplaySnapshot,
23212 ) -> Range<MultiBufferRow>;
23213}
23214
23215impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23216 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23217 let start = self
23218 .start
23219 .to_point(&map.buffer_snapshot)
23220 .to_display_point(map);
23221 let end = self
23222 .end
23223 .to_point(&map.buffer_snapshot)
23224 .to_display_point(map);
23225 if self.reversed {
23226 end..start
23227 } else {
23228 start..end
23229 }
23230 }
23231
23232 fn spanned_rows(
23233 &self,
23234 include_end_if_at_line_start: bool,
23235 map: &DisplaySnapshot,
23236 ) -> Range<MultiBufferRow> {
23237 let start = self.start.to_point(&map.buffer_snapshot);
23238 let mut end = self.end.to_point(&map.buffer_snapshot);
23239 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23240 end.row -= 1;
23241 }
23242
23243 let buffer_start = map.prev_line_boundary(start).0;
23244 let buffer_end = map.next_line_boundary(end).0;
23245 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23246 }
23247}
23248
23249impl<T: InvalidationRegion> InvalidationStack<T> {
23250 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23251 where
23252 S: Clone + ToOffset,
23253 {
23254 while let Some(region) = self.last() {
23255 let all_selections_inside_invalidation_ranges =
23256 if selections.len() == region.ranges().len() {
23257 selections
23258 .iter()
23259 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23260 .all(|(selection, invalidation_range)| {
23261 let head = selection.head().to_offset(buffer);
23262 invalidation_range.start <= head && invalidation_range.end >= head
23263 })
23264 } else {
23265 false
23266 };
23267
23268 if all_selections_inside_invalidation_ranges {
23269 break;
23270 } else {
23271 self.pop();
23272 }
23273 }
23274 }
23275}
23276
23277impl<T> Default for InvalidationStack<T> {
23278 fn default() -> Self {
23279 Self(Default::default())
23280 }
23281}
23282
23283impl<T> Deref for InvalidationStack<T> {
23284 type Target = Vec<T>;
23285
23286 fn deref(&self) -> &Self::Target {
23287 &self.0
23288 }
23289}
23290
23291impl<T> DerefMut for InvalidationStack<T> {
23292 fn deref_mut(&mut self) -> &mut Self::Target {
23293 &mut self.0
23294 }
23295}
23296
23297impl InvalidationRegion for SnippetState {
23298 fn ranges(&self) -> &[Range<Anchor>] {
23299 &self.ranges[self.active_index]
23300 }
23301}
23302
23303fn edit_prediction_edit_text(
23304 current_snapshot: &BufferSnapshot,
23305 edits: &[(Range<Anchor>, String)],
23306 edit_preview: &EditPreview,
23307 include_deletions: bool,
23308 cx: &App,
23309) -> HighlightedText {
23310 let edits = edits
23311 .iter()
23312 .map(|(anchor, text)| {
23313 (
23314 anchor.start.text_anchor..anchor.end.text_anchor,
23315 text.clone(),
23316 )
23317 })
23318 .collect::<Vec<_>>();
23319
23320 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23321}
23322
23323fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23324 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23325 // Just show the raw edit text with basic styling
23326 let mut text = String::new();
23327 let mut highlights = Vec::new();
23328
23329 let insertion_highlight_style = HighlightStyle {
23330 color: Some(cx.theme().colors().text),
23331 ..Default::default()
23332 };
23333
23334 for (_, edit_text) in edits {
23335 let start_offset = text.len();
23336 text.push_str(edit_text);
23337 let end_offset = text.len();
23338
23339 if start_offset < end_offset {
23340 highlights.push((start_offset..end_offset, insertion_highlight_style));
23341 }
23342 }
23343
23344 HighlightedText {
23345 text: text.into(),
23346 highlights,
23347 }
23348}
23349
23350pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23351 match severity {
23352 lsp::DiagnosticSeverity::ERROR => colors.error,
23353 lsp::DiagnosticSeverity::WARNING => colors.warning,
23354 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23355 lsp::DiagnosticSeverity::HINT => colors.info,
23356 _ => colors.ignored,
23357 }
23358}
23359
23360pub fn styled_runs_for_code_label<'a>(
23361 label: &'a CodeLabel,
23362 syntax_theme: &'a theme::SyntaxTheme,
23363) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23364 let fade_out = HighlightStyle {
23365 fade_out: Some(0.35),
23366 ..Default::default()
23367 };
23368
23369 let mut prev_end = label.filter_range.end;
23370 label
23371 .runs
23372 .iter()
23373 .enumerate()
23374 .flat_map(move |(ix, (range, highlight_id))| {
23375 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23376 style
23377 } else {
23378 return Default::default();
23379 };
23380 let mut muted_style = style;
23381 muted_style.highlight(fade_out);
23382
23383 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23384 if range.start >= label.filter_range.end {
23385 if range.start > prev_end {
23386 runs.push((prev_end..range.start, fade_out));
23387 }
23388 runs.push((range.clone(), muted_style));
23389 } else if range.end <= label.filter_range.end {
23390 runs.push((range.clone(), style));
23391 } else {
23392 runs.push((range.start..label.filter_range.end, style));
23393 runs.push((label.filter_range.end..range.end, muted_style));
23394 }
23395 prev_end = cmp::max(prev_end, range.end);
23396
23397 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23398 runs.push((prev_end..label.text.len(), fade_out));
23399 }
23400
23401 runs
23402 })
23403}
23404
23405pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23406 let mut prev_index = 0;
23407 let mut prev_codepoint: Option<char> = None;
23408 text.char_indices()
23409 .chain([(text.len(), '\0')])
23410 .filter_map(move |(index, codepoint)| {
23411 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23412 let is_boundary = index == text.len()
23413 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23414 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23415 if is_boundary {
23416 let chunk = &text[prev_index..index];
23417 prev_index = index;
23418 Some(chunk)
23419 } else {
23420 None
23421 }
23422 })
23423}
23424
23425pub trait RangeToAnchorExt: Sized {
23426 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23427
23428 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23429 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23430 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23431 }
23432}
23433
23434impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23435 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23436 let start_offset = self.start.to_offset(snapshot);
23437 let end_offset = self.end.to_offset(snapshot);
23438 if start_offset == end_offset {
23439 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23440 } else {
23441 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23442 }
23443 }
23444}
23445
23446pub trait RowExt {
23447 fn as_f32(&self) -> f32;
23448
23449 fn next_row(&self) -> Self;
23450
23451 fn previous_row(&self) -> Self;
23452
23453 fn minus(&self, other: Self) -> u32;
23454}
23455
23456impl RowExt for DisplayRow {
23457 fn as_f32(&self) -> f32 {
23458 self.0 as f32
23459 }
23460
23461 fn next_row(&self) -> Self {
23462 Self(self.0 + 1)
23463 }
23464
23465 fn previous_row(&self) -> Self {
23466 Self(self.0.saturating_sub(1))
23467 }
23468
23469 fn minus(&self, other: Self) -> u32 {
23470 self.0 - other.0
23471 }
23472}
23473
23474impl RowExt for MultiBufferRow {
23475 fn as_f32(&self) -> f32 {
23476 self.0 as f32
23477 }
23478
23479 fn next_row(&self) -> Self {
23480 Self(self.0 + 1)
23481 }
23482
23483 fn previous_row(&self) -> Self {
23484 Self(self.0.saturating_sub(1))
23485 }
23486
23487 fn minus(&self, other: Self) -> u32 {
23488 self.0 - other.0
23489 }
23490}
23491
23492trait RowRangeExt {
23493 type Row;
23494
23495 fn len(&self) -> usize;
23496
23497 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23498}
23499
23500impl RowRangeExt for Range<MultiBufferRow> {
23501 type Row = MultiBufferRow;
23502
23503 fn len(&self) -> usize {
23504 (self.end.0 - self.start.0) as usize
23505 }
23506
23507 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23508 (self.start.0..self.end.0).map(MultiBufferRow)
23509 }
23510}
23511
23512impl RowRangeExt for Range<DisplayRow> {
23513 type Row = DisplayRow;
23514
23515 fn len(&self) -> usize {
23516 (self.end.0 - self.start.0) as usize
23517 }
23518
23519 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23520 (self.start.0..self.end.0).map(DisplayRow)
23521 }
23522}
23523
23524/// If select range has more than one line, we
23525/// just point the cursor to range.start.
23526fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23527 if range.start.row == range.end.row {
23528 range
23529 } else {
23530 range.start..range.start
23531 }
23532}
23533pub struct KillRing(ClipboardItem);
23534impl Global for KillRing {}
23535
23536const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23537
23538enum BreakpointPromptEditAction {
23539 Log,
23540 Condition,
23541 HitCondition,
23542}
23543
23544struct BreakpointPromptEditor {
23545 pub(crate) prompt: Entity<Editor>,
23546 editor: WeakEntity<Editor>,
23547 breakpoint_anchor: Anchor,
23548 breakpoint: Breakpoint,
23549 edit_action: BreakpointPromptEditAction,
23550 block_ids: HashSet<CustomBlockId>,
23551 editor_margins: Arc<Mutex<EditorMargins>>,
23552 _subscriptions: Vec<Subscription>,
23553}
23554
23555impl BreakpointPromptEditor {
23556 const MAX_LINES: u8 = 4;
23557
23558 fn new(
23559 editor: WeakEntity<Editor>,
23560 breakpoint_anchor: Anchor,
23561 breakpoint: Breakpoint,
23562 edit_action: BreakpointPromptEditAction,
23563 window: &mut Window,
23564 cx: &mut Context<Self>,
23565 ) -> Self {
23566 let base_text = match edit_action {
23567 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23568 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23569 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23570 }
23571 .map(|msg| msg.to_string())
23572 .unwrap_or_default();
23573
23574 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23575 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23576
23577 let prompt = cx.new(|cx| {
23578 let mut prompt = Editor::new(
23579 EditorMode::AutoHeight {
23580 min_lines: 1,
23581 max_lines: Some(Self::MAX_LINES as usize),
23582 },
23583 buffer,
23584 None,
23585 window,
23586 cx,
23587 );
23588 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23589 prompt.set_show_cursor_when_unfocused(false, cx);
23590 prompt.set_placeholder_text(
23591 match edit_action {
23592 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23593 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23594 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23595 },
23596 cx,
23597 );
23598
23599 prompt
23600 });
23601
23602 Self {
23603 prompt,
23604 editor,
23605 breakpoint_anchor,
23606 breakpoint,
23607 edit_action,
23608 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23609 block_ids: Default::default(),
23610 _subscriptions: vec![],
23611 }
23612 }
23613
23614 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23615 self.block_ids.extend(block_ids)
23616 }
23617
23618 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23619 if let Some(editor) = self.editor.upgrade() {
23620 let message = self
23621 .prompt
23622 .read(cx)
23623 .buffer
23624 .read(cx)
23625 .as_singleton()
23626 .expect("A multi buffer in breakpoint prompt isn't possible")
23627 .read(cx)
23628 .as_rope()
23629 .to_string();
23630
23631 editor.update(cx, |editor, cx| {
23632 editor.edit_breakpoint_at_anchor(
23633 self.breakpoint_anchor,
23634 self.breakpoint.clone(),
23635 match self.edit_action {
23636 BreakpointPromptEditAction::Log => {
23637 BreakpointEditAction::EditLogMessage(message.into())
23638 }
23639 BreakpointPromptEditAction::Condition => {
23640 BreakpointEditAction::EditCondition(message.into())
23641 }
23642 BreakpointPromptEditAction::HitCondition => {
23643 BreakpointEditAction::EditHitCondition(message.into())
23644 }
23645 },
23646 cx,
23647 );
23648
23649 editor.remove_blocks(self.block_ids.clone(), None, cx);
23650 cx.focus_self(window);
23651 });
23652 }
23653 }
23654
23655 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23656 self.editor
23657 .update(cx, |editor, cx| {
23658 editor.remove_blocks(self.block_ids.clone(), None, cx);
23659 window.focus(&editor.focus_handle);
23660 })
23661 .log_err();
23662 }
23663
23664 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23665 let settings = ThemeSettings::get_global(cx);
23666 let text_style = TextStyle {
23667 color: if self.prompt.read(cx).read_only(cx) {
23668 cx.theme().colors().text_disabled
23669 } else {
23670 cx.theme().colors().text
23671 },
23672 font_family: settings.buffer_font.family.clone(),
23673 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23674 font_size: settings.buffer_font_size(cx).into(),
23675 font_weight: settings.buffer_font.weight,
23676 line_height: relative(settings.buffer_line_height.value()),
23677 ..Default::default()
23678 };
23679 EditorElement::new(
23680 &self.prompt,
23681 EditorStyle {
23682 background: cx.theme().colors().editor_background,
23683 local_player: cx.theme().players().local(),
23684 text: text_style,
23685 ..Default::default()
23686 },
23687 )
23688 }
23689}
23690
23691impl Render for BreakpointPromptEditor {
23692 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23693 let editor_margins = *self.editor_margins.lock();
23694 let gutter_dimensions = editor_margins.gutter;
23695 h_flex()
23696 .key_context("Editor")
23697 .bg(cx.theme().colors().editor_background)
23698 .border_y_1()
23699 .border_color(cx.theme().status().info_border)
23700 .size_full()
23701 .py(window.line_height() / 2.5)
23702 .on_action(cx.listener(Self::confirm))
23703 .on_action(cx.listener(Self::cancel))
23704 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23705 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23706 }
23707}
23708
23709impl Focusable for BreakpointPromptEditor {
23710 fn focus_handle(&self, cx: &App) -> FocusHandle {
23711 self.prompt.focus_handle(cx)
23712 }
23713}
23714
23715fn all_edits_insertions_or_deletions(
23716 edits: &Vec<(Range<Anchor>, String)>,
23717 snapshot: &MultiBufferSnapshot,
23718) -> bool {
23719 let mut all_insertions = true;
23720 let mut all_deletions = true;
23721
23722 for (range, new_text) in edits.iter() {
23723 let range_is_empty = range.to_offset(&snapshot).is_empty();
23724 let text_is_empty = new_text.is_empty();
23725
23726 if range_is_empty != text_is_empty {
23727 if range_is_empty {
23728 all_deletions = false;
23729 } else {
23730 all_insertions = false;
23731 }
23732 } else {
23733 return false;
23734 }
23735
23736 if !all_insertions && !all_deletions {
23737 return false;
23738 }
23739 }
23740 all_insertions || all_deletions
23741}
23742
23743struct MissingEditPredictionKeybindingTooltip;
23744
23745impl Render for MissingEditPredictionKeybindingTooltip {
23746 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23747 ui::tooltip_container(window, cx, |container, _, cx| {
23748 container
23749 .flex_shrink_0()
23750 .max_w_80()
23751 .min_h(rems_from_px(124.))
23752 .justify_between()
23753 .child(
23754 v_flex()
23755 .flex_1()
23756 .text_ui_sm(cx)
23757 .child(Label::new("Conflict with Accept Keybinding"))
23758 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23759 )
23760 .child(
23761 h_flex()
23762 .pb_1()
23763 .gap_1()
23764 .items_end()
23765 .w_full()
23766 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23767 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23768 }))
23769 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23770 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23771 })),
23772 )
23773 })
23774 }
23775}
23776
23777#[derive(Debug, Clone, Copy, PartialEq)]
23778pub struct LineHighlight {
23779 pub background: Background,
23780 pub border: Option<gpui::Hsla>,
23781 pub include_gutter: bool,
23782 pub type_id: Option<TypeId>,
23783}
23784
23785struct LineManipulationResult {
23786 pub new_text: String,
23787 pub line_count_before: usize,
23788 pub line_count_after: usize,
23789}
23790
23791fn render_diff_hunk_controls(
23792 row: u32,
23793 status: &DiffHunkStatus,
23794 hunk_range: Range<Anchor>,
23795 is_created_file: bool,
23796 line_height: Pixels,
23797 editor: &Entity<Editor>,
23798 _window: &mut Window,
23799 cx: &mut App,
23800) -> AnyElement {
23801 h_flex()
23802 .h(line_height)
23803 .mr_1()
23804 .gap_1()
23805 .px_0p5()
23806 .pb_1()
23807 .border_x_1()
23808 .border_b_1()
23809 .border_color(cx.theme().colors().border_variant)
23810 .rounded_b_lg()
23811 .bg(cx.theme().colors().editor_background)
23812 .gap_1()
23813 .block_mouse_except_scroll()
23814 .shadow_md()
23815 .child(if status.has_secondary_hunk() {
23816 Button::new(("stage", row as u64), "Stage")
23817 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23818 .tooltip({
23819 let focus_handle = editor.focus_handle(cx);
23820 move |window, cx| {
23821 Tooltip::for_action_in(
23822 "Stage Hunk",
23823 &::git::ToggleStaged,
23824 &focus_handle,
23825 window,
23826 cx,
23827 )
23828 }
23829 })
23830 .on_click({
23831 let editor = editor.clone();
23832 move |_event, _window, cx| {
23833 editor.update(cx, |editor, cx| {
23834 editor.stage_or_unstage_diff_hunks(
23835 true,
23836 vec![hunk_range.start..hunk_range.start],
23837 cx,
23838 );
23839 });
23840 }
23841 })
23842 } else {
23843 Button::new(("unstage", row as u64), "Unstage")
23844 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23845 .tooltip({
23846 let focus_handle = editor.focus_handle(cx);
23847 move |window, cx| {
23848 Tooltip::for_action_in(
23849 "Unstage Hunk",
23850 &::git::ToggleStaged,
23851 &focus_handle,
23852 window,
23853 cx,
23854 )
23855 }
23856 })
23857 .on_click({
23858 let editor = editor.clone();
23859 move |_event, _window, cx| {
23860 editor.update(cx, |editor, cx| {
23861 editor.stage_or_unstage_diff_hunks(
23862 false,
23863 vec![hunk_range.start..hunk_range.start],
23864 cx,
23865 );
23866 });
23867 }
23868 })
23869 })
23870 .child(
23871 Button::new(("restore", row as u64), "Restore")
23872 .tooltip({
23873 let focus_handle = editor.focus_handle(cx);
23874 move |window, cx| {
23875 Tooltip::for_action_in(
23876 "Restore Hunk",
23877 &::git::Restore,
23878 &focus_handle,
23879 window,
23880 cx,
23881 )
23882 }
23883 })
23884 .on_click({
23885 let editor = editor.clone();
23886 move |_event, window, cx| {
23887 editor.update(cx, |editor, cx| {
23888 let snapshot = editor.snapshot(window, cx);
23889 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23890 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23891 });
23892 }
23893 })
23894 .disabled(is_created_file),
23895 )
23896 .when(
23897 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23898 |el| {
23899 el.child(
23900 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23901 .shape(IconButtonShape::Square)
23902 .icon_size(IconSize::Small)
23903 // .disabled(!has_multiple_hunks)
23904 .tooltip({
23905 let focus_handle = editor.focus_handle(cx);
23906 move |window, cx| {
23907 Tooltip::for_action_in(
23908 "Next Hunk",
23909 &GoToHunk,
23910 &focus_handle,
23911 window,
23912 cx,
23913 )
23914 }
23915 })
23916 .on_click({
23917 let editor = editor.clone();
23918 move |_event, window, cx| {
23919 editor.update(cx, |editor, cx| {
23920 let snapshot = editor.snapshot(window, cx);
23921 let position =
23922 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23923 editor.go_to_hunk_before_or_after_position(
23924 &snapshot,
23925 position,
23926 Direction::Next,
23927 window,
23928 cx,
23929 );
23930 editor.expand_selected_diff_hunks(cx);
23931 });
23932 }
23933 }),
23934 )
23935 .child(
23936 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23937 .shape(IconButtonShape::Square)
23938 .icon_size(IconSize::Small)
23939 // .disabled(!has_multiple_hunks)
23940 .tooltip({
23941 let focus_handle = editor.focus_handle(cx);
23942 move |window, cx| {
23943 Tooltip::for_action_in(
23944 "Previous Hunk",
23945 &GoToPreviousHunk,
23946 &focus_handle,
23947 window,
23948 cx,
23949 )
23950 }
23951 })
23952 .on_click({
23953 let editor = editor.clone();
23954 move |_event, window, cx| {
23955 editor.update(cx, |editor, cx| {
23956 let snapshot = editor.snapshot(window, cx);
23957 let point =
23958 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23959 editor.go_to_hunk_before_or_after_position(
23960 &snapshot,
23961 point,
23962 Direction::Prev,
23963 window,
23964 cx,
23965 );
23966 editor.expand_selected_diff_hunks(cx);
23967 });
23968 }
23969 }),
23970 )
23971 },
23972 )
23973 .into_any_element()
23974}